Can anyone explain to me why we test the value of the link register here?
The bl check_position
will place the value of PC+4
in the link register and transfer control to the check_position
also PC relative.bl at ARM So far everything is PC
relative.
The ldr r1,=check_position
gets a value from the literal pool.Ref1 The actual code looks like,
ldr r1,[pc, #offset]
...
offset:
.long check_position # absolute address from assemble/link.
So the R0
contains a PC relative version and the R1
contains the absolute assembled version. Here, they are compared. You could also use arithmetic to calculate the difference and then branch to it if non-zero; or possibly copy the code to it's absolute destination.Ref2 If the code is running at the linked address, then R0
and R1
are the same. This is some pseudo code
for bl
.
mov lr,pc ; pc is actually two instruction ahead.
add pc,pc,#branch_offset-8
The key is that BL
does everything based on the PC
including the update of lr
. Instead of using this trick, we could use mov R0,PC
, except the PC
is 8 bytes ahead. Another alternative would be to use adr R0,check_position
, which would get the assembler to do all the address math for us.
/* 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
An ARMv6 version might look like this,
/* 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
In both cases, the code is more straight forward, smaller by one word and doesn't over write the lr
register, so it could be used for other purposes.
Ref1: See Arm op-codes and .ltorg in gnu-assembler manual
Ref2: This is exactly what the Linux head.S
is doing for the ARM.
Edit: I checked the ARM ARM and the PC is apparently the current instruction +8
, which goes to show why the code was like this. I think the adr
version is more direct and readable, but the adr
pseudo-op is not used as often so people may not be familiar with it.