程序的机械级表示学习记录
X86的三代寻址方式
- DOS时代的平坦模式,不区分用户空间和内核空间,很不安全。
- 8086的分段模式。
- IA32的带保护模式的平坦模式。
对于机械级编程的两种重要抽象
ISA:机械级程序的格式和行为,定义为指令集体系结构,它定义了处理器状态、指令的格式,以及每条指令对状态的影响。
虚拟地址:机器级程序使用的存储器地址,提供的存储器模型看上去是一个非常大的数组。存储器系统的实际实现是将多个硬件存储器和操作系统软件组合起来的。
在GCC中获得汇编代码与反汇编
获得汇编代码:gcc –S xxx.c –o xxx.s。
反汇编(将二进制目标文件还原为汇编代码)objdump –d xxx.o。
注:64位机器上想要得到32位代码:gcc –m32 –S xxx.c。
查看二进制文件
二进制文件可以用od 命令查看,也可以用gdb的x命令查看。 有些输出内容过多,我们可以使用 more或less命令结合管道查看,也可以使用输出重定向来查看。
例:od code.o |. more
od code.o > code.txt
汇编文件中的"."开始的语句
在汇编".s"文件中,以"."开头的行都是指导汇编器和链接器的命令。我们可以忽略这些行。
Linux和Windows的汇编格式的区别
ATT格式和Intel格式
- Intel代码省略了指示大小的后缀。我们看到指令mov,而不是movl。
- Intel代码省略了寄存器名字前面的‘%‘符号。用esp,而不是%esp。
- Intel代码用不同的方式来描述存储器中位置。例如,是‘DWORD PTR [ebp+8]‘而不是‘(ebp)‘。
不同数据汇编代码的后缀
汇编代码指令都有一个字符后缀,表明操作数大小。
- 后缀b代表大小为字节。例如:movb(传送字节)。
- 后缀w代表大小为字。例如:movw(传送字)。
- 后缀l代表大小为双字。例如:movl(传送双字)。
部分寄存器
- esi和edi:可以用来操纵数组。
- esp和ebp:可以用来操纵栈帧。
- 对通用寄存器中eax,ebx,ecx,edx中和ax,al,ah等的关系
|===============EAX===============|--32个0,4个字节,2个字,1个双字
|======AX=======|--16个0,2个字节,1个字
|==AH===|-----------8个0,1个字节
|===AL==|---8个0,1个字节
虽说EAX是32位的寄存器,但其实只是在原有的8086CPU的寄存器AX上增加了一倍的数据位数而已。故而EAX与AX根本不可能独立,二者是整体与部分的关系。
对EAX直接赋值,若更改了低16位自然会改变了AX值,而AX又可以影响EAX整体。而AH,AL寄存器和AX之间的关系也是如此。
三种操作数类型
- 立即数:也就是常数值。
- 寄存器:它表示某个寄存器的内容。
- 存储器:它根据计算出来的地址(通常称为有效地址)访问某个存储器位置。
汇编的寻址方式
类型 |
格式 |
操作数值 |
名称 |
立即数 |
$Imm |
Imm |
立即数寻址 |
寄存器 |
R[] |
寄存器寻址 |
|
存储器 |
Imm |
M[Imm] |
绝对寻址 |
存储器 |
() |
M[R[]] |
间接寻址 |
存储器 |
Imm() |
M[Imm+R[]] |
(基址+偏移量)寻址 |
存储器 |
(,) |
M[R[]+R]] |
变址寻址 |
存储器 |
Imm(,) |
M[lmm+R[]+R[]] |
变址寻址 |
存储器 |
(,,s) |
M[R[]*s] |
比例变址寻址 |
存储器 |
Imm(,,s) |
M[Imm+R]*s] |
比例变址寻址 |
存储器 |
(,,s) |
M[R[]+R[]*s] |
比例变址寻址 |
存储器 |
Imm(,,s) |
M[Imm+R[]+R[]*s] |
比例变址寻址 |
数据传送指令
- MOV:传送字节。
- MOVS:传送符号扩展的字节。
- MOVZ:传送零扩展的字节。
PUSH和POP
push指令的功能是把数据压入到栈上,而pop指令是弹出数据。这些指令都只是只有一个操作数——压入的数据源和弹出的数据目的。
指针就是地址,局部变量保存在寄存器中。
算术和逻辑操作指令
指令 |
效果 |
描述 |
leal S,D |
D←&S |
加载有效地址 |
INC D DEC D NEG D NOT D |
D←D+1 D←D-1 D← -D D← ~D |
加1 减1 取负 取补 |
ADD S , D SUB S , D IMUL S , D XOR S , D OR S , D AND S , D |
D← D+S D← D-S D← D*S D← D^S D← D|S D← D&S |
加 减 乘 异或 或 与 |
SAL k,D SHL k,D SAR k,D SHR k,D |
D← D<<k D← D<<k D← D>> D← D>> |
左移 左移(等同于SAL) 算术右移(补符号位) 逻辑右移(补零) |
条件码
- CF:进位标志。最近的操作使最高位产生了进位。可以用来检查无符号操作数的溢出。
- ZF:零标志。最近的操作得出的结果为0。
- SF:符号标志。最近的操作得到的结果为负数。
- OF:溢出标志。最近的操作导致一个补码溢出——正溢出或负溢出。
CMP指令根据它们的两个操作数之差来设置条件码。
TEST指令根据它们的两个操作数的与(&)来设置条件码。
CMP和SUB的区别:CMP只改变条件码,SUB不仅改变条件码还改变目的操作数。
SET指令:根据条件码的某个组合,将一个字节设为0或者1。(P125 图3-11)
跳转指令
指令 |
描述 |
jmp Label |
直接跳转 |
jmp *Operand |
间接跳转 |
je(jz) |
相等/零 |
jne(jnz) |
不相等/非零 |
js |
负数 |
jns |
非负数 |
jg(jnle) |
大于(有符号>) |
jge(jnl) |
大于或等于(有符号>=) |
jl(jnge) |
小于(有符号<) |
jle(jng) |
小于或者等于(有符号<=) |
ja(jnbe) |
超过(无符号>) |
jae(jnb) |
超过或相等(无符号>=) |
jb(jnae) |
低于(无符号<) |
jbe(jna) |
低于或相等(无符号<=) |
转移控制指令
指令 |
描述 |
call Label |
过程调用 |
call *Operand |
过程调用 |
leave |
为返回准备栈 |
ret |
从过程调用中返回 |
注:leave指令在16位汇编下相当于:
mov sp,bp
pop bp
在32位汇编下相当于:
mov esp,ebp
pop ebp
过程与栈
一个过程调用包括将数据(以过程参数和返回值的形式)和控制从代码的一部分传递到另一部分。另外,它还必须在进入时为过程的局部变量分配空间,并在退出时释放这些空间。数据传递、局部变量的分配和释放都是通过操纵程序栈来实现的。
机器用栈来传递过程参数、存储返回信息、保存寄存器用于以后恢复,以及本地存储。为单个过程分配的那部分栈称为栈帧。
过程调用和返回指令
指令 |
描述 |
call Label |
过程调用 |
call *Operand |
过程调用 |
leave |
为返回准备栈 |
ret |
从过程调用中返回 |
注:call/ret; 函数返回值存在%eax中。
实验练习
实验代码:
编译后的汇编代码:
a:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $3, %eax
popl %ebp
ret
r:
pushl %ebp
movl %esp, %ebp
pushl 8(%ebp)
call a
addl $4, %esp
leave
ret
main:
pushl %ebp
movl %esp, %ebp
pushl $8
call r
addl $4, %esp
addl $1, %eax
leave
ret