计算机执行的是机器代码,机器代码是二进制文件,既程序。机器代码用字节(1Byte=8bit)序列编码低级的操作,例如数据处理,管理存储器,从存储设备取数据等。使用高级语言(c,c++等)编写的程序(文本形式)最终需要被编译成机器代码才可以被计算机执行。当使用GCC c编译器来编译c语言代码时,会首先将其编译成汇编语言形式的内容,然后c编译器调用汇编器和连接器来最终形成计算机可执行的机器代码。汇编语言代码是机器码的文本形式,是机器码(字节序列)的文本助记形式。现在的要求是可以理解经过编译器优化过的汇编代码。
在Unix/Linux系统中,gcc是默认的编译器,也可以使用cc命令来调用gcc编译器。gcc命令实际上调用了一系列的程序来完成程序的编译工作。首先,c语言预处理器扩展程序源代码,插入#include加入的文件的源码,扩展使用#define声明的宏。然后编译器将源码文件编译为汇编语言文件(file.s形式),接下来编译器将汇编代码文件编译为目标文件(file.o形式),目标文件中的内容是目标代码,目标代码是机器码的另一种形式,它包含所有指令的机器码形式,但是没有填入地址的全局值,最后,连接器将目标文件与实现库函数的代码合并,形成最后可以执行的代码文件。
计算机系统为程序提供各种各样的抽象来屏蔽实现细节,对于机器级编程来说,提供的抽象属于比较低级的抽象。对机器级编程最重要的为两个抽象。第一个抽象是机器级程序的格式和行为,定义为指令集体系结构(instruction set architecture,ISA),它定义了处理器状态,指令的格式和行为,以及每条指令对状态的影响。大多数ISA(包括IA32和X86-64)将指令的执行描述成顺序的,但是实际上计算机系统并行的执行很多条指令。但是提供给机器级程序的抽象使得每条指令像是顺序执行的。第二种抽象是存储器地址是虚拟地址,机器级程序将存储器看成是一个大的字节数组,但是实际上存储器系统是由多个硬件存储器和操作系统软件组成的。
相对于c语言程序,IA32代码差别很大。许多对c语言屏蔽的细节在IA32程序中是可见的。(1)PC:程序计数器,保存下一个执行指令的地址(IA32中用%ebp表示)。(2)整数寄存器文件,包括8个命名的存储器(存储位置),分别存储32位的值,这些存储器存储地址或者整数数据。(3)浮点数存储器存储浮点数据。(4)条件码寄存器:保存最近执行的算术或者逻辑指令的状态信息,它们用来实现控制或者数据流中的条件变化,比如说if或者while的实现。
同时,相对于c语言,机器码不区分数据结构,机器码只是将存储器(内存)看做连续的字节数组,c语言中的聚合数据类型,例如数组或者对象,在机器码中只是一组字节。程序存储器(内存)中存储的信息主要包括:程序的可执行机器代码,操作系统需要的一些信息,用来管理过程调用和返回的运行时栈,以及用户分配的存储器块(比如使用malloc库函数申请的块)。操作系统负责管理地址,用于将虚拟地址映射到存储器的真实物理地址。