调用函数时保存状态寄存器

Saving Status register when calling a function

据我了解,当我根据 GCC 调用约定调用函数时,会发生以下情况:

调用者保存AX、CX和DX寄存器的值。参数和返回地址被压入堆栈。此外,calle 必须保留 SI、DI、BX 和 BP 寄存器的值。

但是,状态寄存器呢?谁救了它?

还有,压栈的返回地址的值是指令寄存器的实际值吗?

状态寄存器不会跨函数调用保留。如果状态寄存器中有重要的东西需要将其复制到其他地方(通常使用 SETcc),但调用约定并不 要求 调用函数来执行此操作,就像它不需要一样'不需要调用函数来保存和恢复 AX 等。如果里面没有什么重要的东西。

回答你的第二个问题:

Also, is value of returning address which is pushed on stack actually value of Instruction register?

你的意思是call指令推送的值?是的,这是 rip(32/16 位模式下的 eip/ip)内部执行期间的 call 值(因为 rip 指向下一条指令)。

ret 指令将弹出堆栈顶部的任何值,并将其设置为 rip,更改下一条指令的代码执行流程(远离下一条指令之后的指令) ret 到堆栈中的 address/value)。因此,在 ret 完成后,堆栈中的值成为 ip 寄存器的内容。 ret 就像(不存在的)pop ip,但它有自己的助记符,使它在人类阅读时更好地在源代码中脱颖而出,而且它有完全不同的操作码,所以 HW晶体管中的实现完全特定于它(这在现代 x86 上有意义,其中 ret 实现使用许多额外的技巧来获得更好的性能,但我有点好奇为什么 8086 不会将其编码为 pop ip,就像 pop 的另一个寄存器一样,即使在当时,在某些细节上也可能有些特殊)。

GCC Calling Convention

gcc 在其目标平台上使用标准调用约定。听起来您正在描述 Linux、and/or 某些 Windows 调用约定中使用的 i386 System V 调用约定/ABI。 (其中一些以不同的方式传递参数,但对可以破坏的寄存器做出相同的选择)。

您正在使用 16 位寄存器名称,但 gcc 几乎不支持 16 位 x86。它基本上生成 32 位代码,然后用 .code16 进行汇编,因此大多数指令都有操作数大小 and/or 地址大小前缀。

Caller saves values of AX, CX and DX registers

不,调用者只有在其中有任何想要在 call 中保留的数据时才会这样做。正常情况是调用者让这些值消亡。 "caller-saved" 与 "callee-saved" 是糟糕的术语,因为它暗示所有寄存器 实际上 都保存在某个地方。

在我看来,更容易理解的是

  • call-clobbered:EAX ECX EDX 和条件代码(EFLAGS 的一部分),所有 xmm regs
  • 调用保留:EBX、ESI EDI、EBP、ESP。

DF 在调用时必须为 0,并且 return,因此字符串指令向上。 (DF 是 EFLAGS 中的另一位)。 x87 堆栈在 callret 上必须为空,但具有 return FP 值的函数除外(在这种情况下 st0 具有 return 值,并且x87 堆栈的其余部分为空)。

Call-clobbered 意味着在 call 之后,调用者必须假定寄存器包含垃圾,无论被调用者是否实际使用了寄存器。 如果该寄存器中有调用者稍后需要的任何内容,它必须将其移动到其他地方。但如果没有,让价值消亡完全没问题。例如要编译像 rv = foo(a + b + c) 这样的东西,调用者会在寄存器中计算 a+b+c。但是,如果在函数调用后它也不需要该值,则不需要保留它。

调用保留意味着调用者可以假定寄存器值没有改变,无论被调用者只是避免接触该寄存器,还是被调用者保存/恢复它。 (或者对于 ESP,被调用者通常使用 add esp, 28 或类似的方式恢复它,以反转它用 pushsub 所做的任何更改。被调用者如何处理并不重要设法 return 调用保留寄存器仍然保存调用者的值,就像它所做的那样。这就是为什么 "callee-saved" 也不是最清晰的术语:它暗示被调用者明确保存它们。

But, what about Status register? Who saves it?

没有人保存它,除非在极少数情况下。如果需要,调用者 可以 保存它,但通常重做比较更容易和更便宜(popf 很慢, pushf 保存 EFLAGS首先不是免费的)。

或者更常见的情况是,条件代码中没有任何有用的数据,只有整数寄存器中的整数值。大多数指令写入 EFLAGS,但大多数时候您从未读取过这些结果。您通常对整数结果使用 addimul 等,而忽略标志结果。

有趣的事实:64-bit OS X system calls set CF on error, otherwise they clear CF。 EFLAGS 中没有任何通用的 32 位或 64 位函数调用约定 return;他们只是被破坏了。 (对于 Linux 系统调用,EFLAGS / RFLAGS 被保留。系统调用通常不破坏任何寄存器,除了 return 值,部分原因是这避免将内核信息泄漏回用户 space.)