assembly 什么是被叫方和主叫方保存的寄存器?

pbossiut  于 2022-12-13  发布在  其他
关注(0)|答案(6)|浏览(235)

我在理解调用者和被调用者保存的寄存器之间的区别以及何时使用什么时遇到了一些麻烦。
我使用的是MSP430:
步骤:

mov.w #0,R7 
mov.w #0,R6 
add.w R6,R7 
inc.w R6 
cmp.w R12,R6 
jl l$loop 
mov.w R7,R12
ret

上面的代码是一个被调用者,并且在一个教科书示例中使用过,因此它遵循了约定。R6和R7是被调用者保存的,R12是调用者保存的。我的理解是,被调用者保存的reg不是“全局”的,因为在过程中更改它的值不会影响它在过程之外的值。这就是为什么你必须在开始时将一个新值保存到被调用者reg中。
R12,保存的调用者是“全局”的,因为缺少更好的词。过程所做的在调用后对R12有持久的影响。
我的理解正确吗?我错过了其他的东西吗?

nxowjjhe

nxowjjhe1#

调用方保存寄存器(AKAvolatile寄存器或call-crobbered)用于保存不需要在调用之间保留的临时数量。

因此,调用方有责任将这些寄存器压入堆栈或将它们复制到其他地方,* 如果 * 它希望在过程调用后恢复此值。
不过,让call破坏这些寄存器中的临时值是正常的。

被调用方保存的寄存器(AKA非易失性寄存器或调用保留的)用于保存应在调用之间保留的长期值。

当调用者进行过程调用时,它可以预期那些寄存器在被调用者返回后将保持相同的值,这使得被调用者有责任在返回到调用者之前保存和恢复它们,或者不去碰它们。

zlhcx6iw

zlhcx6iw2#

被调用者与调用者保存是一种约定,用于指定在调用中由谁负责保存和恢复寄存器中的值。所有寄存器都是“全局”的,因为任何地方的任何代码都可以看到(或修改)寄存器,并且这些修改将被任何地方的任何后续代码看到。寄存器保存约定的要点是代码不应该修改某些寄存器,因为其他代码假定值没有被修改。
在你的示例代码中,没有一个寄存器是被调用者保存,因为它没有尝试保存或恢复寄存器值。然而,它似乎不是一个完整的过程,因为它包含了一个指向未定义标签(l$loop)的分支。所以它可能是一个过程中间的代码片段,该过程将一些寄存器视为被调用者保存;只是缺少保存/恢复说明。

0yg35tkg

0yg35tkg3#

调用方保存/被调用方保存的术语是基于一个相当愚蠢的低效编程模型,其中调用方实际上保存/恢复所有调用损坏的寄存器(而不是在其他地方保存长期有用的值),而被调用方实际上保存/恢复所有调用保留的寄存器(而不是不使用其中的一些或任何寄存器)。
或者你必须理解“调用者保存”意味着“如果你以后想要这个值,以某种方式保存”。
实际上,高效的代码会让不再需要的值被销毁。编译器通常会在函数开始时保存一些调用保留寄存器(并在函数结束时恢复它们)。在函数内部,它们会使用这些寄存器来保存需要在函数调用中保留的值。

**我更喜欢“call-preserved”(呼叫保留)和“call-crobbered”(呼叫被破坏),**一旦你听说了基本概念,这两个词就很明确,而且是自我描述的,不需要从呼叫者或被呼叫者的Angular 进行任何严肃的心理训练。(这两个术语都是从 * 相同 * 的Angular )。

另外,这些术语的差异不止一个字母。
术语volatile / non-volatile非常好用,可以类比存储器在掉电或不掉电时会失去其价值(就像DRAM与Flash)。但是C volatile关键字具有完全不同的技术含义,因此在描述C调用约定时,这是“(非)-volatile”的一个缺点。

*Call-clobbered,也称为caller-savedvolatile寄存器适用于下一次函数调用后不需要的临时值。

从被调用方的Angular 来看,您的函数可以随意覆盖(aka clobber)这些寄存器,而无需保存/恢复。
从调用者的Angular 来看,call foo会销毁(也称为crobbers)所有被调用crobbered的寄存器,或者至少您必须假设它会这样做。
你可以编写具有自定义调用约定的私有helper函数,例如,你知道它们不会修改某个寄存器。但是,如果你所知道的(或者想要假设或依赖的)只是目标函数遵循正常的调用约定,那么你必须将函数调用视为破坏了所有被调用的寄存器。这就是这个名字的字面意思:呼叫会破坏这些寄存器。
某些执行过程间优化的编译器还可以使用自定义调用约定,为不遵循ABI的函数创建仅供内部使用的定义。

*Call-preserved,也称为calee-savednon-volatile寄存器在函数调用之间保持其值。这对于进行函数调用的循环中的循环变量或一般非叶函数中的任何内容都很有用。

从被调用者的Angular 来看,这些寄存器不能被修改,除非你把原始值保存在某个地方,这样你就可以在返回之前恢复它。(几乎总是调用保留),您可以减去一个已知的偏移量,然后在返回之前再将其加回来,而不是实际上将旧值 * 保存 * 在任何地方。即,您可以通过航位推算法恢复它,除非你分配了一个运行时变量的堆栈空间。然后你通常从另一个寄存器恢复堆栈指针。
一个可以从使用大量寄存器中获益的函数可以保存/恢复一些调用保留寄存器,这样它就可以将它们用作更多的临时寄存器,即使它不进行任何函数调用。通常你只会在用完调用乱码寄存器后才这样做,因为保存/恢复通常会在函数的开始/结束处花费一次push/pop。(或者,如果您的函数有多个退出路径,则每个退出路径中都有一个pop。)
“呼叫者保存”这一名称具有误导性:你不需要特意保存/恢复它们。通常你会把你的代码中的值保存在调用保留寄存器中,或者堆栈上的某个地方,或者你可以重新加载的其他地方。让一个call破坏临时值是正常的。

ABI或调用约定定义了哪些是哪些

有关x86-64系统V ABI的示例,请参见What registers are preserved through a linux x86-64 function call
而且,在我所知道的所有函数调用约定中,arg-passing寄存器总是被调用重写。
但是系统调用的调用约定通常使除返回值调用保留之外的所有寄存器。(通常包括偶数条件码/标志。)参见What are the calling conventions for UNIX & Linux system calls on i386 and x86-64

t98cgbkg

t98cgbkg4#

调用方保存(也称为易失性或调用乱码)寄存器

  • 调用程序保存的寄存器中的值是短期的,不会在调用之间保留
  • 它保存临时(即短期)数据
    被调用方保存(AKA非易失性或调用保留)寄存器
  • 被调用方保存的寄存器保存跨调用的值,并且是长期的
  • 它保存通过多个函数/调用使用的非临时(即长期)数据
dgsult0t

dgsult0t5#

我不确定这是否能增加什么,
调用者保存意味着调用者必须保存寄存器,因为它们将在调用中被乱码,并且在调用返回后别无选择,只能处于乱码状态(例如,对于cdecl,返回值为eax。对于被调用者来说,将返回值恢复到调用前的值没有意义,因为它是一个返回值)。
被调用者保存意味着被调用者必须保存寄存器,然后在调用结束时恢复它们,因为它们向调用者保证在函数返回后包含相同的值,并且有可能恢复它们,即使它们在调用期间的 * 某个点 * 被重写。
上述定义的问题是,例如在Wikipedia cdecl上,它说eaxecxedx是调用者保存的,其余的是被调用者保存的,这意味着调用者必须保存所有这3个寄存器,而如果这些寄存器中没有一个被调用者首先使用的话,它可能不会保存。在这种情况下,调用者“保存”就成了一个用词不当的问题。但“callclobbered”仍然正确地适用。这与“the rest”被调用者保存的情况相同。这意味着所有其他x86寄存器将被保存并由被调用者恢复,eax:edx可以用来返回一个64位的值。我不知道为什么ecx在需要的时候也是调用者保存的,但是它确实是。

cwxwcias

cwxwcias6#

我在问了差不多3年的问题后补充了这一点。简而言之,调用者保存以下寄存器--〉rdi,rsi,rdx,rcx,r8,r9。被调用者保存以下寄存器--〉rbx,rbp,r12,r13,r14。
注:我是从x86-64架构的Angular 来回答的。
参考:计算机系统:《程序员的视角》作者:Randal Bryant(作者),大卫O 'Hallaron(作者)

相关问题