1.程序转换概述
机器指令和汇编指令
机器指令与汇编指令意义对应,都是机器级指令
汇编指令
如:M[R[bx]+R[di]-6]←R[cl]
R:寄存器内容
M:存储单元内容
机器指令
高级语言转换为机器代码的过程
根据计算机系统基础(一),我们知道预处理,编译,汇编,链接四个步骤。
接下来讲讲反汇编,即将机器代码反汇编为汇编代码
2.IA-32指令系统
IA-32的定点寄存器组织
8个通用寄存器,2个专用寄存器和6个段寄存器(本图未表现)
其中EAX,EBX,ECX,EDX主要用来存放操作数
IA-32的寻址方式
操作数的寻址方式
各变量采用基址+位移的方式
如a[i]=104+i×4
IA-32常用指令类型
栈
从高地址向低地址增长
例如我现在将AL来push进去,则AL就应该在栈顶,也就是低地址
传送指令
乘法指令
mulb是无符号乘,imulb是带符号乘
注意:带符号乘,如果积只取低n位,则和无符号相同;若取2n位,则采用“布斯”乘法
让我们来看一个例子
指令:imull $-16, (%eax,%ebx,4), %eax
功能:R[eax]←(-16)×M[R[eax]+R[ebx]×4]
按位运算
salw $2, %ax 1111 1111 1000 0000<<2
sarw $1, %ax 1111 1101 1000 0000>>1=1111 1110 1100 0000
sal和saw都是算术移位
通用寄存器的编号
3.C语言程序的机器级表示
过程调用的机器级表示
现场:通用寄存器的内容;为何保存现场:因为所有过程共享一套通用寄存器
- P将入口参数(实参)放到Q能访问到的地方;
- P保存返回地址,然后将控制转移到Q;
- Q保存P的现场,并为自己的非静态局部变量分配空间;
- 执行Q的过程体(函数体)
- Q恢复P的现场,释放局部变量空间;
- Q取出返回地址,将控制转移到P
调用者保存寄存器: EAX、EDX、ECX
当过程P调用过程Q时,Q可以直接使用这三个寄存器。如果P在从Q返回后还要用这三个寄存器的话,P应在转到Q之前先保存,并在从Q返回后先恢复它们的值再使用
被调用者保存寄存器:EBX、ESI、EDI
Q必须先将它们的值保存到栈中再使用它们,并在返回P之前恢复它们的值。
接下来我们根据实际例子,来一步一步分析过程调用的情况
这是一段汇编代码
首先把ebp(栈底指针)压入栈中,再将esp(栈顶指针)指向ebp所在位置,然后esp=esp-24达到栈顶位置,再将ebp-12的位置放入125,同样ebp-8的位置放入80,将ebp-8的内容赋给eax也就是80,eax又把值放在esp+4的位置中,然后ebp-12的内容赋给eax也就是125,eax又把值放在esp的位置中。
这个时候调用add函数(此时返回参数存放在eax中),将eax的值给ebp-4(add返回值送给sum),再将ebp-4赋给eax(把sum作为caller的返回值)
call指令:保存返回地址并转被调用函数(返回地址是call指令的下一条指令)
退栈:leave指令 或 pop指令
取返回地址返回:ret指令
- 参数的地址总比局部变量的地址大
- 总是最右边参数的地址最大,因为参数入栈顺序为:右→左
递归函数
每次递归调用都会增加一个栈帧,每一个栈帧至少16个字节
选择语句的机器级表示
选择语句的机器表示,让我们看一个例子就理解了
4.复杂数据类型的分配和访问
数组的分配和访问
一个指针数组可以实现一个二维数组,每个元素都是一个指向int型数据的指针
接下来我们还是看一个例子
指令:s[i]+=*pn[i]++; i 在ECX,s[i]在AX
反汇编代码
movl pn(,%ecx,4), %edx
addw (%edx), %ax
addl $2, pn(, %ecx, 4)
先将pn+ecx*4放入edx,这里是pn[i]放在edx(这里是4的原因是因为存的是地址,地址大小是4字节)
再将edx的值放入ax也就是s[i]
最后,pn[i]+”1”→pn[i],2是因为是short类型
结构体数据的分配和访问
当结构体作为入口参数,应该按地址传递(按值传递,加时间开销又增加空间开销,且更新后的数据无法在调用过程使用)
联合体数据的分配和访问
联合体各成员共享存储空间,按最大长度成员所需空间大小为目标
IA-32中编译时,long和int长度一样,union一共只能用四字节
通常用于特殊场合,如,当事先知道某种数据结构中的不同字段的使用时间是互斥的
数据的对齐
CPU访问主存时只能一次读取或写入若干特定位,按边界对齐可使读写数据位于8i~8i+7(i=0,1,2,…) 单元内
short型为2字节边界对齐,其他的如int、double、long double和指针等类型都是4字节边界对齐(即为4的倍数)
边界不对齐虽节省了空间,但增加了访存次数
5.越界访问和缓冲区溢出
越界访问
- 当i=0或1,OK
- 当i=2, d3~d0=0x40000000 低位部分(尾数)被改变
- 当i=3, d7~d3=0x40000000 高位部分被改变
- 当i=4, EBP被改变,虽然能返回3.14,但是返回到调用过程后,在调用过程中使用ebp作为基址寄存器访问数据时,访问的是0x4000 0000附近的单元,本例中0x4000 0000附近的单元属于没有内容的空洞页面,对空洞页面的访问会导致发生存储保护错
C语言中的数组元素可使用指针来访问,因而对数组的引用没有边界约束,也即程序中对数组的访问可能会有意或无意地超越数组存储区范围而无法发现
C标准规定, 数组越界访问属于未定义行为, 访问结果是不可预知的
缓冲区溢出
数组存储区可看成是一个缓冲区,超越数组存储区范围的写入操作称为缓冲区溢出
造成缓冲区溢出的原因是没有对栈中作为缓冲区的数组的访问进行越界检查