第三章:程序的转换
概述
计算机硬件只能识别和理解机器语言程序,用各种汇编语言或高级语言编写的源程序都要翻译(汇编,解释或编译)成以机器指令形式表示的机器语言才能在计算机中执行。
计算机的指令有微指令,机器指令和伪(宏)指令之分。微指令是微程序级命令,属于硬件范畴;伪指令是由若干机器指令组成的指令序列,属于软件范畴;机器指令介于二者之间,处于硬件和软件的交界面。
指令结构体系
在计算机系统的抽象层中,最重要的抽象层就是指令集体系结构(ISA),他作为计算机硬件之上的抽象层,对使用硬件的软件屏蔽了底层硬件的实现细节,将物理上的计算机硬件抽象成一个逻辑上的虚拟计算机,称为机器语言级虚拟机。
ISA 定义了机器语言级虚拟机的属性和功能特性,主要包括以下信息:
可执行的指令集合,包括指令格式,操作种类以及每种操作对应的操作数的相应规定;
指令可接收的操作数的类型;
操作数或其地址所能存放的寄存器组的结构,包括每个寄存器的名称,编号,长度和用途;
操作数所能存放的存储空间的大小和编制方式;
操作数在存储空间存放时按照大端方式还是小端方式存放;
指令获取操作数以及下一条指令的方式,即寻址方式;
指令执行过程的控制方式,包括程序计数器,条件码定义等。
生成机器代码的过程
通常,将一个 C 语言程序转换为可执行目标代码的过程分为 4 个步骤:
预处理:在预处理阶段对带有 # 开头的语句进行处理,在源程序中插入所有用 #include 命令指定的文件和用 #define 声明指定的宏。
编译:将预处理后的源程序编译生成相应的汇编语言程序。
汇编:由汇编程序将汇编语言转换为可重定向的机器语言目标代码文件。
链接:由链接器将多个可重定位的机器语言目标文件以及库文件链接起来,生成最终的可执行文件。
源程序文件后缀名为 .c,所包含的头文件后缀名为 .h;预处理过的源文件后缀名为 .i;汇编语言源程序后缀名为 .s;编译后的可重定向文件后缀名为 .o,最终生成的可执行代码文件没有后缀。
寄存器与寻址方式
寄存器
IA-32 中的定点寄存器中共有 8 个通用寄存器,2 个专用寄存器和 6 个段寄存器。
8 个通用寄存器的长度为 32 位,其中 EAX, EBX, ECX, EDX 主要用来存放操作数,可根据操作数长度是字节,字还是双子来确定存取寄存器的最低 8 位,16 位还是全部 32位。ESP, EBP, ESI, EDI 主要用来存放变址值或指针。ESP 是栈顶指针,EBP 是栈底指针。
2 个专用寄存器分别是指令指针寄存器 EIP 和标志寄存器 EFLAGS。IP 与程序计数器 PC 是功能完全一样的寄存器,名称不同而已。EFLAGS 內有各种标志信息,用来记录操作数的状态。常见的标志信息有: OF:溢出标志,反映带符号数的运算结果是否超过相应数值范围。 SF:符号标志,反映带符号数运算结果的符号。 CF:进/借位标志,反映无符号整数加减运算后的进借位情况。 DF:方向标志,用来确定串操作指令执行时编制寄存器 ESI 和 EDI 中的内容是自增还是自减。 IF:中断允许标志。IF 对非屏蔽中断和内部异常不起作用,仅对外部可屏蔽中断起作用。 TF:陷阱标志,用来控制单步执行操作。
寻址方式
根据指令给定的信息得到操作数或操作数地址的方式称为寻址方式。
立即寻址是指指令中直接给出操作数;寄存器寻址指指令中给出操作数所存放的寄存器的编号。除了立即寻址和寄存器寻址外,其他寻址方式下的操作数都在存储单元中,也称为存储器操作数。
指令系统概述
数据类型
c 语言基本数据类型和 IA-32 操作数类型的对应关系:
c 语言声明 |
Intel 操作数类型 |
汇编指令长度后缀 |
存储长度(字节) |
(unsigned) char |
整数/字节 |
b |
1b |
(unsigned) short |
整数/字 |
w |
2b |
(unsigned) int |
整数/双字 |
l |
4b |
(unsigned) long int |
整数/双字 |
l |
4b |
(unsigned) long long int |
- |
- |
8b |
char * |
整数/双字 |
l |
4b |
float |
单精度浮点数 |
s |
4b |
double |
双精度浮点数 |
l |
8b |
long double |
扩展精度浮点数 |
t |
10b~12b |
在表中,可以看出双字整数和双精度浮点数的长度后缀都一样,因为已经通过指令操作码区分了是浮点数还是整数,所以长度后缀相同不会产生歧义。
C 语言程序的基本数据类型主要有以下几类:
指针或地址:用来表示字符串或其他数据区域的指针或存储地址,可声明为 char * 等类型,其宽度为 32 位,对应 IA-32 的双字。
序数,位串:用来表示序号,元素个数,元素总长度,位串等的无符号数。
带符号整数:是 C 语言应用最广泛的基本数据类型,可声明位 char,short,int,long 等。
浮点数:用来表示实数,可声明为 float,double 和 long double 等。
指令类型
1. 传送指令
传送指令用于寄存器,存储单元或 I/O 端口之间传送信息。
通用数据传送指令:
MOV:一般的传送指令,包括 movb(字节传送),movw(单字传送),movl(双字传送);
MOVS:符号扩展传送指令,将短的源数据按高位符号扩展后传送到目的地址,如 movzwl 表示把一个字节进行符号扩展后送到一个字地址中。
MOVZ:零扩展传送指令,将短的源数据按高位零扩展后传送到目的地址。
XCHG:数据交换指令,将两个寄存器内容互换。
PUSH:压栈操作,先将栈下移一个单元,为新进成员让出空间,然后再把新进成员放进来。
POP:出栈操作,先将栈顶成员算到目的寄存器中,再上移缩小栈空间。
地址传送指令:LEA 指令,主要是加载有效地址,用来将源操作数的存储地址送到目的寄存器中。
输入输出指令:专门用于累加器和 I/O 端口之间进行数据传送。例如:in 指令用于将 I/O 端口内容送至累加器,out 指令将累加器内容送至 I/O 端口。
标志传送指令:标志传送指令专门用于对标志寄存器进行操作。如 pushf 指令用于将标志寄存器的内容压栈,popf 指令将栈顶内容送至标志寄存器。
2. 定点算术运算指令
加/减运算指令:ADD/SUB。用于对给定长度的两个位串进行相加或相减,两个操作数中最多只有一个是存储器操作数,不区分是无符号数还是带符号整数,产生的和/差送到目的地址,生成的标志信息送到标志寄存器 EFLAGS。
增/减运算指令:INC/EDC。对给定长度的一个位串加一或减一,给定的操作数既是源操作数,也是目的操作数。
取负指令:NEG。用于求操作数的负数。
比较指令:CMP。用于两个寄存器操作数的比较,用目的操作数减去源操作数,结果不送回目的操作数,即两个操作数保持不变,只是标志位作相应改变,因而类似于 SUB 指令。
乘法指令: 无符号数乘:MUL。只能明显给出一个操作数。 带符号数乘:IMUL。可以明显给出一个,两个或三个操作数。
> 若指令只给出一个操作数,则另一个源操作数隐含在累加器 EAX 中。对于 MUL 指令,若乘积高 n 位为全 0,则标志 OF 和 CF 皆为 0,否则皆为 1。对于 IMUL 指令,若乘积的高 n 位为全 0 或全 1,并且等于低 n 位中的最高位,则 OF 和 CF 皆为 0,否则皆为 1。除法指令: 无符号数除:DIV。 有符号数除:IDIV
> 指令中只能明显指出除数,用累加器 EAX,EDX 存放被除数。若源操作数(除数)是 32 位,则 64 位被除数隐含在 EDX-EAX 寄存器中,计算后的商在 EAX,余数在 EDX。
3. 按位运算指令
逻辑运算指令:
NOT:单操作数取反指令,将操作数每一位取反,然后把结果送回对应位。
AND:对双操作数进行按位逻辑“与”,主要用来实现“掩码”操作。
OR:对双操作数进行按位逻辑“或”,常用于使目的操作时的特定位置为 1.
XOR:对双操作数进行按位逻辑“异或”,常用于判断两个操作数中哪些位不同或用于改变指定位的值。
TEST:根据两个操作数相“与”的结果来设置标志位,常用于检测某种条件但不能改变源操作数的场合。
移位指令:
SHL:逻辑左移,每左移一次,最高位送入 CF,并在低位补 0。
SHR:逻辑右移,每右移一次,最低位送入 CF,并在低位补 0。
SAL:算术左移,操作与 SHL 类似,每次移位,最高位送入 CF,并在低位补 0.执行 SAL 指令使,如果移位前后符号位发生变化,则 OF=1,表示左移后结果溢出。这是 SAL 与 SHL 的不同之处。
SAR:算术右移,每右移一次,操作数的最低位送入 CF,并在高位补符号位。
ROL:循环左移,每左移一次,最高位移到最低位,并送入 CF。
ROR:循环右移,每右移一次,最低位移到最高位,并送入 CF。
RCL:带循环左移,将 CF 作为操作数的一部分循环左移。
RCR:带循环右移,将 CF 作为操作数的一部分循环右移。
4. 控制转移指令
无条件转移指令:JMP。无条件转移到转移目标地址处执行。
条件转移指令:以条件标志或者条件标志位的逻辑运算结果作为转移依据。
条件设置指令:SET。用来将条件标志组合得到的条件值设置到一个 8 位通用寄存器中。
条件传送指令:CMOV。如果符合条件就进行传送操作,否则什么都不做。
调用指令:CALL。是一种无条件转移指令,跳转方式与 JMP 指令类似,它具有两个功能:
将返回地址入栈(相当于 PUSH 操作)
跳转到指定地址处执行。
返回指令:RET。是一种无条件转移指令,是子程序执行后返回主程序继续执行。
5. 浮点处理指令
浮点装入指令 FLD 用来将存储单元中的浮点数转入到浮点寄存器栈的栈顶。
浮点存储指令 FST 和 FSTP 用来将浮点寄存器栈顶中的元素存储到存储单元中。