Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Assembly 为什么子例程调用时没有完整的上下文保存?_Assembly_Call_Cpu Registers_Subroutine_Context Switch - Fatal编程技术网

Assembly 为什么子例程调用时没有完整的上下文保存?

Assembly 为什么子例程调用时没有完整的上下文保存?,assembly,call,cpu-registers,subroutine,context-switch,Assembly,Call,Cpu Registers,Subroutine,Context Switch,在子程序调用时,我们保存pc的内容,以便重新启动调用例程。但是,如果被调用的子例程更改通用寄存器的值,会发生什么情况?如果调用子例程必须访问寄存器中存储的旧值,是否会给调用子例程带来任何问题?有两个相互冲突的需求: 被调用函数(被调用方)需要临时寄存器来完成其工作 调用方需要某种状态才能在函数调用中生存 如果调用者必须保存/还原它想要保留的所有内容,或者被调用者必须保存/还原它使用的每个寄存器,则速度会很慢 冲突需求的解决方案是定义哪些寄存器被调用者保存,哪些寄存器可以被删除。非调用保留寄存

在子程序调用时,我们保存pc的内容,以便重新启动调用例程。但是,如果被调用的子例程更改通用寄存器的值,会发生什么情况?如果调用子例程必须访问寄存器中存储的旧值,是否会给调用子例程带来任何问题?

有两个相互冲突的需求:

  • 被调用函数(被调用方)需要临时寄存器来完成其工作
  • 调用方需要某种状态才能在函数调用中生存
如果调用者必须保存/还原它想要保留的所有内容,或者被调用者必须保存/还原它使用的每个寄存器,则速度会很慢

冲突需求的解决方案是定义哪些寄存器被调用者保存,哪些寄存器可以被删除。非调用保留寄存器可能不会被调用的特定函数破坏,但调用方必须假定它们是

相关:我的回答考虑了参数传递寄存器过多和调用保留寄存器不足之间的权衡


在循环中调用另一个函数的函数通常会将其循环计数器和其他内容保留在调用保留寄存器中。被调用的函数要么根本不使用寄存器,要么已经保存/恢复了它

如果调用函数的状态多于保留调用的寄存器,则必须将部分状态“溢出”到内存中(即保存/恢复)。理想情况下,它可以溢出调用前不需要保存的未修改值,只在调用后重新加载(例如,非静态数组或结构的基址)。对于循环计数器之类的东西,这比将到内存的往返放入依赖链更有效。(如果被调用的函数只需要几个周期,但无法内联,因为它是单独编译的,这很重要。它还可以简单地保存指令/代码大小。)


x86有很多功能。有关链接,请参见。Agner Fog对这一主题有很好的指导

在x86-64 SystemV ABI(Linux和OS X使用)中:

  • 保留呼叫:RBP、RBX和R12–R15
  • 调用clobbered:xmm0-15、标志、所有其他整数寄存器。(包括
    r11
    ,它不用于传递任何内容,因此可以用作包装器/垫片函数的scratch-reg。)

如果需要,可以创建不符合ABI的函数,调用方知道被调用函数实际上是哪个寄存器阻塞的。编译器可以在使用诸如
gcc-fwhole程序
或链接时间优化之类的选项时执行此操作。通常,编译器总是使用ABI兼容的函数,因为它们不确定它们发出的定义是否是链接时使用的定义。显然,手工编写的ASM可以做任何事情,但是手工编写任何东西(除了一小部分函数)都是维护的噩梦

但是,如果被调用的子例程更改通用寄存器的值,会发生什么情况

它取决于子例程修改的寄存器。根据的不同,有一个子程序根据合同有义务不修改的寄存器列表(以及另一个子程序可以自由修改的寄存器列表)

如果一个子例程不遵守这个契约,并且修改了它不应该拥有的寄存器,那么糟糕的事情就会发生

如果子例程想要使用它有义务不修改的寄存器,它必须首先将这些寄存器值保存到堆栈中。一旦保存了寄存器值,它就可以将寄存器用于新值。当子例程完成时,它必须使用堆栈上保存的值来恢复原始寄存器值。通过这种方式,子例程可以根据需要使用寄存器,但是对于调用者来说,寄存器没有明显的修改

如果调用子例程必须访问存储在寄存器中的旧值,是否会导致任何问题

只要子例程遵循调用约定,就不会。如果子例程不遵循调用约定,并且它破坏(或“破坏”)了“保留”寄存器中的原始值,那么是的,它会导致问题


不过,并非所有寄存器都必须保留。根据调用约定,子例程可以修改某些寄存器。如果这些寄存器对调用者很重要,那么调用者必须在调用子例程之前将这些寄存器保存到堆栈中,然后在调用子例程之后使用堆栈恢复寄存器值。

@dwelch:ASM新手提出的问题已经够多了,他们编写了
pusha/pushf
popf/popa
表明许多人不理解调用约定,特别是在32位的寄存器中,arg没有传递。我没有投反对票,因为我同意这不是一个好问题。我认为写一个有趣的答案是可能的,但如果不是因为赏金,我不会费心的。所以这是在别处问和回答的?一模一样then@dwelch:更像是,当我们提到这是关于某人讨厌的代码的旁注时,我们可以指出这一点。我不记得这是以前的主要问题,只是当人们询问其他问题时,阅读难看的代码会让我发疯。