c - 为什么将调用者堆栈的局部变量保存在被调用者堆栈的寄存器中?

标签 c arm callstack stack-frame

我正在尽力了解有关调用堆栈以及ARM Cortex-M0中堆栈帧的结构,事实证明这有些困难,但我正在耐心地学习。在这一个问题中,我有几个问题,希望大家可以在所有领域为我提供帮助。在本说明中,我的问题将以粗体突出显示。

我正在使用带有GDB的ARM Cortex-M0和一个简单的程序来调试。这是我的程序:

int main(void) {
    static uint16_t myBits;
    myBits = 0x70;

    halInit();

    return 0;
}


我在halInit()上设置了一个断点。然后,我在GDB终端上执行命令info frame以获得以下输出:

Stack level 0, frame at 0x20000400:
pc = 0x80000d8 in main (src/main.c:63); saved pc 0x8002dd2
source language c.
Arglist at 0x200003e8, args: 
Locals at 0x200003e8, Previous frame's sp is 0x20000400
Saved registers:
 r0 at 0x200003e8, r1 at 0x200003ec, r4 at 0x200003f0, r5 at 0x200003f4, r6 at 0x200003f8, lr at 0x200003fc


我将解释我的解释方式,如果我是正确的,请告诉我。

Stack level 0:堆栈帧的当前级别。 0将始终代表堆栈的顶部,换句话说,当前正在使用的堆栈帧。

frame at 0x20000400:这表示堆栈帧在闪存中的位置。

pc = 0x80000d8 in main (src/main.c:63);:这表示要执行的下一个执行,即程序计数器值。由于程序计数器始终代表要执行的下一条指令。

saved pc 0x8002dd2:这让我有些困惑,但是我认为这意味着返回地址,本质上是从执行halInit()函数返回时要执行的指令。但是,如果我在GDB终端中键入命令info reg,我会看到链接寄存器不是此值,而是下一个地址:lr 0x8002dd3。这是为什么?

source language c.:这表示所使用的语言。

Arglist at 0x200003e8, args::这表示传递给堆栈框架的参数的起始地址。由于args:为空白,这意味着未传递任何参数。这有两个原因:这是调用堆栈中的第一个堆栈帧,而我的函数没有任何参数int main(void)

Locals at 0x200003e8:这是我的局部变量的起始地址。如您在原始代码段中所见,我应该有一个局部变量myBits。我们待会儿再讲。

Previous frame's sp is 0x20000400:这是指向调用者堆栈框架顶部的堆栈指针。由于这是第一个堆栈帧,因此我希望该值应等于当前帧的地址。

Saved registers:
r0 at 0x200003e8
r1 at 0x200003ec
r4 at 0x200003f0
r5 at 0x200003f4
r6 at 0x200003f8
lr at 0x200003fc


这些是已被压入堆栈以保存以供以后当前堆栈帧使用的寄存器。我很好奇这部分,因为它是第一个堆栈帧,那么为什么要保存这么多寄存器?如果执行命令info reg,将得到以下输出:

r0             0x20000428   0x20000428
r1             0x0  0x0
r2             0x0  0x0
r3             0x70 0x70
r4             0x80000c4    0x80000c4
r5             0x20000700   0x20000700
r6             0xffffffff   0xffffffff
r7             0xffffffff   0xffffffff
r8             0xffffffff   0xffffffff
r9             0xffffffff   0xffffffff
r10            0xffffffff   0xffffffff
r11            0xffffffff   0xffffffff
r12            0xffffffff   0xffffffff
sp             0x200003e8   0x200003e8
lr             0x8002dd3    0x8002dd3
pc             0x80000d8    0x80000d8 <main+8>
xPSR           0x21000000   0x21000000


这告诉我,如果我通过执行命令p/x *(register)检查存储在已保存寄存器的每个存储器地址中的值,则该值应等于上面输出中显示的值。

Saved registers:
r0 at 0x200003e8 -> 0x20000428
r1 at 0x200003ec -> 0x0
r4 at 0x200003f0 -> 0x80000c4
r5 at 0x200003f4 -> 0xffffffff
r6 at 0x200003f8 -> 0xffffffff
lr at 0x200003fc -> 0x8002dd3


它可以正常工作,每个地址中的值代表info reg命令显示的值。但是,我注意到一件事。我有一个值为myBits的局部变量0x70,它似乎存储在r3中。但是,r3不会被压入堆栈进行保存。

如果我们进入下一条指令,则会为函数halInit()创建一个新的堆栈框架。这是通过在终端上执行命令bt来显示的。它生成以下输出:

#0  halInit () at src/hal/src/hal.c:70
#1  0x080000dc in main () at src/main.c:63


如果执行命令info frame,则会得到以下输出:

Stack level 0, frame at 0x200003e8:
pc = 0x8001842 in halInit (src/hal/src/hal.c:70); saved pc 0x80000dc
called by frame at 0x20000400
source language c.
Arglist at 0x200003e0, args: 
Locals at 0x200003e0, Previous frame's sp is 0x200003e8
Saved registers:
 r3 at 0x200003e0, lr at 0x200003e4


现在我们看到寄存器r3被压入该堆栈帧。该寄存器保存变量myBits的值。如果调用方堆栈帧需要此寄存器,为什么将r3压入此堆栈帧?

抱歉,很长的帖子,我只想介绍所有必填信息。

更新资料

我想我可能知道为什么r3被推入被调用者堆栈而不是推入调用者堆栈,即使调用者是需要此值的人也是如此。

是因为函数halInit()将修改r3中的值吗?

换句话说,被调用方堆栈框架知道调用方堆栈框架需要此寄存器值,因此它将把它压入其自己的堆栈框架中,以便可以出于自己的目的修改r3,然后在弹出堆栈框架时会将被压入堆栈帧的值0x70恢复回r3供调用者再次使用。这是正确的吗?如果是这样,被调用方堆栈框架如何知道调用方堆栈框架将需要此值?

最佳答案

我正在努力学习有关调用堆栈以及堆栈帧的方式
在ARM Cortex-M0中构建


因此,基于该报价,首先,cortex-m0没有堆栈框架,处理器确实是愚蠢的逻辑。编译器生成的堆栈帧是编译器对象,而不是指令集对象。函数的概念是编译器,不是更低的东西。编译器使用调用约定或设计的一些基本规则,以便对于该语言,调用者和被调用者函数可以准确知道参数的位置,返回值,并且没有人浪费其他数据。

编译器作者可以自由地做他们想做的任何事情,只要它可以工作并符合指令集的规则即可,就像逻辑而不是汇编语言一样。 (只要机器代码符合逻辑规则,汇编作者就可以自由地编写他们想要的任何汇编语言,助记符)。而且他们曾经这样做,处理器供应商已经开始提出建议,可以这样说,编译器也符合它们。它不是关于在编译器之间尽可能多地共享对象1)我不必提出自己的想法2)我们信任IP供应商及其处理器,并希望它们的调用约定是为性能和我们期望的其他原因而设计的。

到目前为止,随着gcc的发展和gcc的发展,gcc一直试图与ARM ABI保持一致。

当您有“许多”寄存器时,有很多方法是个问题,但是您会看到约定将首先使用寄存器,然后使用堆栈传递参数。您还将看到在功能中某些寄存器将被指定为易失性,以提高性能,而不必过多地使用内存(堆栈)。

通过使用调试器和断点,您在错误的位置查找语句,是要了解有关调用堆栈和堆栈帧的信息,这是编译器,而不是逻辑中如何处理异常。除非您的问题确实不够准确,否则您无法理解。

像GCC这样的编译器都有优化器,尽管它们对从优化版本学习死代码造成了混乱,但比未优化版本容易。让我们潜入

extern unsigned int more_fun ( unsigned int, unsigned int );
unsigned int fun ( unsigned int a, unsigned int b )
{
    return(a+b);
}


优化

 <fun>:
   0:   1840        adds    r0, r0, r1
   2:   4770        bx  lr




00000000 <fun>:
   0:   b580        push    {r7, lr}
   2:   b082        sub sp, #8
   4:   af00        add r7, sp, #0
   6:   6078        str r0, [r7, #4]
   8:   6039        str r1, [r7, #0]
   a:   687a        ldr r2, [r7, #4]
   c:   683b        ldr r3, [r7, #0]
   e:   18d3        adds    r3, r2, r3
  10:   0018        movs    r0, r3
  12:   46bd        mov sp, r7
  14:   b002        add sp, #8
  16:   bd80        pop {r7, pc}


首先,为什么功能在地址零?因为我反汇编了不是链接二进制文件的对象,所以也许以后再说。为何要反汇编还是编译为汇编?如果反汇编程序很好,那么实际上您将看到生成的内容,而不是将要包含的程序集,当然其中包含已编译的代码,许多非指令语言以及最终汇编后会更改的伪代码。

堆栈帧IMO是当存在第二个指针(即帧指针)时。您经常会在指令集中看到此指令,而指令集具有与此相关的指令或限制。例如,一个指令集可能有一个堆栈指针寄存器,但您不能从中寻址,可能还有另一个帧寄存器指针,并且您可以。因此,典型的输入方法是将帧指针保存在堆栈上,因为调用者可能一直在使用它作为其帧,我们希望将其返回找到的位置,然后将堆栈指针的地址复制到帧指针,然后移动尽可能多地使用此函数的堆栈指针,以便中断或调用其他函数,堆栈指针始终位于使用和未使用的堆栈空间之间的边界上。在这种情况下,将使用帧指针来访问任何传入的参数,或者以帧指针加偏移量的方式(对于向下增长的堆栈)并以负偏移方向访问本地数据的任何传入参数或返回地址。

现在看起来确实编译器正在使用帧指针,这很浪费,让我们不要这样做:

00000000 <fun>:
   0:   b082        sub sp, #8
   2:   9001        str r0, [sp, #4]
   4:   9100        str r1, [sp, #0]
   6:   9a01        ldr r2, [sp, #4]
   8:   9b00        ldr r3, [sp, #0]
   a:   18d3        adds    r3, r2, r3
   c:   0018        movs    r0, r3
   e:   b002        add sp, #8
  10:   4770        bx  lr


因此首先编译器确定要在堆栈中保存8个字节的内容。未经优化的几乎所有内容都会在堆栈中占有一席之地,传递的参数以及局部变量,在这种情况下没有任何局部变量,因此我们只传入了两个32位数字,即8个字节。调用约定尝试将r0用作第一个参数,将r1用作第二个参数(如果适用),在这种情况下可以。因此,当从堆栈指针中减去8时,就形成了堆栈帧,在这种情况下,堆栈帧指针就是堆栈指针。此处使用的调用约定允许r0-r3在函数中易失。编译器不必将找到的那些寄存器返回给调用者,它们可以在函数中随意使用。在这种情况下,编译器选择从堆栈中取出加法操作数,而不是使用第一个到释放的操作数。一旦r0和r1保存到堆栈中,则自由寄存器的“池”将假定以r0,r1,r2,r3开始。因此,是的,它的确看起来确实很破损,但这是事实,它在功能上是正确的,这是编译器的工作,以产生在功能上实现已编译代码的代码。此编译器使用的调用约定指出,如果合适,返回值将进入r0。

这样就设置了堆栈帧,从sp中减去8。传入的参数将保存到堆栈中。现在,函数从堆栈中拉入传入的参数开始,将它们相加,然后将结果放入返回寄存器中。

然后使用bx lr返回,并与pop一起查找该指令(对于armv6m,对于armv4t pop不能用于切换模式,因此编译器会在弹出至lr的情况下选择bx lr)。

armv4t thumb,如果此代码与arm混合使用,则不能使用pop来返回,因此返回信息会弹出到volatile寄存器中并执行bx lr,您不能直接通过thumb弹出到lr中。您可能可以告诉编译器我没有将此代码与arm代码混合使用,因此可以保存以使用pop返回。取决于编译器。

00000000 <fun>:
   0:   b580        push    {r7, lr}
   2:   b082        sub sp, #8
   4:   af00        add r7, sp, #0
   6:   6078        str r0, [r7, #4]
   8:   6039        str r1, [r7, #0]
   a:   687a        ldr r2, [r7, #4]
   c:   683b        ldr r3, [r7, #0]
   e:   18d3        adds    r3, r2, r3
  10:   0018        movs    r0, r3
  12:   46bd        mov sp, r7
  14:   b002        add sp, #8
  16:   bc80        pop {r7}
  18:   bc02        pop {r1}
  1a:   4708        bx  r1


看一个框架指针

00000000 <fun>:
   0:   b580        push    {r7, lr}
   2:   b082        sub sp, #8
   4:   af00        add r7, sp, #0
   6:   6078        str r0, [r7, #4]
   8:   6039        str r1, [r7, #0]
   a:   687a        ldr r2, [r7, #4]
   c:   683b        ldr r3, [r7, #0]
   e:   18d3        adds    r3, r2, r3
  10:   0018        movs    r0, r3
  12:   46bd        mov sp, r7
  14:   b002        add sp, #8
  16:   bd80        pop {r7, pc}


首先,将帧指针保存到堆栈,因为调用者或调用者调用者等可能正在使用它,这是我们必须保留的寄存器。现在,一些调用约定从一开始就发挥了作用。我们知道编译器知道我们不会调用另一个函数,因此我们不需要保留返回地址(存储在链接寄存器r14中),那么为什么要将其压入堆栈,为什么浪费空间和时钟周期呢?好吧,约定不久前就改变了,说堆栈应该是64位对齐的,因此基本上可以成对地对寄存器(偶数个寄存器)进行压入和弹出操作。有时,如我们在armv4t返回中所见,它们对使用多个指令。因此,编译器需要推入另一个寄存器,它可以并且有时您会看到它确实只是选择了一些未使用的寄存器并将其推入堆栈,也许我们可以在这里稍作改动。在这种情况下,您可以使用pop来切换模式,这样可以安全地使用pop pc生成返回信息,因此可以使用pop来切换模式,因此可以使用此处的链接寄存器(而不是其他寄存器)来保存指令。尽管是未优化的代码,但仍进行了一些优化。

保存帧指针,然后将帧指针与堆栈指针相关联,在这种情况下,它首先移动堆栈指针,并使帧指针与堆栈指针匹配,然后使用帧指针进行堆栈访问。噢,即使对于未优化的代码,也很浪费。但是,当被告知像这样进行编译时,也许此编译器默认为帧指针。

在这里,您的问题之一,到目前为止,我已经间接地对此进行了评论。完整尺寸的手臂处理器armv4t至armv7支持手臂指令和拇指指令。并不是每个人都支持每个人都有进化,但是您可以将手臂和拇指指令共存,作为该核心逻辑定义的规则的一部分。支持ARM的设计是因为arm指令必须是字对齐的,因此arm指令地址的低两位始终为零。所需的也对齐的16位指令集将始终具有地址零的低位。因此,为什么不使用地址的lsbit作为切换模式的方式呢?这就是他们选择要做的。首先是几条指令,然后是armv7架构所允许的更多指令,如果分支的地址(先查找bx,分支交换)的lsbit为1,则处理器在开始获取时会切换到拇指模式在该地址的指令中,程序计数器不保留该指令,而是由指令剥离,它只是一个信号,用于告诉指令切换模式。如果lsbit为0,则处理器切换到布防模式。如果已经处于所述模式,则仅停留在该模式。

现在出现了这些cortex-m核,它们只是拇指机器,没有手臂模式。工具到位了,所有这些都没有理由进行更改,如果您尝试在cortex-m上进入手臂模式,则会出现故障。

现在看一下上面的代码,有时我们用bx lr返回,有时我们用pop pc返回,在两种情况下lr都持有“返回地址”。为了使bx lr情况起作用,必须设置lr的lsbit。调用者无法知道我们将使用哪个指令来返回,并且调用者不必但可能使用bl进行调用,因此逻辑实际上将位设置为编译器。这就是为什么您的返回地址不按一个字节的原因。

如果您想了解编译器和堆栈框架,虽然未优化的绝对使用堆栈,如您所见,但是如果您了解不制作无效代码,则优化代码(如果您具有经过适当优化的编译器)可以更容易理解编译器的输出。

00000000 <fun>:
   0:   1840        adds    r0, r0, r1
   2:   4770        bx  lr


r0和r1是传入的参数,r0是返回值所在的位置,链接寄存器是返回地址。这就是您希望编译器为类似函数生成的内容。

因此,现在让我们尝试一些更复杂的事情。

extern unsigned int more_fun ( unsigned int, unsigned int );
unsigned int fun ( unsigned int a, unsigned int b )
{
    return(more_fun(a,b));
}

00000000 <fun>:
   0:   b510        push    {r4, lr}
   2:   f7ff fffe   bl  0 <more_fun>
   6:   bd10        pop {r4, pc}


首先,为什么优化器不这样做:

fun:
   b more_fun


我不知道。

为什么说bl 0,更多的乐趣不是零?这是一个未链接的对象,链接后,链接器将修改该bl指令以指向more_fun()。

第三,我们已经有了编译器来推送我们未使用的寄存器。它正在推入并弹出r4,以便它可以按照此编译器使用的调用约定使堆栈对齐。它可能选择了几乎任何一个寄存器,您可能会发现使用r3而不是r4的gcc或llvm / clang版本。 gcc已经使用r4了一段时间。它是寄存器列表中的第一个,您必须保留在寄存器列表中的第一个,如果他们想在调用中保留某些东西,他们将使用它们(我们将在第二秒看到)。所以也许这就是为什么,谁知道问作者。

extern unsigned int more_fun ( unsigned int, unsigned int );
unsigned int fun ( unsigned int a, unsigned int b )
{
    more_fun(a,b);
    return(a);
}

00000000 <fun>:
   0:   b510        push    {r4, lr}
   2:   0004        movs    r4, r0
   4:   f7ff fffe   bl  0 <more_fun>
   8:   0020        movs    r0, r4
   a:   bd10        pop {r4, pc}


现在我们正在进步。因此,我们告诉编译器必须在函数调用之间保存传入的参数。每个函数都会重新启动规则,因此每个调用的函数都会破坏r0-r3,因此,如果将r0-r3用于某些用途,则需要将其保存在某个地方。因此,这是一个非常明智的选择,而不是将传入的参数保存在堆栈上,并且可能必须执行多个代价高昂的内存周期才能访问它。取而代之的是将被调用者或被调用者的被调用者等值保存在堆栈中,并在函数中使用寄存器来保存该参数,因为这样做可以节省很多浪费的周期。无论如何,我们都需要使堆栈对齐,因此所有这些都可以保留r4并保存返回地址,因为我们自己进行调用,将其丢弃。调用r4后,保存我们需要的参数。使调用将返回值放在返回寄存器中并返回。随手清理堆栈。因此,这里的堆栈框架很小。没有太多使用堆栈。

extern unsigned int more_fun ( unsigned int, unsigned int );
unsigned int fun ( unsigned int a, unsigned int b )
{
    b<<=more_fun(a,b);
    return(a+b);
}

00000000 <fun>:
   0:   b570        push    {r4, r5, r6, lr}
   2:   0005        movs    r5, r0
   4:   000c        movs    r4, r1
   6:   f7ff fffe   bl  0 <more_fun>
   a:   4084        lsls    r4, r0
   c:   1960        adds    r0, r4, r5
   e:   bd70        pop {r4, r5, r6, pc}


我们又做了一次,我们得到了编译器必须保存一个我们没有用来保持对齐的寄存器。我们正在使用更多的堆栈,但是您会称其为堆栈框架吗?我们强制编译器必须通过子例程调用保留两个传入的参数。

extern unsigned int more_fun ( unsigned int, unsigned int );
unsigned int fun ( unsigned int a, unsigned int b, unsigned int c, unsigned int d )
{
    b<<=more_fun(b,c);
    c<<=more_fun(c,d);
    d<<=more_fun(b,d);
    return(a+b+c+d);
}


 0: b5f8        push    {r3, r4, r5, r6, r7, lr}
   2:   000c        movs    r4, r1
   4:   0007        movs    r7, r0
   6:   0011        movs    r1, r2
   8:   0020        movs    r0, r4
   a:   001d        movs    r5, r3
   c:   0016        movs    r6, r2
   e:   f7ff fffe   bl  0 <more_fun>
  12:   0029        movs    r1, r5
  14:   4084        lsls    r4, r0
  16:   0030        movs    r0, r6
  18:   f7ff fffe   bl  0 <more_fun>
  1c:   0029        movs    r1, r5
  1e:   4086        lsls    r6, r0
  20:   0020        movs    r0, r4
  22:   f7ff fffe   bl  0 <more_fun>
  26:   4085        lsls    r5, r0
  28:   19a4        adds    r4, r4, r6
  2a:   19e4        adds    r4, r4, r7
  2c:   1960        adds    r0, r4, r5
  2e:   bdf8        pop {r3, r4, r5, r6, r7, pc}


怎么办?我们至少确实得到它来保存r3以使堆栈均匀。我敢打赌我们现在就可以推...

extern unsigned int more_fun ( unsigned int, unsigned int );
unsigned int fun ( unsigned int a, unsigned int b, unsigned int c, unsigned int d, unsigned int e, unsigned int f )
{
    b<<=more_fun(b,c);
    c<<=more_fun(c,d);
    d<<=more_fun(b,d);
    e<<=more_fun(e,d);
    f<<=more_fun(e,f);
    return(a+b+c+d+e+f);
}

00000000 <fun>:
   0:   b5f0        push    {r4, r5, r6, r7, lr}
   2:   46c6        mov lr, r8
   4:   000c        movs    r4, r1
   6:   b500        push    {lr}
   8:   0011        movs    r1, r2
   a:   0007        movs    r7, r0
   c:   0020        movs    r0, r4
   e:   0016        movs    r6, r2
  10:   001d        movs    r5, r3
  12:   f7ff fffe   bl  0 <more_fun>
  16:   0029        movs    r1, r5
  18:   4084        lsls    r4, r0
  1a:   0030        movs    r0, r6
  1c:   f7ff fffe   bl  0 <more_fun>
  20:   0029        movs    r1, r5
  22:   4086        lsls    r6, r0
  24:   0020        movs    r0, r4
  26:   f7ff fffe   bl  0 <more_fun>
  2a:   4085        lsls    r5, r0
  2c:   9806        ldr r0, [sp, #24]
  2e:   0029        movs    r1, r5
  30:   f7ff fffe   bl  0 <more_fun>
  34:   9b06        ldr r3, [sp, #24]
  36:   9907        ldr r1, [sp, #28]
  38:   4083        lsls    r3, r0
  3a:   0018        movs    r0, r3
  3c:   4698        mov r8, r3
  3e:   f7ff fffe   bl  0 <more_fun>
  42:   9b07        ldr r3, [sp, #28]
  44:   19a4        adds    r4, r4, r6
  46:   4083        lsls    r3, r0
  48:   19e4        adds    r4, r4, r7
  4a:   1964        adds    r4, r4, r5
  4c:   4444        add r4, r8
  4e:   18e0        adds    r0, r4, r3
  50:   bc04        pop {r2}
  52:   4690        mov r8, r2
  54:   bdf0        pop {r4, r5, r6, r7, pc}
  56:   46c0        nop         ; (mov r8, r8)


好的,那就是这样...

extern unsigned int more_fun ( unsigned int, unsigned int );
extern void not_dead ( unsigned int *);
unsigned int fun ( unsigned int a, unsigned int b )
{
    unsigned int x[16];
    unsigned int ra;
    for(ra=0;ra<16;ra++)
    {
        x[ra]=more_fun(a+ra,b);
    }
    not_dead(x);
    return(ra);
}


00000000 <fun>:
   0:   b5f0        push    {r4, r5, r6, r7, lr}
   2:   0006        movs    r6, r0
   4:   b091        sub sp, #68 ; 0x44
   6:   0004        movs    r4, r0
   8:   000f        movs    r7, r1
   a:   466d        mov r5, sp
   c:   3610        adds    r6, #16
   e:   0020        movs    r0, r4
  10:   0039        movs    r1, r7
  12:   f7ff fffe   bl  0 <more_fun>
  16:   3401        adds    r4, #1
  18:   c501        stmia   r5!, {r0}
  1a:   42b4        cmp r4, r6
  1c:   d1f7        bne.n   e <fun+0xe>
  1e:   4668        mov r0, sp
  20:   f7ff fffe   bl  0 <not_dead>
  24:   2010        movs    r0, #16
  26:   b011        add sp, #68 ; 0x44
  28:   bdf0        pop {r4, r5, r6, r7, pc}
  2a:   46c0        nop         ; (mov r8, r8)


并且有您的堆栈框架,但是它实际上没有框架指针,并且不使用堆栈来访问内容。必须继续努力才能看到​​这一点,这是非常可行的。但希望到现在为止您明白我的意思了。您的问题是关于堆栈帧是用已编译的代码构造的,特别是编译器如何针对特定目标实现堆栈帧。

顺便说一句,这就是clang使用该代码所做的事情。

00000000 <fun>:
   0:   b5b0        push    {r4, r5, r7, lr}
   2:   af02        add r7, sp, #8
   4:   b090        sub sp, #64 ; 0x40
   6:   460c        mov r4, r1
   8:   4605        mov r5, r0
   a:   f7ff fffe   bl  0 <more_fun>
   e:   9000        str r0, [sp, #0]
  10:   1c68        adds    r0, r5, #1
  12:   4621        mov r1, r4
  14:   f7ff fffe   bl  0 <more_fun>
  18:   9001        str r0, [sp, #4]
  1a:   1ca8        adds    r0, r5, #2
  1c:   4621        mov r1, r4
  1e:   f7ff fffe   bl  0 <more_fun>
  22:   9002        str r0, [sp, #8]
  24:   1ce8        adds    r0, r5, #3
  26:   4621        mov r1, r4
  28:   f7ff fffe   bl  0 <more_fun>
  2c:   9003        str r0, [sp, #12]
  2e:   1d28        adds    r0, r5, #4
  30:   4621        mov r1, r4
  32:   f7ff fffe   bl  0 <more_fun>
  36:   9004        str r0, [sp, #16]
  38:   1d68        adds    r0, r5, #5
  3a:   4621        mov r1, r4
  3c:   f7ff fffe   bl  0 <more_fun>
  40:   9005        str r0, [sp, #20]
  42:   1da8        adds    r0, r5, #6
  44:   4621        mov r1, r4
  46:   f7ff fffe   bl  0 <more_fun>
  4a:   9006        str r0, [sp, #24]
  4c:   1de8        adds    r0, r5, #7
  4e:   4621        mov r1, r4
  50:   f7ff fffe   bl  0 <more_fun>
  54:   9007        str r0, [sp, #28]
  56:   4628        mov r0, r5
  58:   3008        adds    r0, #8
  5a:   4621        mov r1, r4
  5c:   f7ff fffe   bl  0 <more_fun>
  60:   9008        str r0, [sp, #32]
  62:   4628        mov r0, r5
  64:   3009        adds    r0, #9
  66:   4621        mov r1, r4
  68:   f7ff fffe   bl  0 <more_fun>
  6c:   9009        str r0, [sp, #36]   ; 0x24
  6e:   4628        mov r0, r5
  70:   300a        adds    r0, #10
  72:   4621        mov r1, r4
  74:   f7ff fffe   bl  0 <more_fun>
  78:   900a        str r0, [sp, #40]   ; 0x28
  7a:   4628        mov r0, r5
  7c:   300b        adds    r0, #11
  7e:   4621        mov r1, r4
  80:   f7ff fffe   bl  0 <more_fun>
  84:   900b        str r0, [sp, #44]   ; 0x2c
  86:   4628        mov r0, r5
  88:   300c        adds    r0, #12
  8a:   4621        mov r1, r4
  8c:   f7ff fffe   bl  0 <more_fun>
  90:   900c        str r0, [sp, #48]   ; 0x30
  92:   4628        mov r0, r5
  94:   300d        adds    r0, #13
  96:   4621        mov r1, r4
  98:   f7ff fffe   bl  0 <more_fun>
  9c:   900d        str r0, [sp, #52]   ; 0x34
  9e:   4628        mov r0, r5
  a0:   300e        adds    r0, #14
  a2:   4621        mov r1, r4
  a4:   f7ff fffe   bl  0 <more_fun>
  a8:   900e        str r0, [sp, #56]   ; 0x38
  aa:   350f        adds    r5, #15
  ac:   4628        mov r0, r5
  ae:   4621        mov r1, r4
  b0:   f7ff fffe   bl  0 <more_fun>
  b4:   900f        str r0, [sp, #60]   ; 0x3c
  b6:   4668        mov r0, sp
  b8:   f7ff fffe   bl  0 <not_dead>
  bc:   2010        movs    r0, #16
  be:   b010        add sp, #64 ; 0x40
  c0:   bdb0        pop {r4, r5, r7, pc}


现在,您使用了术语调用堆栈。此编译器使用的调用约定说,尽可能使用r0-r3传入第一个参数,然后再使用堆栈。

unsigned int fun ( unsigned int a, unsigned int b, unsigned int c, unsigned int d, unsigned int e )
{
    return(a+b+c+d+e);
}
00000000 <fun>:
   0:   b510        push    {r4, lr}
   2:   9c02        ldr r4, [sp, #8]
   4:   46a4        mov r12, r4
   6:   4463        add r3, r12
   8:   189b        adds    r3, r3, r2
   a:   185b        adds    r3, r3, r1
   c:   1818        adds    r0, r3, r0
   e:   bd10        pop {r4, pc}


因此,具有四个以上的参数,前四个在r0-r3中,然后假设您要引用的“调用堆栈”是第五个参数。拇指指令集使用bl作为其主要调用指令,该指令使用r14作为返回地址,与其他可能使用堆栈存储返回地址的指令集不同,arm使用寄存器。流行的arm调用约定在前几个操作数中使用寄存器,然后在其后使用堆栈。

您可能希望查看其他指令集以了解更多调用堆栈

00000000 <_fun>:
   0:   1d80 0008       mov 10(sp), r0
   4:   6d80 000a       add 12(sp), r0
   8:   6d80 0006       add 6(sp), r0
   c:   6d80 0004       add 4(sp), r0
  10:   6d80 0002       add 2(sp), r0
  14:   0087            rts pc

关于c - 为什么将调用者堆栈的局部变量保存在被调用者堆栈的寄存器中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52067217/

相关文章:

c - 用ARM汇编语言编写一个函数,将一个字符串插入到另一个字符串的特定位置

c - 执行 malloc 时程序崩溃

c++ - 使用 mingw 编译时增加堆栈大小?

typescript - 以编程方式访问 TypeScript 调用堆栈

c - 如何从另一个字符串中删除最后一次出现的字符串?

c++ - 如何查看库的内部(尤其是 C 标准库)?

c++ - 交叉编译问题

assembly - x86 的交叉编译 arm 程序集

c - 分段断层

c - 为什么 C 中的数组会衰减为指针?