R7和R11与ARM体系结构(拇指/臂)调用约定中的链接寄存器的关系

时间:2016-08-04 13:06:59

标签: arm calling-convention thumb

我正在查看由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到PC使用bx LR,而是为什么?

1 个答案:

答案 0 :(得分:3)

首先,他们可能希望保持堆栈在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给出的内容。

使用--target = arm-none-eabi构建的gcc版本6.1.0,没有任何其他的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,并且没有那个未对齐并保存指令的时刻。哦,好吧......