我正在查看由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,而是为什么?
答案 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,并且没有那个未对齐并保存指令的时刻。哦,好吧......