问题描述
我写了一个简单的汇编程序:
I've written a simple assembly program:
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,负责将目标文件链接到 linux 上的可执行文件.
I am the NASM assembler and the GCC to link the object file to an executable on linux.
本质上,该程序首先将堆栈指针的值放入寄存器 EDX,然后打印该寄存器的内容两次.但是,在第二次 printf 调用之后,打印到 stdout 的值与第一个不匹配.
Essentially, this program is first putting the value of the stack pointer into register EDX, it is then printing the contents of this register twice. However, after the second printf call, the value printed to the stdout does not match the first.
这种行为看起来很奇怪.当我用 EBX 替换这个程序中 EDX 的每个用法时,输出的整数与预期的一样.我只能推断 EDX 在 printf 函数调用期间的某个时刻被覆盖.
This behaviour seems strange. When I replace every usage of EDX in this program with EBX, the outputted integers are identical as expected. I can only infer that EDX is overwritten at some point during the printf function call.
为什么会这样?以及如何确保我将来使用的寄存器不会与 C lib 函数发生冲突?
Why is this the case? And how can I make sure that the registers I use in future don't conflict with C lib functions?
推荐答案
根据 x86 ABI、EBX
、ESI
、EDI
和EBP
是被调用者保存寄存器,EAX
、ECX
和 EDX
是调用者保存寄存器.
According to the x86 ABI, EBX
, ESI
, EDI
, and EBP
are callee-save registers and EAX
, ECX
and EDX
are caller-save registers.
这意味着函数可以自由地使用和破坏以前的值EAX
、ECX
和EDX
.出于这个原因,如果你不想改变它们的值,请在调用函数之前保存 EAX
、ECX
、EDX
的值.这就是呼叫者保存".意思是.
It means that functions can freely use and destroy previous values EAX
, ECX
, and EDX
.For that reason, save values of EAX
, ECX
, EDX
before calling functions if you don't want their values to change. It is what "caller-save" mean.
或者更好的是,将其他寄存器用于函数调用后仍然需要的值.EBX
在函数开始/结束时的 push/pop 比在进行函数调用的循环中的 EDX
的 push/pop 好得多.如果可能,请为调用后不需要的临时寄存器使用调用破坏寄存器.已经在内存中的值,因此它们在重新读取之前不需要写入,也更容易溢出.
Or better, use other registers for values that you're still going to need after a function call. push/pop of EBX
at the start/end of a function is much better than push/pop of EDX
inside a loop that makes a function call. When possible, use call-clobbered registers for temporaries that aren't needed after the call. Values that are already in memory, so they don't need to written before being re-read, are also cheaper to spill.
由于EBX
、ESI
、EDI
和EBP
是被调用者保存寄存器,函数必须恢复在返回之前,它们修改的任何值的原始值.
Since EBX
, ESI
, EDI
, and EBP
are callee-save registers, functions have to restore the values to the original for any of those they modify, before returning.
ESP
也是被调用者保存的,但除非你将返回地址复制到某处,否则你不能搞砸.
ESP
is also callee-saved, but you can't mess this up unless you copy the return address somewhere.
这篇关于为什么调用 printf 时会覆盖 EDX 的值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!