【发布时间】:2018-06-17 04:59:13
【问题描述】:
据我了解,当我根据 GCC 调用约定调用函数时,会发生以下情况:
调用者保存 AX、CX 和 DX 寄存器的值。参数和返回地址被压入堆栈。此外,calle 必须保留 SI、DI、BX 和 BP 寄存器的值。
但是,状态寄存器呢?谁来拯救它?
另外,压栈的返回地址的值实际上是指令寄存器的值吗?
【问题讨论】:
标签: gcc assembly calling-convention status-register
据我了解,当我根据 GCC 调用约定调用函数时,会发生以下情况:
调用者保存 AX、CX 和 DX 寄存器的值。参数和返回地址被压入堆栈。此外,calle 必须保留 SI、DI、BX 和 BP 寄存器的值。
但是,状态寄存器呢?谁来拯救它?
另外,压栈的返回地址的值实际上是指令寄存器的值吗?
【问题讨论】:
标签: gcc assembly calling-convention status-register
状态寄存器不会在函数调用之间保留。如果状态寄存器中有重要的内容,则需要将其复制到其他地方(通常使用 SETcc),但调用约定不要求调用函数来执行此操作,就像它不需要调用函数来保存和恢复 AX 等。如果它们没有什么重要的东西。
【讨论】:
inc/dec 不会影响进位标志。不将其视为临时性是非常不切实际的。
回答你的第二个问题:
另外,压栈的返回地址的值实际上是指令寄存器的值吗?
你的意思是call指令推送的值?是的,这是call 内部执行期间的当前rip(eip/ip 在 32/16 位模式下)值(因为rip 指向下一条指令)。
ret 指令将弹出堆栈顶部的任何值,并将其设置为rip,从而更改下一条指令的代码执行流程(远离ret 之后的下一条指令到地址/value 在堆栈中)。因此,在ret 完成后,堆栈中的值成为ip 寄存器的内容。 ret 类似于(不存在)pop ip,但它有自己的助记符,可以在人类阅读时更好地在源代码中脱颖而出,并且它具有完全不同的操作码,因此晶体管中的硬件实现完全特定于它(这在现代 x86 上是有意义的,其中 ret 实现使用许多额外的技巧来获得更好的性能,但我有点好奇为什么 8086 不会将其编码为 pop ip,就像只是另一个寄存器pop,在当时可能在某些细节上有些特别)。
【讨论】:
GCC 调用约定
gcc 在它所针对的任何平台上都使用标准调用约定。听起来您在描述 Linux 上使用的 i386 System V 调用约定/ABI,和/或一些 Windows 调用约定。 (其中一些以不同的方式传递 args,但对可以被破坏的寄存器做出相同的选择)。
您使用的是 16 位寄存器名称,但 gcc 几乎不支持 16 位 x86。它基本上生成 32 位代码,然后用 .code16 汇编它,因此大多数指令都有操作数大小和/或地址大小前缀。
调用者保存 AX、CX 和 DX 寄存器的值
不,调用者只有在其中有任何想要在call 中保留的数据时才会这样做。正常情况是调用者让这些值消失。 “caller-saved”与“callee-saved”是一个不好的术语,因为它暗示所有寄存器实际上都保存在某个地方。
更容易理解,IMO,是
DF 在调用和返回时必须为 0,因此字符串指令向上。 (DF 是 EFLAGS 中的另一个位)。 x87 堆栈在 call 和 ret 上必须为空,但返回 FP 值的函数除外(在这种情况下,st0 具有返回值,而 x87 堆栈的其余部分为空)。
Call-clobbered 意味着在call 之后,调用者必须假设寄存器包含垃圾,无论被调用者是否实际使用了寄存器。 如果该寄存器中有调用者稍后需要的任何内容,它必须将其移动到其他地方。但如果不是,让价值消亡完全没问题。例如要编译像rv = foo(a + b + c) 这样的东西,调用者会在寄存器中计算a+b+c。但如果它在函数调用后也不需要该值,则无需保留它。
调用保留意味着调用者可以假设寄存器值没有改变,无论被调用者只是避免接触该寄存器,还是被调用者保存/恢复它。 (或者对于 ESP,被调用者通常使用 add esp, 28 或类似名称来恢复它,以逆转它使用 push 和 sub 所做的任何更改。被调用者如何设法通过调用返回并不重要 -保留的寄存器仍然保存调用者的值,只是它确实如此。这就是为什么“被调用者保存”也不是最清晰的术语:它意味着被调用者显式地保存它们。
但是,状态寄存器呢?谁来拯救它?
没有人会保存它,除非在极少数情况下。如果需要,调用者可以保存它,但通常重做比较更容易也更便宜(popf 很慢,而 pushf 首先保存 EFLAGS 不是免费)。
或者更多情况下,条件代码中没有任何有用的数据,只有整数寄存器中的整数值。大多数指令都会写入 EFLAGS,但大多数时候您从未阅读过这些结果。整数结果通常使用add、imul等,而忽略标志结果。
有趣的事实:64-bit OS X system calls set CF on error, otherwise they clear CF。没有常见的 32 位或 64 位函数调用约定在 EFLAGS 中返回任何内容;他们只是被重创了。 (对于 Linux 系统调用,保留 EFLAGS / RFLAGS。系统调用通常不会破坏除返回值之外的任何寄存器,部分原因是这样可以避免将内核信息泄漏回用户空间。)
【讨论】: