Skip to content

PUSHAD POPAD PUSHFD POPFD

在汇编语言中,这四条指令是“批量操作”的代表。它们的后缀非常有规律:A 代表 All(所有通用寄存器),F 代表 Flags(标志寄存器),D 代表 Double word(32位数据)。

需要特别注意的是:PUSHAD 和 POPAD 在 64 位模式下是不存在的,它们是 32 位时代的产物。


一、 PUSHAD 与 POPAD:通用寄存器全家桶

这两条指令用于一次性保护或恢复所有 8 个通用寄存器。在编写函数、处理中断或进行反调试/壳处理时非常方便。

1. PUSHAD (Push All Double-word)

它按照特定的顺序将 8 个 32 位寄存器压入栈中。 * 压栈顺序:EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI。 * 有趣细节:当压入 ESP 时,压入的是执行 PUSHAD 之前的栈指针值。 * 作用:当你需要执行一段代码,而这段代码会弄乱几乎所有寄存器时,先执行 PUSHAD。

2. POPAD (Pop All Double-word)

它是 PUSHAD 的逆操作。 * 出栈顺序:与压栈相反(EDI 先出,EAX 最后)。 * 注意:它会恢复除 ESP 以外的所有寄存器。虽然它会从栈里弹出 ESP 的备份值,但会直接丢弃它,而不是真的修改当前的 ESP(因为如果改了 ESP,栈就乱了)。

3. 为什么 64 位没有 PUSHAQ?

在 64 位模式(x64)中,Intel 删除了这两条指令。 * 原因:64 位多了 R8-R15 等 8 个寄存器(共 16 个)。一次性压入 16 个 64 位寄存器开销太大。 * 现代做法:编译器或程序员只手动 PUSH 那些真正需要保护的寄存器。


二、 PUSHFD 与 POPFD:标志寄存器的克星

这两条指令专门用来读写 EFLAGS(标志寄存器)。还记得之前讲的 ZF, CF, OF 吗?它们都在这里。

1. PUSHFD (Push EFLAGS Double-word)

  • 功能:把当前的 32 位标志寄存器(EFLAGS)压入栈中。
  • 用途:
    • 保存状态:在进行复杂的比较运算前备份标志位。
    • 读取标志位:因为没有 MOV EAX, EFLAGS 这种指令,如果你想看标志位的值,必须先 PUSHFD 然后 POP EAX。

2. POPFD (Pop EFLAGS Double-word)

  • 功能:从栈顶弹出一个 32 位值,并将其覆盖到标志寄存器中。
  • 用途:
    • 恢复状态:恢复之前备份的标志位。
    • 修改标志位:这是修改某些特殊标志位(如 TF 陷阱标志、IF 中断控制位)的唯一或常用手段。先 PUSHFD,修改栈里的值,再 POPFD 回去。

三、 总结对比表

指令 操作对象 宽度 适用模式 作用
PUSHAD EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI 32位 × 8 仅 32位 批量保存通用寄存器
POPAD 同上 32位 × 8 仅 32位 批量恢复通用寄存器
PUSHFD EFLAGS 32位 32/64位* 保存标志位状态
POPFD EFLAGS 32位 32/64位* 恢复/修改标志位状态

*在 64 位模式下,对应的是 PUSHFQ 和 POPFQ,操作的是 64 位的 RFLAGS。


四、 进阶:如何用这些指令做“黑科技”?

在逆向工程或漏洞利用中,程序员经常利用 PUSHFD 来检测 CPU 状态。

例子:检查“陷阱标志位(TF)”来反调试

pushfd              ; 将标志寄存器入栈
or dword [esp], 0x100 ; 将栈内对应 TF 位(第 8 位)设为 1
popfd               ; 恢复标志寄存器。此时 TF=1,CPU 进入单步调试模式
; 如果此时有调试器在盯着,程序会触发异常,程序员以此判断自己是否被调试。

关键点回顾:

  1. A 代表 All,F 代表 Flags。
  2. PUSHAD/POPAD 极大地方便了寄存器保护,但为了性能和简洁,在 64 位中被废弃了。
  3. PUSHFD/POPFD 是操作标志寄存器的“传送门”。

既然你已经了解了这些批量操作,你是否想看一个 32 位汇编函数中使用 PUSHAD 保护现场的完整例子?或者我们继续深入 64 位下如何优雅地处理函数调用?