据我了解,当我根据GCC Calling Convention召集函数时,会发生这种情况:
来电者保存AX,CX和DX寄存器的值。堆栈上推送参数和返回地址。此外,calle必须保留SI,DI,BX和BP寄存器的值。
但是,状态寄存器怎么样?谁救了它?
另外,是指令寄存器的堆栈实际值的返回地址的值是什么?
答案 0 :(得分:2)
状态寄存器不会在函数调用中保留。如果状态寄存器中有重要的东西,则需要将其复制到别处(通常使用SETcc),但调用约定不会要求调用函数来执行此操作,就像它不需要调用函数来保存和恢复AX等。如果没有什么重要的话。
答案 1 :(得分:2)
回答你的第二个问题:
另外,是指令寄存器的堆栈实际值的返回地址的值是什么?
你的意思是call
指令所推动的价值?是的,rip
内部执行期间当前eip/ip
(32/16位模式下的call
)值(因为rip
指向下一条指令)。
ret
指令将弹出堆栈顶部的任何值,并将其设置为rip
,更改下一条指令的代码执行流程(远离{{1}之后的指令1}}到堆栈中的地址/值)。因此,在ret
完成后,堆栈中的值将成为ip
寄存器的内容。 ret
就像(不存在)ret
,但是它有自己的助记符,可以在人类阅读时更好地在源代码中脱颖而出,并且它具有完全不同的操作码,因此HW晶体管中的实现是完全特定的(这在现代x86上是有意义的,其中pop ip
实现使用了许多额外的技巧以获得更好的性能,但我有点好奇为什么8086不会将其编码为{ {1}},就像ret
的另一个注册表一样,甚至可能在某些细节方面有点特别。)
答案 2 :(得分:0)
GCC呼叫大会
gcc在其定位的任何平台上使用标准调用约定。听起来你正在描述在Linux上使用的i386 System V调用约定/ ABI和/或一些Windows调用约定。 (其中一些以不同的方式传递args,但是可以选择可以被破坏的寄存器)。
您使用的是16位寄存器名称,但gcc几乎不支持16位x86。它基本上生成32位代码,然后用.code16
组装它,因此大多数指令都有操作数大小和/或地址大小的前缀。
来电者保存AX,CX和DX寄存器的值
不,调用者只有在其中有任何想要保留在call
内的数据时才会这样做。正常情况是调用者让这些值死亡。 "呼叫者保存的" vs." 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
所做的任何更改。它没有&#39 ;无论被调用者如何设置返回,保持呼叫保留的寄存器仍然保持调用者的值,就像它一样。这就是为什么"被调用者保存"也不是最明确的术语:它暗示被调用者明确地保存它们。
但是,状态寄存器怎么样?谁救了它?
没有人保存,除非极少数情况。如果需要,调用者可以保存它,但通常只需重做比较(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。除了返回值之外,系统调用通常不会破坏任何寄存器,部分原因是这样可以避免将内核信息泄漏回用户空间。)