- 目的:了解如何将C程序编译成机器代码
程序编码
gcc -Og -S mstore.c
生成C语言编译器产生的汇编代码,-O
为编译优化选项- objdump将机器代码反汇编为汇编代码
数据格式
- 字节-8位-b 字-16位-w 双字-32位-l 四字-64位-q
访问信息
- 操作数:指示出执行一个操作中要使用的源数据值,以及放置结果的目的位置
- 操作数被分为三种类型:
- 立即数:用来表示常数值
- 寄存器:表示某个寄存器的内容
- 内存:根据计算出来的地址访问某个内存位置
- 寻址形式 I m m ( r b , r i , s ) → I m m + R [ r b ] + R [ r i ] ⋅ s Imm(r_b,r_i,s)\rightarrow Imm+R[r_b]+R[r_i]·s Imm(rb,ri,s)→Imm+R[rb]+R[ri]⋅s
- MOV类把数据从源位置复制到目的位置,传送的两个操作数不能都指向内存位置
- MOVZ类中的指令把目的中剩余的字节填充为0;MOVS类中的指令通过符号扩展填充,把源操作的最高位进行复制
- 寄存器部分的大小必须 与指令最后一个字符指定的大小匹配
- movl以寄存器作为目的时,会将高4位设置为0
- movabsq只能以64位立即数作为源操作数,以寄存器作为目的
- cltq只作用于%eax和%rax
- c语言中的指针就是地址,局部变量通常是保存在寄存器中
- x86-64中,栈向低地址方向增长,压栈是减小栈指针的值
算术和逻辑操作
- leaq将有效地址写入到寄存器
- 移位指令中,移位量可以是一个立即数,或者放在单字节寄存器%cl中
控制
- 条件码:
- CF:进位标志,使最高位产生了进位
- ZF:零标志,操作得出的结果为零
- SF:符号标志,操作得出的结果为负数
- OF:溢出标志,导致补码溢出
- CMP指令根据两个操作数之差来设置条件码,TEST根据两个操作数的与运算来设置条件码,这两个指令只设置条件码而不改变任何其他寄存器
- jmp可以直接跳转或者间接跳转或者条件跳转
- 跳转指令会将目标指定地址与紧跟在跳转指令后面那条指令的地址之间的差作为编码
- 条件传送指令(cmov)比使用控制的条件转移更加高效,是由于处理器采用分支预测逻辑来猜测每条跳转指令是否会执行,每一个错误预测会浪费15~30个时钟周期
- 执行条件传送时,只是检查条件码来决定是否更新寄存器,无需进行预测
- 循环可以用条件测试和跳转组合的方式实现
- switch语句使用跳转表实现,执行开关语句的时间与开关情况的数量无关
过程
- 过程是一种很重要的抽象,用一组指定的参数和一个可选的返回值实现某种功能
- 当过程需要的存储空间超出寄存器能够存放的大小时,就会在栈上分配空间,这个部分称为过程的栈帧,当前正在执行的过程的帧总是在栈顶
- 程序计数器(PC)是用于存放下一条指令所在单元的地址的地方
- 调用者保存寄存器与被调用者保存寄存器的区别:函数A调用函数B,寄存器在调用前后保持一致,若是在调用前保存,由函数A来保存并恢复,称为调用者保存寄存器;若是由函数B保存并恢复,称为被调用者保存寄存器。区分方式:返回前恢复还是返回后恢复
- 传递的参数需要8字节对齐,而局部变量是不需要对齐的
数组分配和访问
- 对于数据类型T和整型常数N,T A[N],起始位置为 x A x_A xA,在内存中分配一个L·N字节的连续区域,L是类型T的大小,A是指向数组开头的指针,数组元素i会被存放在地址为 x A + L ⋅ i x_A+L\cdot i xA+L⋅i的地方
异质的数据结构
- 结构类似数组的实现,所有组成部分都存放在内存中一段连续的区域内,指向结构的指针就是结构第一个字节的地址,编译器维护关于每个结构类型的信息,指示每个字段的字节偏移
- union的所有字段共享存储区域,一个union的总的大小等于它最大字段的大小
- 联合体的应用情况,两个字段的使用是互斥的
- 数据对齐原则是任何K字节的基本对象的地址必须是K的倍数
在机器级程序中将控制与数据结合起来
- 函数指针的值是该函数机器代码表示中第一条指令的地址
- 缓冲区溢出的常见情况:在栈中分配某个字符数组来保存一个字符串,但是字符串的长度超出了为数组分配的空间
- 对抗缓冲区溢出攻击:1.栈随机化 2.栈破坏检测 3.限制可执行代码区域
浮点代码
- AVX浮点体系结构允许数据存储在16个YMM寄存器中,每个YMM寄存器都是256位(32字节),这些寄存器只保存浮点数,低16字节可以作为XMM寄存器来访问