【问题标题】:R7 and R11 relation with Link Register in ARM architecture (thumb/arm) calling conventionR7 和 R11 与 ARM 架构中的链接寄存器(thumb/arm)调用约定的关系
【发布时间】:2016-12-10 15:06:00
【问题描述】:

我在看一个由 gcc 生成的 arm 汇编代码,我注意到 GCC 用以下代码编译了一个函数:

   0x00010504 <+0>: push    {r7, lr}
   0x00010506 <+2>: sub sp, #24
   0x00010508 <+4>: add r7, sp, #0
   0x0001050a <+6>: str r0, [r7, #4]
=> 0x0001050c <+8>: mov r3, lr
   0x0001050e <+10>:    mov r1, r3
   0x00010510 <+12>:    movw    r0, #1664   ; 0x680
   0x00010514 <+16>:    movt    r0, #1
   0x00010518 <+20>:    blx 0x10378 <printf@plt>
   0x0001051c <+24>:    add.w   r3, r7, #12
   0x00010520 <+28>:    mov r0, r3
   0x00010522 <+30>:    blx 0x10384 <gets@plt>
   0x00010526 <+34>:    mov r3, lr
   0x00010528 <+36>:    mov r1, r3
   0x0001052a <+38>:    movw    r0, #1728   ; 0x6c0
   0x0001052e <+42>:    movt    r0, #1
   0x00010532 <+46>:    blx 0x10378 <printf@plt>
   0x00010536 <+50>:    adds    r7, #24
   0x00010538 <+52>:    mov sp, r7
   0x0001053a <+54>:    pop {r7, pc}

对我来说有趣的是,我看到 GCC 使用 R7 将值弹出到 PC 而不是 LR。我在 R11 上看到了类似的情况。编译器将 r11 和 LR 推入堆栈,然后将 R11 弹出到 PC。不应该 LR 充当返回地址而不是 R7 或 R11。为什么在这里使用 R7(拇指模式下的帧指针)? 如果您查看苹果 ios 调用约定,它甚至会有所不同。它使用其他寄存器(例如 r4 到 r7)将控制权返回给 PC。不应该用LR吗?

或者我在这里遗漏了什么?

另一个问题是,看起来 LR、R11 或 R7 值从来都不是返回地址的直接值。但是指向包含返回地址的堆栈的指针。对吗?

另一个奇怪的事情是编译器不会对函数 epoilogue 做同样的事情。例如,它可能不使用 pop to PC 而是使用 bx LR,但为什么呢?

【问题讨论】:

  • 如果编译器必须将返回地址存储在堆栈中,它可以通过简单地将返回地址直接解栈到PC中来保存bx lr。为此目的,popldm* 被记录为互通分支指令。
  • pop {r7, pc} 不会像您的问题所暗示的那样将 r7 放在 pc 上。它从堆栈中弹出两个寄存器 (sp)。
  • 我不确定我是否正确理解了您的问题,但为了清楚起见,push {r7, lr} 等同于stmdb sp!, {r7, lr},这意味着它将减少sp,将r7 存储为@ 987654334@,再次递减sp,并将lr存储到[sp]。另一方面,pop {r7, pc} 等价于stmia sp!, {r7, pc},这意味着它将从[sp] 加载pc,递增sp,从[sp] 加载r7,然后再次递增sp。因为 pc 已加载,它将有效地分支。 r7 用于函数体(其中r0-r3 存储参数),因此需要将其堆叠以保留值。
  • “或者我在这里遗漏了什么?” - 函数序言将调用者的帧指针保存在入口处,而后记将其恢复为返回的一部分,与几乎任何 ABI 变体;我看不出有什么不寻常的地方......

标签: arm calling-convention thumb


【解决方案1】:

首先,他们可能希望堆栈在 64 位边界上对齐。

R7 比任何更大的帧指针都要好,因为大多数指令不支持寄存器 r8 到 r15。我必须看看我会假设有特殊的 pc 和 sp 偏移加载/存储指令,那么为什么 r7 会被烧掉呢?

不确定你在问什么,拇指你可以推 lr 但pop pc,我认为这相当于 bx lr,但你必须为每个架构查找它,因为有些你不能用 pop 切换模式。在这种情况下,它似乎假设并没有用 pop r3 bx r3 之类的东西烧掉额外的指令。实际上,要做到这一点,可能需要两条额外的指令 pop r7、pop r3、bx r3。

因此,一个编译器可能会被告知正在使用什么架构,并且可以假设 pop pc 是安全的,而另一个编译器则不太确定。再次必须阅读各种架构的 arm 架构文档,以了解哪些指令可用于更改模式和哪些指令的变化。也许如果您使用 gnu 浏览各种架构类型,它可能会改变它的返回方式。

编辑

unsigned int morefun ( unsigned int, unsigned int );
unsigned int fun ( unsigned int x, unsigned int y )
{
    x+=1;
    return(morefun(x,y+2)+3);
}
arm-none-eabi-gcc -O2 -mthumb -c so.c -o so.o
arm-none-eabi-objdump -D so.o 
00000000 <fun>:
   0:   b510        push    {r4, lr}
   2:   3102        adds    r1, #2
   4:   3001        adds    r0, #1
   6:   f7ff fffe   bl  0 <morefun>
   a:   3003        adds    r0, #3
   c:   bc10        pop {r4}
   e:   bc02        pop {r1}
  10:   4708        bx  r1
  12:   46c0        nop         ; (mov r8, r8)

arm-none-eabi-gcc -O2 -mthumb -mcpu=cortex-m3 -march=armv7-m -c so.c -o so.o
arm-none-eabi-objdump -D so.o 
00000000 <fun>:
   0:   b508        push    {r3, lr}
   2:   3102        adds    r1, #2
   4:   3001        adds    r0, #1
   6:   f7ff fffe   bl  0 <morefun>
   a:   3003        adds    r0, #3
   c:   bd08        pop {r3, pc}
   e:   bf00        nop

仅在没有 mcpu 的情况下使用该行军会产生相同的结果(不会将 lr 弹出到 r1 到 bx)。

march=armv5t 稍作改动

00000000 <fun>:
   0:   b510        push    {r4, lr}
   2:   3102        adds    r1, #2
   4:   3001        adds    r0, #1
   6:   f7ff fffe   bl  0 <morefun>
   a:   3003        adds    r0, #3
   c:   bd10        pop {r4, pc}
   e:   46c0        nop         ; (mov r8, r8)

armv4t 像预期的那样做 pop 和 bx 的事情。

armv6-m 给出了 armv5t 给出的内容。

gcc 版本 6.1.0 使用 --target=arm-none-eabi 构建,没有任何其他 arm 说明符。

很可能当 OP 询问我是否理解正确时,他们可能会看到三个指令 pop pop bx 而不是单个 pop {rx,pc}。或者至少一个编译器与另一个编译器相比有所不同。提到了 Apple IOS,因此它可能默认为比在任何地方都可以工作的东西更重的核心。他们的 gcc 和我的一样默认在任何地方都可以工作(包括原始的 ARMv4T),而不是在任何地方都可以工作,除了原始的。我假设如果你添加一些命令行选项,你会看到 gcc 编译器的行为与我演示的不同。

注意在这些例子中 r3 和 r4 没有被使用,那为什么还要保留它们呢?这可能是我提到的在堆栈上保持 64 位对齐的第一件事。如果对于所有拇指变体解决方案,如果您在弹出之间获得中断,则中断处理程序正在处理未对齐的堆栈。由于 r4 无论如何都是一次性的,他们可以分别弹出 r1 和 r2 或 r2 和 r3 然后 bx r2 或 bx r3 并且没有那个未对齐并保存指令的时刻。哦,好吧……

【讨论】:

  • 进入 PC 是在 ARMv5T 及更高版本上互通,此时这实际上意味着“任何不是 ARM7TDMI 的东西”,因为这是唯一仍然存在于 v5 之前的东西现代套件。
  • 明白了,但这仍然取决于编译器被告知要编译什么,泛型在任何地方都可以工作(包括 armv4t),或者不能在任何地方都工作。
  • 查看编辑,当然取决于首先构建 gcc 时的选项,但是通用 -mthumb 似乎可以在任何地方工作,所有拇指变体,解决方案,如果您指定拱门,那么它知道它可以弹出到 pc 中,而不必对 bx 执行两/三步。
  • 诚然,ARM7TDMI 仍在微控制器世界中流行,这意味着它可能是大量裸机编译器的基准默认目标。
  • 对于那些基于 ARM7TDMI 的微控制器,他们可能希望运行拇指模式以获取尺寸。愿意打赌,另一个简单的实验,以这种方式编译的 gcc 但 arm 模式指令仍然构建所有 arm 变体类型指令。那里有无数的 gcc 构建器和预构建器,所以你通常不能说没有 mcpu 或 March 你会得到 ARMv4T,但你可以说没有 mcpu/march 你得到你得到的,这可能会帮助或伤害你取决于。
猜你喜欢
  • 2021-04-13
  • 2011-07-14
  • 2010-09-20
  • 1970-01-01
  • 2014-10-21
  • 2012-10-22
  • 2021-03-03
  • 1970-01-01
相关资源
最近更新 更多