为什么在调用 printf 时会覆盖 EDX 的值?

Why is the value of EDX overwritten when making call to printf?

我写了一个简单的汇编程序:

section .data
str_out db "%d ",10,0
section .text
extern printf
extern exit
global main
main:

MOV EDX, ESP
MOV EAX, EDX
PUSH EAX
PUSH str_out
CALL printf
SUB ESP, 8 ; cleanup stack
MOV EAX, EDX
PUSH EAX
PUSH str_out
CALL printf
SUB ESP, 8 ; cleanup stack
CALL exit

我是 NASM 汇编程序和 GCC link 目标文件到 linux 上的可执行文件。

本质上,这个程序是先把堆栈指针的值放入寄存器EDX,然后打印两次这个寄存器的内容。但是,在第二次 printf 调用之后,打印到标准输出的值与第一次不匹配。

这种行为似乎很奇怪。当我用 EBX 替换此程序中对 EDX 的每次使用时,输出的整数与预期的相同。我只能推断 EDX 在 printf 函数调用期间的某个时刻被覆盖了。

为什么会这样?以及如何确保我以后使用的寄存器不会与 C 库函数冲突?

根据 x86 ABIEBXESIEDIEBP 是被调用者保存寄存器,EAXECXEDX 是来电保存寄存器。

这意味着函数可以自由使用和销毁以前的值EAXECXEDX。 因此,如果您不希望它们的值发生变化,请在调用函数之前保存 EAXECXEDX 的值。这就是“来电保存”的意思。

或者更好的是,将其他寄存器用于您在函数调用后仍然需要的值。 push/pop of EBX at the start/end of a function is much better than push/pop of EDX inside a loop that make a function call.如果可能,对调用后不需要的临时寄存器使用调用破坏寄存器。已经在内存中的值,因此在重新读取之前不需要写入,溢出也更便宜。


由于 EBXESIEDIEBP 是被调用者保存寄存器,函数必须将其中任何一个的值恢复为原始值修改,在 returning.

之前

ESP 也是被调用者保存的,但是除非你在某处复制 return 地址,否则你不能把它搞砸。

目标平台(例如 32 位 x86 Linux)的 ABI 定义了函数可以使用哪些寄存器而不保存。 (即,如果你想在通话中保留它们,你必须自己做)。

指向 Windows 和非 Window、32 位和 64 位的 ABI 文档链接,位于 https://whosebug.com/tags/x86/info

拥有一些不跨调用保留的寄存器(可用作临时寄存器)意味着函数可以更小。简单的函数通常可以避免做任何 push/pop save/restores。这减少了指令数量,导致更快的代码。

每个都有一些很重要:必须在调用中将所有状态溢出到内存中会使非叶函数的代码膨胀,尤其是减慢速度。在被调用函数没有触及所有寄存器的情况下。

另请参阅 What are callee and caller saved registers? 以了解有关保留调用与调用破坏寄存器的更多信息。