为什么在调用 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 ABI、EBX
、ESI
、EDI
和 EBP
是被调用者保存寄存器,EAX
, ECX
和 EDX
是来电保存寄存器。
这意味着函数可以自由使用和销毁以前的值EAX
、ECX
和EDX
。
因此,如果您不希望它们的值发生变化,请在调用函数之前保存 EAX
、ECX
、EDX
的值。这就是“来电保存”的意思。
或者更好的是,将其他寄存器用于您在函数调用后仍然需要的值。 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.如果可能,对调用后不需要的临时寄存器使用调用破坏寄存器。已经在内存中的值,因此在重新读取之前不需要写入,溢出也更便宜。
由于 EBX
、ESI
、EDI
和 EBP
是被调用者保存寄存器,函数必须将其中任何一个的值恢复为原始值修改,在 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? 以了解有关保留调用与调用破坏寄存器的更多信息。
我写了一个简单的汇编程序:
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 ABI、EBX
、ESI
、EDI
和 EBP
是被调用者保存寄存器,EAX
, ECX
和 EDX
是来电保存寄存器。
这意味着函数可以自由使用和销毁以前的值EAX
、ECX
和EDX
。
因此,如果您不希望它们的值发生变化,请在调用函数之前保存 EAX
、ECX
、EDX
的值。这就是“来电保存”的意思。
或者更好的是,将其他寄存器用于您在函数调用后仍然需要的值。 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.如果可能,对调用后不需要的临时寄存器使用调用破坏寄存器。已经在内存中的值,因此在重新读取之前不需要写入,溢出也更便宜。
由于 EBX
、ESI
、EDI
和 EBP
是被调用者保存寄存器,函数必须将其中任何一个的值恢复为原始值修改,在 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? 以了解有关保留调用与调用破坏寄存器的更多信息。