目录
成都创新互联主营祁门网站建设的网络公司,主营网站建设方案,app软件开发公司,祁门h5小程序定制开发搭建,祁门网站营销推广欢迎祁门等地区企业咨询1. 汇编语言和机器级语言
1.1 不同的编程语言
1.2 Linux下的汇编语言
2. 程序编码
2.1 机器级代码
2.2 代码示例
3. 数据格式
1. 汇编语言和机器级语言 1.1 不同的编程语言本文基于CSAPP第三章撰写,主要介绍部分x86-64汇编的相关知识,后续会将该部分内容慢慢完善(PS:所有代码编译和汇编均在Linux环境下进行)
从计算机诞生至今,编程语言总数超过2500种编程语言,其发展大致经历了四个阶段
编程语言的发展简史——编年史
🔷 机器语言
在最早期采用穿孔纸带保存程序(1-打孔,0-不打孔)
优点:
1. 速度快 2. 占存储空间小 3. 翻译质量高缺点:
1. 可移植性差 2. 编译难度大 3. 直观性差 4. 调试困难
🔷 汇编语言
用助记符代替机器指令的操作码,用地址符号或标号代替指令或操数的地址
- 机器指令:1000100111011000
- 操 作:寄存器bx的内容送到ax中
- 汇编指令:mov %bx, %ax,
汇编指令同机器指令是一一对应的关系
优点:
1.执行速度快 2.占存储空间小 3.可读性有所提高缺点:
1.类似机器语言 2.可移植性差 3.与人类语言还相差很悬殊
🔷 高级语言
C++和Java等高级语言与汇编语言及机器语言之间是一对多的关系
一条简单的C++语句会被扩展成多条汇编语言或者机器语言指令
高级语言到机器语言的转换方法:
汇编语言与高级语言的比较
应用程序类型 | 高级语言 | 汇编语言 |
用于单一平台的中到大型商业应用软件 | 正式的结构化支持使组织和维护大量代码很方便 | 最小的结构支持使程序员需要人工组织大量代码,使各种不同水平的程序员维护现存代码的难度极高 |
硬件驱动程序 | 语言本身未必提供直接访问硬件的能力,即使提供了也因为要经常使 用大量的技巧而导致维护困难 | 硬件访问简单直接。当程序很短并且文档齐全时很容易维护 |
多种平台下的商业应用软件 | 可移植性好,在不同平 台可以重新编译,需要 改动的源代码很少 | 必须为每种平台重新编写程序,通常要使用不同的汇编语言,难于维护 |
需要直接访问硬件的嵌入式系统和计算机游戏 | 由于生成的执行代码过 大,执行效率低 | 很理想,执行代码很小并且运行很快 |
x86汇编语言通常有两种风格——AT&T汇编、Intel 汇编,其中DOS操作系统采用Intel汇编风格,而Linux下采用AT&T汇编风格
AT&T汇编 | Intel 汇编 | 说明 | |
寄存器前缀% | %eax | eax | Intel省略了寄存器前缀% |
源/目的操作数顺序 | movl %eax,%ebx | mov ebx,eax | AT&T源操作数在前,目的操作数在后,Intel反之 |
常数/立即数的格式$ | movl $0xd00d, %ebx | mov ebx,0xd00d | AT&T在立即数前加$ |
操作数长度标识 | movw var_x, %bx (b-1字节,w-2字节,l-4 字节,q-8 字节) | mov bx, word ptr var_x | Intel省略了指示大小的后缀 |
寻址方式 | imm32(basepointer, indexpointer,indexscale) | [basepointer+indexpointer *indexscale + imm32] | Linux寻址将在后面介绍 |
对两个C语言文件p1.c和p2.c进行编译的命令行为
gcc -Og -o p p1.c p2.c
再来回顾一下程序的编译链接过程
对于机器级编程来说,有两种抽象尤为重要
- 第一种是指令集体系结构或指令集架构(Instruction Set Architecture,ISA),它定义了处理器状态、指令的格式以及每条指令对状态的影响
- 第二种是机器级程序使用的内存地址是虚拟地址,有关虚拟地址的内容以后将会介绍
在整个编译的过程中,编译器会完成大部分的工作,编译过后生成的汇编代码十分接近于机器代码,但与二进制的机器代码相比,汇编代码用文本格式表示,具有更好的可读性
x86-64的机器代码和原始的C代码差别很大,一些通常对C语言程序员隐藏的处理器状态都是可见的:
机器级代码将内存简单地看成一个很大的、按字节寻址的数组(这意味着它并不像C语言一样会将内存分为多个部分,它只会按照地址来处理数据)。对于数据类型,汇编代码并不区分有符号或无符号整数、不区分各种指针,甚至不区分指针和整数
一条机器指令只执行一个非常基本的操作。例如,将两个寄存器中的数值相加,或简单地在寄存器之间传送数据。然而,整个程序的构建正是由这一条条简单的指令所构成
2.2 代码示例假设我们写了一个C语言程序mstore.c,包含如下函数定义
long mult2(long, long);
void multstore(long x, long y, long* dest)
{
long t = mult2(x, y);
*dest = t;
}
仅对其进行编译的命令行和生成的文件为
gcc -Og -S mstore.c
-rw-rw-r-- 1 hqs hqs 393 Jan 12 11:52 mstore.s
得到mstore.s文件,用cat将其打印到屏幕上可以看到原始C代码的汇编为
[hqs@VM-8-2-centos test]$ cat mstore.s
.file "mstore.c"
.text
.globl multstore
.type multstore, @function
multstore:
.LFB0:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
movq %rdx, %rbx
call mult2
movq %rax, (%rbx)
popq %rbx
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size multstore, .-multstore
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
.section .note.GNU-stack,"",@progbits
所有以‘.’开头的都是指导汇编器和链接器工作的伪指令,通常可以忽略。关于.cfi指令,有兴趣可以查看CFI directives (Using as) ,这里不对进行展开
上述汇编代码中最主要的几行如下,每条指令都对应一条机器级指令
multstore:
pushq %rbx
movq %rdx, %rbx
call mult2
movq %rax, (%rbx)
popq %rbx
ret
接下来我们使用mstore.c文件汇编后生成的mstore.o文件,所使用的命令行和生成的文件为
gcc -Og -c mstore.c
-rw-rw-r-- 1 hqs hqs 1368 Jan 12 12:06 mstore.o
使用gdb对这段代码进行调试,使用如下的命令行可以展示函数multstore的二进制代码
[hqs@VM-8-2-centos test]$ gdb mstore.o
(gdb) x/14xb multstore
这条命令告诉GDB显示(简写为‘x’)从函数multstore开始位置往后的14字节(简写为'b')内容的十六进制表示(简写为'x')
得到的结果为如下
(gdb) x/14xb multstore
0x0: 0x53 0x48 0x89 0xd3 0xe8 0x00 0x00 0x00
0x8: 0x00 0x48 0x89 0x03 0x5b 0xc3
但是仅仅这样并没有很好地观察到汇编代码和二进制机器代码的对应关系,这时使用objdump -d命令完成对于.o文件的反汇编
objdump -d:
- 检查目标代码的有用工具
- 分析指令的位模式
- 生成近似的汇编代码表述/译文
- 可处理a.out (完整可执行文件)或 .o文件
可以看到下面的每一条汇编指令都存在一条相应的二进制指令,比如 push %rbx对应的二进制指令为53,mov %rdx %rbx对应的二进制指令为 48 89 d3
[hqs@VM-8-2-centos test]$ objdump -d mstore.o
mstore.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000:
0: 53 push %rbx
1: 48 89 d3 mov %rdx,%rbx
4: e8 00 00 00 00 callq 99: 48 89 03 mov %rax,(%rbx)
c: 5b pop %rbx
d: c3 retq
Disassembly of section .text 表示这是.text节的反汇编,也就是程序的代码段的产生的汇编
最左边的一行表示这条指令的相对于.text段起始地址的偏移量,也就是相对于程序第一条代码的偏移量,可以理解为相对位置,因此最后一条指令的偏移量为十六进制数d,也就是13,而指令长度为1个字节,因此这段程序的机器级代码总长度为14字节
机器级代码和它的反汇编表示有一些值得注意的地方
接下来我们创建一个完整的工程,添加一个main函数,其中包括mult2函数的定义
#includevoid multstore(long, long, long*);
int main()
{
long d;
multstore(2, 3, &d);
printf("2 * 3 -->%ld\n", d);
return 0;
}
long mult2(long a, long b)
{
long s = a * b;
return s;
}
用下面的命令生成可执行程序prog
gcc -Og -o prog main.c mstore.c
-rwxrwxr-x 1 hqs hqs 8456 Jan 12 13:49 prog
同样用objdump -d查看prog文件的反汇编
000000000040056b:
40056b: 53 push %rbx
40056c: 48 89 d3 mov %rdx,%rbx
40056f: e8 ef ff ff ff callq 400563400574: 48 89 03 mov %rax,(%rbx)
400577: 5b pop %rbx
400578: c3 retq
这时prog文件相比于之前的mstore.o文件,经过了链接的过程,所有的指令都带上了地址,而不再是相对于起始位置的偏移了,之后在链接部分会介绍可重定位目标文件和可执行目标文件文件的差别
3. 数据格式由于是从16位体系结构扩展为32位的,Intel用术语“字(word)”来表示16位数据类型
在x86-64指令集中,各种数据类型之间的关系为
C声明 | Intel数据类型 | 汇编代码后缀 | 大小(字节) |
char | 字节 | b | 1 |
short | 字 | w | 2 |
int | 双字 | l | 4 |
long | 四字 | q | 8 |
char* | 四字 | q | 8 |
float | 单精度 | s | 4 |
double | 双精度 | l | 8 |
大多数GCC生成的汇编代码指令都有一个字符的后缀,表示操作数的大小,如
movq %rdx, %rbx
mov是原始指令,后面跟了一个q,说明在两个寄存器之间操作的数是四字类型的,在C语言中可表示long或者指针。因此,一条指令可能有多个变种,mov指令可能有movb、movw、movl和movq四种类型
值得注意的是,汇编代码也使用后缀 'l' 来表示四字节整数和双精度浮点数,由于前者使用整数寄存器,后者使用浮点寄存器,所有并不会产生歧义
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧