简体   繁体   English

调用函数时保存状态寄存器

[英]Saving Status register when calling a function

As I understand, when I'm calling a function, based on GCC Calling Convention, this is what happens: 据我了解,当我基于GCC调用约定调用函数时,会发生以下情况:

Caller saves values of AX, CX and DX registers. 调用者保存AX,CX和DX寄存器的值。 Arguments and returning address are pushed on stack. 参数和返回地址被压入堆栈。 Also, calle must preserve values of SI, DI, BX and BP registers. 同样,被调用方必须保留SI,DI,BX和BP寄存器的值。

But, what about Status register? 但是,状态寄存器呢? Who saves it? 谁来保存?

Also, is value of returning address which is pushed on stack actually value of Instruction register? 另外,被压入堆栈的返回地址的值实际上是指令寄存器的值吗?

The status register is not preserved across function calls. 状态寄存器不会在函数调用之间保留。 If there's something important in the status register it needs to be copied elsewhere (generally with SETcc), but the calling convention doesn't require the calling function to do this, just as it doesn't require the calling function to save and restore AX et al. 如果状态寄存器中有重要内容,则需要将其复制到其他位置(通常使用SETcc),但是调用约定不需要调用函数来执行此操作,就像不需要调用函数来保存和还原AX一样等。 if there's nothing important in them. 如果他们没有什么重要的。

Answering your second question: 回答第二个问题:

Also, is value of returning address which is pushed on stack actually value of Instruction register? 另外,被压入堆栈的返回地址的值实际上是指令寄存器的值吗?

You mean the value pushed by call instruction? 您的意思是call指令推入的值? Yes, that's current rip ( eip/ip in 32/16 bit modes) value during call internal execution (as rip points to next instruction). 是的,这是call内部执行期间的当前rip (32/16位模式下的eip/ip )值( rip指向下一条指令)。

And the ret instruction will pop whatever value is on top of stack, and set that into rip , changing the code-execution flow for next instruction (away from the instruction next after the ret to the address/value which was in stack). ret指令将弹出堆栈顶部的所有值,并将其设置为rip ,从而更改下一条指令的代码执行流(远离ret之后的下一条指令到堆栈中的地址/值)。 So the value from stack becomes content of ip register, after ret is finished. 因此, ret完成后,来自堆栈的值将成为ip寄存器的内容。 The ret is like (non-existent) pop ip , but it has its own mnemonics to make it stand out in source better when reading it by human, and also it has completely different opcode, so the HW implementation in the transistors is completely specific to it (which makes sense on modern x86 where the ret implementation is using many additional tricks for better performance, but I'm sort of curious why 8086 would not encode it as pop ip , like just another register for pop , was probably somewhat special in some detail even back then). ret就像(不存在) pop ip ,但是它具有自己的助记符,可以使人在阅读时更好地在源代码中脱颖而出,并且它的操作码也完全不同,因此晶体管中的硬件实现是完全特定的对此(在现代x86上有意义,因为ret实现使用了许多其他技巧来提高性能,但是我很好奇为什么8086不会像其他pop寄存器那样将其编码为pop ip可能有点特殊甚至在那时也有一些细节)。

GCC Calling Convention GCC通话惯例

gcc uses the standard calling convention on whatever platform it's targeting. gcc在目标平台上使用标准的调用约定。 It sounds like you're describing the i386 System V calling convention / ABI used on Linux, and/or some Windows calling conventions. 听起来您正在描述Linux上使用的i386 System V调用约定/ ABI和/或某些Windows调用约定。 (some of which pass args differently, but make the same choice of registers that can be clobbered). (其中一些通过args的方式有所不同,但是对可以被破坏的寄存器的选择相同)。

You're using 16-bit register names, but gcc barely supports 16-bit x86. 您使用的是16位寄存器名称,但gcc几乎不支持16位x86。 It basically generates 32-bit code and then assembles it with .code16 so most instructions have operand-size and/or address-size prefixes. 它基本上会生成32位代码,然后将其与.code16进行汇编,因此大多数指令都具有操作数大小和/或地址大小的前缀。

Caller saves values of AX, CX and DX registers 调用者保存AX,CX和DX寄存器的值

No, the caller does that only if it had any data in them that it wants to preserve across the call . 不,仅当呼叫者中有任何要在整个call保留的数据时,呼叫者才这样做。 The normal case is that the caller lets those values die. 正常情况是调用者让这些值消失。 "caller-saved" vs. "callee-saved" is bad terminology because it implies that all registers actually get saved somewhere. “ caller-saved”与“ callee-saved”是不好的术语,因为它意味着所有寄存器实际上都保存在某个地方。

Much easier to understand, IMO, is IMO更容易理解的是

  • call-clobbered : EAX ECX EDX and condition codes (part of EFLAGS), all xmm regs 呼叫密集型 :EAX ECX EDX和条件代码(EFLAGS的一部分),所有xmm规则
  • call-preserved : EBX, ESI EDI, EBP, ESP. 通话保留 :EBX,ESI EDI,EBP,ESP。

DF must be 0 at call and return, so string instructions go upwards. 调用和返回时DF必须为0,因此字符串指令向上。 (DF is another bit in EFLAGS). (DF是EFLAGS中的另一位)。 The x87 stack must be empty on call and ret , except for functions which return an FP value (in which case st0 has the return value, and the rest of the x87 stack is empty). x87堆栈在callret必须为空,但返回FP值的函数除外(在这种情况下, st0具有返回值,而x87堆栈的其余部分为空)。

Call-clobbered means that after a call , the caller has to assume the register holds garbage, whether or not the callee actually used the register. 呼叫密集型意味着在call之后,无论被叫方是否实际使用了该寄存器,调用者都必须假定该寄存器持有垃圾。 If there was anything in that register the caller needed for later, it has to move it somewhere else. 如果该寄存器中包含任何内容,则调用者以后需要使用它,则必须将其移至其他位置。 But if not, it's totally fine to let the value die. 但是,如果没有,让价值消亡是完全可以的。 eg to compile something like rv = foo(a + b + c) , the caller would calculate a+b+c in a register. 例如,要编译rv = foo(a + b + c) ,调用者将在寄存器中计算a+b+c But if it doesn't also need that value after the function call, it doesn't have any need to preserve it. 但是,如果在函数调用之后它也不需要该值,则不需要保留它。

Call-preserved means the caller can assume the register value hasn't changed, whether the callee simply avoided touching that register, or the callee saved / restored it. 保留呼叫意味着呼叫者可以假定寄存器值没有改变,无论被叫方只是避免触摸该寄存器,还是被叫方保存/恢复了该寄存器。 (Or for ESP, it's common for the callee to restore it with an add esp, 28 or similar, to reverse whatever changes it made with push and sub . It doesn't matter how the callee manages to return with call-preserved registers still holding the caller's values, just that it does. This is why "callee-saved" is also not the clearest term: it implies the callee explicitly saves them. (或者,对于ESP,被调用方通常使用add esp, 28或类似的值)来恢复它,以撤消对pushsub所做的任何更改。被调用方如何管理仍保留有调用保留的寄存器,这并不重要保持调用者的值就可以了,这就是为什么“保存被调用者”也不是最清晰的术语:这意味着被调用者明确地保存了它们。

But, what about Status register? 但是,状态寄存器呢? Who saves it? 谁来保存?

Nobody saves it, except in very rare cases . 除极少数情况外,没人保存它 The caller could save it if desired, but usually it's far easier and cheaper to to just redo a compare ( popf is slow, and the pushf to save EFLAGS in the first place isn't free). 调用者可以根据需要保存它,但是通常只需重做比较就容易得多,而且便宜得多( popf很慢,而保存EFLAGS的pushf并不是免费的)。

Or more often there wasn't any useful data in the condition codes, just integer values in integer registers. 或更常见的是,条件代码中没有任何有用的数据,而整数寄存器中只有整数值。 Most instructions write EFLAGS, but most of the time you never read those results. 大多数指令都写EFLAGS,但是大多数时候您从未读过这些结果。 You usually use add , imul , and so on for the integer result and ignore the flag result. 您通常对整数结果使用addimul ,而忽略标志结果。

Fun fact: 64-bit OS X system calls set CF on error, otherwise they clear CF . 有趣的事实: 64位OS X系统调用将CF设置为出错,否则将CF清除 No common 32 or 64-bit function-calling conventions return anything in EFLAGS; 没有通用的32位或64位函数调用约定在EFLAGS中返回任何内容。 they're just clobbered. 他们只是被破坏。 (For Linux system calls, EFLAGS / RFLAGS is preserved. It's typical for system calls to not clobber any registers, other than the return value, partly because this avoids leaking kernel information back to user space.) (对于Linux系统调用,将保留EFLAGS / RFLAGS。系统调用通常不破坏除返回值以外的任何寄存器,部分原因是这样可以避免将内核信息泄漏回用户空间。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM