我有一个用汇编编写的裸机ARM的启动代码,我试图了解它是如何工作的。二进制文件写在一些外部闪存中,并在启动时在RAM中复制自身的一部分。尽管我读了这个维基百科条目,但我仍然没有完全理解在这种情况下搬迁的概念。RAM被映射到一个低地址窗口,而闪存被映射到一个高地址窗口。有人能向我解释一下为什么我们在这里测试链接寄存器的值吗?
/* Test if we are running from an address, we are not linked at */
bl check_position
check_position:
mov r0, lr
ldr r1, =check_position
cmp r0, r1 /* ; don't relocate during debug */
beq relocated_entry
我的猜测是应用程序从ram运行,当调试应用程序时,作者可能使用某种引导加载程序或jtag将测试应用程序直接加载到ram中,因此没有理由复制和运行(这可能导致崩溃)。
这样做的另一个原因是为了避免无限循环。例如,如果你想从闪存启动(通常必须),但从ram执行,那么最简单的方法就是将整个闪存或整个闪存块复制到ram,然后分支到ram的开始。当你这样做意味着你击中"复制应用程序到ram和分支"循环再次,以避免它的第二次(这可能会使你崩溃),你有某种我从flash运行这个循环或不测试。
谁能向我解释一下为什么我们在这里测试链接寄存器的值?
bl check_position
将把PC+4
的值放在链路寄存器中,并将控制转移到check_position
,也相对于PC。bl at ARM到目前为止,一切都是相对于PC
的。
ldr r1,=check_position
从字面值池获取一个值。Ref1实际代码看起来像,
ldr r1,[pc, #offset]
...
offset:
.long check_position # absolute address from assemble/link.
因此R0
包含PC相对版本,R1
包含绝对汇编版本。在这里,它们是比较的。你也可以使用算术来计算差值,然后在非零的情况下将分支到;或者可能将代码复制到它的绝对目的地。Ref2如果代码在链接的地址上运行,则R0
和R1
相同。这是bl
的pseudo code
。
mov lr,pc ; pc is actually two instruction ahead.
add pc,pc,#branch_offset-8
关键是BL
基于PC
做所有事情,包括lr
的更新。而不是使用这个技巧,我们可以使用mov R0,PC
,除了PC
在前面8个字节。另一种选择是使用adr R0,check_position
,它会让汇编程序为我们做所有的地址计算。
/* Test if we are running from an address, we are not linked at */
check_position:
adr r0, check_position
ldr r1, =check_position
cmp r0, r1 /* ; don't relocate during debug */
beq relocated_entry
ARMv6版本可能是这样的,
/* Test if we are running from an address, we are not linked at */
check_position:
adr r0, check_position
movw r1, #:lower16:check_position
movt r1, #:upper16:check_position
cmp r0, r1 /* ; don't relocate during debug */
beq relocated_entry
在这两种情况下,代码都更直接,少了一个单词,并且不会覆盖lr
寄存器,因此它可以用于其他目的。
Ref1:参见gnu汇编手册中的Arm操作码和.ltorg
Ref2:这正是Linux head.S
为ARM所做的。
编辑:我检查了ARM和PC显然是当前指令+8
,这表明为什么代码是这样的。我认为adr
版本更直接和可读,但adr
伪op不经常使用,所以人们可能不熟悉它。