- 假设有一个c程序,在linux系统下便可以使用gcc指令来编译这些代码
实际上gcc指令调用了一系列的程序
首先是用c预处理器扩展源代码,插入所有用#include命令指定的文件,并扩展所有用#define指定的宏
其次,编译器会生成两个源文件的汇编代码,扩展名为.s
接下来,汇编器会将汇编代码转化成二进制目标代码文件,扩展名为.o
最后,链接器会将目标代码文件与实现库函数的代码合并,并生成最终的二进制可执行文件
机器级编码
- 机器级编程的两种抽象
第一种是由指令集体系结构或指令集架构(ISA)来定义机器级程序的格式和行为,它定义了处理器状态、指令的格式,以及每条指令对状态的影响。
第二种是,机器级程序使用的内存地址是虚拟地址,提供的内存模型类似一个很大的字节数组,它实际上是由多个硬件存储器和操作系统软件组合而来的。 - 在机器代码中,存在一下几类处理器状态
- 程序计数器 (%rip)给出将要执行的下一条指令在内存中的地址
- 整数寄存器 包含16个命名的位值,分别存储64位的值,可以存放地址或整数数据
- 条件码寄存器 保存最近执行的算术或逻辑指令的状态信息,他们用来实现控制数据流中的条件变化。
- 向量寄存器 可以存放一个或多个整数或浮点的值。
- 机器代码不会区分内存中存放的数据类型。
- 内存程序包含了:程序的可执行代码,操作系统需要的一些信息,用来管理过程调用和返回的运行时栈,以及用户分配的内存块。
- 在任意给定时刻,只有有限的一部分虚拟地址被认为是合法的。
例如x86-64的虚拟地址是由64位的字来表示的,而其中高16位必须为0,所以一个地址所能指定的是256TB范围内的一个字节。 - 一条机器指令只执行一个非常基本的操作,编译器必须产生这些指令的序列,才能实现程序结构。
代码实例
long mult2(long, long);
void mulstore(long x, long y, long *dest)
{
long t = mult2(x, y);
*dest = t;
}
将这段代码以gcc -Og -S mstore.c的指令编译后得到mstore.s的汇编文件,其中包括以下代码
multstore :
pushq %rbx
movq %rdx, %rbx
call mult2
movq %rax, (%rbx)
popq %rbx
ret
此时用指令gcc -Og -c mstore.c,便会生成mstore.o,其中有一段14字节的序列
53 48 89 d3 e8 00 00 00 00 48 89 03 5b c3
分别对应上方的每条指令
- 以下是一些关于机器码和它的反汇编表示的特性
- x86-64的指令长度从1到15个字节不等
- 设计指令格式的方式是,从某给定位置开始,可以将字节唯一地解码哼机器指令。
- 反汇编器只是基于机器代码文件中的字节序列来确定汇编代码。
- 反汇编器使用的指令名规则与gcc生成的汇编代码使用的有些细微的差别。
关于格式的注解
- gcc生成的汇编代码中会包含以“.”开头的指令,其为指导汇编器和链接器工作的伪指令
- 为了清楚地说明汇编代码,我们会省略大部分伪指令,并写入注释
void multstore(long x,long y,long *dest)
x in %rdi,y in %rsi,dest in %rdx
1multstore :
2 pushq %rbx save %rbx
3 movq %rdx, %rbx copy dest to %rbx
4 call mult2 call mult2 (x,y)
5 movq %rax, (%rbx) store result at *dest
6 popq %rbx restore %rbx
7 ret return