程序的机器级表示
汇编代码是机器代码的文本表示,给出程序中的每一条指令。然后GCC 调用汇编器和链接器,根据汇编代码生成可执行的机器代码。
通常情况下,使用现代的优化编译器产生的代码至少与一个熟练的汇编语言程序员手工编写的代码一样有效。最大的优点是,用高级语言编写的程序可以在很多不同的机器上编译和执行,而汇编代码则是与特定机器密切相关的。
1. 机器级代码
计算机系统使用了多种不同形式的抽象,利用更简单的抽象模型来隐藏实现的细节。对于机器级编程来说,其中两种抽象尤为重要。
第一种是由指令集体系结构或指令集架构(Instruction Set Architecture, ISA)来定义机器级程序的格式和行为,它定义了处理器状态、指令的格式,以及每条指令对状态的影响。大多数IAS将程序的行为描述成好像每条指令都是按顺序执行的,一条指令结束后,下一条再开始。处理器并发的执行许多指令,但是通过一些措施保证整体行为与ISA指定的顺序执行的行为完全一致。
第二种是机器级程序使用的内存地址是虚拟地址,提供的内存模型看上去是一个非常大的字节数组。
x86-64的机器代码和原始的C代码差别非常大。一些通常隐藏的处理器状态都是可见的:
- 程序计数器PC,用%rip表示,给出将要执行的吓一条指令在内存中的地址。
- 整数寄存器文件,包含16个命名的位置,分别存储64位的值。这些寄存器可以存储地址(对应于指针)或整数数据。有的寄存器被用来记录某些重要的程序状态,而其他的寄存器用来保存临时数据,例如过程的参数和局部变量,以及函数的返回值。
- 条件码寄存器保存着最近执行的算数或逻辑指令的状态信息。他们用来实现控制或数据流中的条件变化,比如说用来实现if和while语句。
- 一组向量寄存器可以存放一个或多个整数或浮点数值。
虽然C 语言提供了一种模型,可以在内存中声明和分配各种数据类型的对象,但是机器代码只是简单地将内存看成一个很大的、按字节寻址的数组。C 语言中的聚合数据类型,例如数组和结构,在机器代码中用一组连续的字节来表示。即使是对标量数据类型,汇编代码也不区分有符号或无符号整数,不区分各种类型的指针,甚至于不区分指针和整数。
程序内存包含:程序的可执行机器代码,操作系统需要的一些信息,用来管理过程调用和返回的运行时栈,以及用户分配的内存块(比如说用malloc 库函数分配的)。正如前面提到的,程序内存用虚拟地址来寻址。在任意给定的时刻,只有有限的一部分虚拟地址被认为是合法的。例如, x86-64 的虚拟地址是由64 位的字来表示的。在目前的实现中,这些地址的高16 位必须设置为o, 所以一个地址实际上能够指定的是248 或64TB 范围内的一个字节。较为典型的程序只会访问几兆字节或几千兆字节的数据。操作系统负责管理虚拟地址空间,将虚拟地址翻译成实际处理器内存中的物理地址。
一条机器指令只执行一个非常基本的操作。例如,将存放在寄存器中的两个数字相加,在存储器和寄存器之间传送数据,或是条件分支转移到新的指令地址。编译器必须产生这些指令的序列,从而实现(像算术表达式求值、循环或过程调用和返回这样的)程序结构。
2. 代码示例
有一个代码文件mstore.c如下:
long mul2(long, long);
void multstroe(long x, long y, long *dest){
long t = mult2(x, y);
*dest = t;
}
相应的汇编代码文件如下:
multstore:
pushq %rbx
movq %rdx, %rbx
call mult2
movq %rax, (%rbx)
popq %rbx
ret
相应的目标代码文件如下(16进制表示):
反汇编得到
生成实际可执行的代码需要对一组目标代码文件运行链接器,而这一组目标代码文件中必须含有一个main 函数。假设在文件main.c 中有下面这样的函数:
将main.c和mstore.c使用gcc生成可执行文件prog,prog文件有8655个字节,因为它不仅包含了两个过程的代码,还包含了用来启动和终止程序的代码,以及用来与操作系统交互的代码。
反汇编这个东西,其中一段代码序列展示如下:
这段代码与mstore.c 反汇编产生的代码几乎完全一样。其中一个主要的区别是左边列出的地址不同——链接器将这段代码的地址移到了一段不同的地址范围中。第二个不同之处在千链接器填上了callq 指令调用函数mult2 需要使用的地址(反汇编代码第4 行)。链接器的任务之一就是为函数调用找到匹配的函数的可执行代码的位置。最后一个区别是多了两行代码(第8 和9 行)。这两条指令对程序没有影响,因为它们出现在返回指令后面(第7 行)。插入这些指令是为了使函数代码变为16 字节,使得就存储器系统性能而言,能更好地放置下一个代码块。
3. 对汇编代码格式的注解
mstore.s的完整内容如下:
所有以“.”开头的行都是指导汇编器和链接器工作的伪指令。我们通常可以忽略这些行。为了更清楚地说明汇编代码,我们用这样一种格式来表示汇编代码,它省略了大部分伪指令,但包括行号和解释性说明。对于mstore.s,带解释的汇编代码如下: