x86 汇编/控制流
几乎所有编程语言都有改变语句执行顺序的能力,汇编也不例外。指令指针 (EIP) 寄存器包含要执行的下一条指令的地址。为了改变控制流,程序员必须能够修改 EIP 的值。这就是控制流函数发挥作用的地方。
mov eip, label ; wrong
jmp label ; right
| test 引用,累加器 | GAS 语法 |
| test 累加器,引用 | Intel 语法 |
对 累加器 和 引用 进行按位逻辑 and 操作,我们将结果称为 commonBits,并根据 commonBits 设置 ZF(零)、SF(符号)和 PF(奇偶校验)标志。然后丢弃 CommonBits。
操作数
引用
- 寄存器
- 立即数
累加器
- 寄存器(如果
引用是立即数,则可以对AL/AX/EAX进行特殊编码) - 内存
修改后的标志
SF≔ MostSignificantBit(commonBits)ZF≔ (commonBits= 0),因此设置ZF意味着累加器和引用没有共同的设置位PF≔ BitWiseXorNor(commonBits[Max-1:0]),因此当且仅当commonBits[Max-1:0] 具有偶数个 1 位时,PF设置为 1CF≔ 0OF≔ 0AF未定义
应用
- 将同一个寄存器传递两次:
test rax, raxSF成为rax的符号,这是一个简单的非负性测试- 如果
rax为零,则设置ZF - 如果
rax具有偶数个设置位,则设置PF
| cmp 被减数,减数 | GAS 语法 |
| cmp 减数,被减数 | Intel 语法 |
在 被减数 和 减数 之间执行比较操作。比较操作通过从 被减数 中减去 减数(称为 差)来执行。然后丢弃 差。如果 减数 是一个立即数,它将被符号扩展到 被减数 的长度。EFLAGS 寄存器以与 sub 指令相同的方式设置。
请注意,GAS/AT&T 语法可能相当令人困惑,例如 cmp $0, %rax 后跟 jl branch 将分支到 %rax < 0(而不是像操作数顺序可能预期的那样相反)。
操作数
被减数
AL/AX/EAX(仅当减数是立即数时)- 寄存器
- 内存
减数
- 寄存器
- 立即数
- 内存
修改后的标志
SF≔ MostSignificantBit(difference),因此未设置SF意味着difference为非负(被减数≥减数[注意:带符号比较])ZF≔ (difference= 0)PF≔ BitWiseXorNor(difference[Max-1:0])CF、OF和AF
跳转指令允许程序员(间接)设置 EIP 寄存器的值。作为参数传递的位置通常是一个标签。跳转后执行的第一条指令是紧随标签之后的指令。除了 jmp 之外,所有跳转指令都是条件跳转,这意味着只有在条件为真时才会改变程序流。这些指令通常在比较指令(见上文)之后使用,但由于许多其他指令也设置标志,因此此顺序不是必需的。
有关标志及其含义的更多信息,请参见章节“X86 架构”,§ “EFLAGS 寄存器”。
jmp loc
用指定的地址加载 EIP(即,执行的下一条指令是 jmp 指定的指令)。
je loc
ZF = 1
如果先前 cmp 指令的操作数相等,则用指定的地址加载 EIP。je 与 jz 相同。例如
mov ecx, $5
mov edx, $5
cmp ecx, edx
je equal
; if it did not jump to the label equal,
; then this means ecx and edx are not equal.
equal:
; if it jumped here, then this means ecx and edx are equal
jne loc
ZF = 0
如果先前 cmp 指令的操作数不相等,则用指定的地址加载 EIP。jne 与 jnz 相同
jg loc
SF = OF 且 ZF = 0
如果先前 cmp 指令的 被减数 大于第二个(执行带符号比较),则用指定的地址加载 EIP。
jge loc
SF = OF 或 ZF = 1
如果先前 cmp 指令的 被减数 大于或等于 减数(执行带符号比较),则用指定的地址加载 EIP。
ja loc
CF = 0 且 ZF = 0
如果先前 cmp 指令的 被减数 大于 减数,则用指定的地址加载 EIP。ja 与 jg 相同,只是它执行无符号比较。
这意味着,以下代码段始终跳转(除非 rbx 也为 -1),因为负一在二进制补码中表示为所有位都设置。
mov rax, -1 // rax := -1
cmp rax, rbx
ja loc
将所有位都设置为 1(不将任何位视为符号)的值为 2ⁿ-1(其中 n 是寄存器的长度)。这是寄存器可以保存的最大无符号值。
jae loc
CF = 0 或 ZF = 1
如果之前 `cmp` 指令的 `被减数` 大于或等于 `减数`,则将 `EIP` 加载到指定地址。`jae` 与 `jge` 相同,区别在于它执行无符号比较。
jl loc
`jl` 所需的条件是 `SF` ≠ `OF`。如果满足该条件,则将 `EIP` 加载到指定地址。因此,`SF` 或 `OF` 可以设置,但不能同时设置以满足该条件。如果以 `sub`(基本上等同于 `cmp`)指令为例,我们有
- `被减数` - `减数`
关于 `sub` 和 `cmp`,有几种情况满足该条件
- `被减数` < `减数` 且操作没有溢出
- `被减数` > `减数` 且操作溢出
在第一种情况下,`SF` 将被设置,但 `OF` 不会被设置;在第二种情况下,`OF` 将被设置,但 `SF` 不会被设置,因为溢出会将最高有效位重置为零,从而阻止 `SF` 被设置。`SF` ≠ `OF` 条件避免了以下情况
- `被减数` > `减数` 且操作没有溢出
- `被减数` < `减数` 且操作溢出
- `被减数` = `减数`
在第一种情况下,`SF` 和 `OF` 都不设置;在第二种情况下,`OF` 将被设置,`SF` 也将被设置,因为溢出会将最高有效位重置为 1;在最后一种情况下,`SF` 和 `OF` 都不设置。
jle loc
`SF` ≠ `OF` 或 `ZF = 1`。
如果之前 `cmp` 指令的 `被减数` 小于或等于 `减数`,则将 `EIP` 加载到指定地址。有关条件的更详细说明,请参阅`jl` 部分。
jb loc
CF = 1
如果之前 CMP 指令的第一个操作数小于第二个操作数,则将 EIP 加载到指定地址。`jb` 与 `jl` 相同,区别在于它执行无符号比较。
mov rax, 0 ; rax ≔ 0
cmp rax, rbx ; rax ≟ rbx
jb loc ; always jumps, unless rbx is also 0
jbe loc
CF = 1 或 ZF = 1
如果之前 `cmp` 指令的 `被减数` 小于或等于 `减数`,则将 `EIP` 加载到指定地址。`jbe` 与 `jle` 相同,区别在于它执行无符号比较。
jz loc
ZF = 1
如果之前算术表达式的零位被设置,则将 `EIP` 加载到指定地址。`jz` 与 `je` 相同。
jnz loc
ZF = 0
如果之前算术表达式的零位未设置,则将 `EIP` 加载到指定地址。`jnz` 与 `jne` 相同。
js loc
SF = 1
如果之前算术表达式的符号位被设置,则将 `EIP` 加载到指定地址。
jns loc
SF = 0
如果之前算术表达式的符号位未设置,则将 `EIP` 加载到指定地址。
jc loc
CF = 1
如果之前算术表达式的进位标志被设置,则将 `EIP` 加载到指定地址。
jnc loc
CF = 0
如果之前算术表达式的进位标志未设置,则将 `EIP` 加载到指定地址。
jo loc
OF = 1
如果之前算术表达式的溢出标志被设置,则将 `EIP` 加载到指定地址。
jno loc
OF = 0
如果之前算术表达式的溢出标志未设置,则将 `EIP` 加载到指定地址。
jcxz loc
CX = 0
jecxz loc
ECX = 0
jrcxz loc
RCX = 0
如果计数器寄存器为零,则将 `EIP` 加载到指定地址。
- 该指令的存在使得计数器寄存器特别适合保存(高级语言)指针:在大多数编程语言中,“空指针”(无效指针)由数字值
0实现。通常,您不想取消引用这样的空指针,因为结果将是错误的,甚至会导致 GPF。通过使用jecx跳转到一些处理此错误的代码,您可以在尝试取消引用指针值之前避免陷入这种状况。您不需要额外的test ecx, ecx。 - 如果您使用
loop指令实现循环,但请求的迭代次数可能为零,您可能希望在循环主体之前插入jecx。否则,loop将递减零,从而最终执行 232 次迭代。
call proc
将 `call` 调用后面的指令地址(即通常是源代码中的下一行)压入堆栈顶部,然后跳转到指定位置。这主要用于子例程。
ret [val]
将堆栈上的下一个值弹出到 `EIP` 中,然后从堆栈中弹出指定数量的字节。如果未提供 `val`,则该指令在返回后不会从堆栈中弹出任何值。
loop arg
loop 指令递减 ECX 并跳转到 arg 指定的地址,除非递减 ECX 使其值变为零。例如
mov ecx, 5 ; ecx ≔ 5
head:
; the code here would be executed 5 times
loop head
loop 不设置任何标志。
loopcc arg
这些循环指令递减 ECX 并跳转到 arg 指定的地址,如果它们的条件满足(即,特定的标志被设置),除非递减 ECX 使其值变为零。
loope如果相等则循环loopne如果不相等则循环loopnz如果不为零则循环loopz如果为零则循环
这样,只有测试非零 ECX 可以与测试 ZF 相结合。其他标志不能被测试,比如没有 loopnc “当 ECX ≠ 0 且 CF 未设置时循环”。
enter arg
enter 在堆栈上分配指定的空间创建堆栈帧。
leave
leave 销毁当前堆栈帧,并恢复之前的帧。使用英特尔语法,这等效于
mov esp, ebp ; esp ≔ ebp
pop ebp
这将使 EBP 和 ESP 设置为函数序言开始之前各自的值,从而逆转序言期间对堆栈进行的任何修改。
hlt
停止处理器。执行将在处理下一个硬件中断后恢复,除非 IF 被清除。
nop
无操作。此指令不执行任何操作,但在处理器中浪费(一个)指令周期。
此指令通常表示为 xchg 操作,操作数为 EAX 和 EAX(一个没有副作用的操作),因为没有指定的操作码用于不执行任何操作。这只是一个顺便提及,这样你不会与反汇编代码混淆。
lock
在下一条指令上断言 #LOCK 前缀。
wait
等待 FPU 完成其最后一个计算。