汇编基础
汇编程序由4种类型的组件组成:指令(instruction)、伪指令(directive)、标号(label)及注释(comment)
AT&T和Intel语法
AT&T语法会在每个寄存器前面加上%,每个常量前加上$,并且源操作数在目的地操作数前
mov $0x1, %eax
mov eax, 0x1
x86指令的机器级结构
x86指令由可选前缀(prefix)、操作码(opcode)及零个或多个操作数(operand)组成。注意除了操作码外,剩余部分都是可选的

寄存器
通用寄存器

其他寄存器:
rip:指令寄存器
rflag:标志寄存器,用于一些条件,判断等标志位
cs、ds、ss、es、fs及gs段寄存器:x86-64目前已废止内存分段
常见指令
| 指令 | 描述 |
|---|---|
| 数据传输 | |
| mov dst,src | 将src赋给dst |
| xchg dst1,dst2 | 互换dst1和dst2 |
| push src | 将src压栈,并递减rsp |
| pop dst | 出栈赋给dst,并递增rsp |
| 算术 | |
| add dst, src | dst +=src |
| sub dst, src | dst –= src |
| inc dst | dst += 1 |
| dec dst | dst –= 1 |
| neg dst | dst = –dst |
| cmp src1, src2 | 根据src1−src2设置状态标志位 |
| 逻辑/按位 | |
| and dst, src | dst &= src |
| or dst, src | dst |= src |
| xor dst, src | dst ˆ= src |
| not dst | dst = ~dst |
| test src1, src2 | 根据src1 & src2设置状态标志位 |
| 无条件分支 | |
| jmp addr | 跳转到地址 |
| call addr | 压入返回地址到栈上,然后调用函数地址 |
| ret | 从栈上弹出返回地址,然后跳转到该地址 |
| syscall | 进入内核执行系统调用 |
| 跳转分支(基于状态标志位)jcc addr仅在条件cc成立时才跳转到该地址,否则进入jncc相反条件,在条件cc不成立时跳转 | |
| je addr / jz addr | 如果设置ZF零标志位则跳转(如当上一个cmp中的操作数相同时) |
| ja addr | 上一次比较中,如果dst大于src则跳转(无符号) |
| jb addr | 上一次比较中,如果dst小于src则跳转(无符号) |
| jg addr | 上一次比较中,如果dst大于src则跳转(有符号) |
| jl addr | 上一次比较中,如果dst小于src则跳转(有符号) |
| jge addr | 上一次比较中,如果dst大于等于src则跳转(有符号) |
| jle addr | 上一次比较中,如果dst小于等于src则跳转(有符号) |
| js addr | 上一次比较中,如果结果为负则跳转,符号位置1 |
| 杂项 | |
| lea dst, src | 将内存地址加载到dst中,(dst=&src,其中src必须在内存) |
| nop | 空指令,不执行操作(用作代码填充) |
函数调用栈
在compiler explorer看,简单的函数调用栈,调用前edi保存第一个参数
call指令把下一条指令地址压栈,这里编译器优化并没有为foo开辟新栈,pop恢复栈帧,ret返回mov eax,0这条指令地址
栈的弹出后原本数据依然存在,只是复制该值并更新了rsp,所以可以通过此来获取一些数据?
左边:指令地址
中间:机器码
右边:指令
1 | void foo(int i) |
1 | 0x7fffffffde68: 0x00 0x00 0x00 0x00 0x01 0x00 0x00 0x00 |
最后总结一下:
当call指令执行时,caller会根据需要保存调用方寄存器eax、ecx和edx,然后从最后一个参数开始压栈,最后保存下一条指令的地址;4字节以内的返回值存储在eax,如果超过4字节,the caller passes an “extra” first argument to the callee,这里像是x=foo(a,b,c)和foo(&x,a,b,c)的区别,第二个x是指向返回值的地址
当被调用foo获得程序控制权,首先设置堆栈帧,执行两条指令
push ebp move ebp,esp,然后为临时变量分配空间(上面的程序没有临时变量需要存储,所以直接用main的堆栈);最后如果使用ebx,esi,edi则callee保存这些内容;不论esp如何push和pop,始终可以通过ebp+8指向第一个参数
