【发布时间】: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。为此目的,pop和ldm*被记录为互通分支指令。 -
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