archiveFeb 10, 2021
ARM: Loading Addresses into Registers
ADR/ADRL vs LDR Rd,=label for address materialization, jump-table patterns, and string-copy examples in ARM and Thumb.
Loading an address into a register
- You often need an address in a register: a variable, a string constant, the start of a jump table, and so on.
- Addresses are usually expressed as an offset from the current PC or from another register.
- Direct load with ADR / ADRL, or load the address from a literal pool with
LDR Rd, =label.
Direct loading with ADR and ADRL
- ADR and ADRL are pseudo-instructions that build an address within range without a data load.
- A program-relative expression: a label (optional offset) whose address is relative to the current PC.
- A register-relative expression: a label (optional offset) relative to an address already in a general-purpose register.
Assembler expansion of ADR rn, label
- If the address is in range: a single ADD or SUB that builds it.
- If one instruction cannot reach it: an error.
- Offset range is ±255 bytes for non-word-aligned addresses,
- and ±1020 bytes (255 words) for word-aligned addresses.
- In Thumb the address must be word-aligned and the offset must be positive.Assembler expansion of ADRL rn, label
- If the address is in range: two data-processing instructions that build it.
- If two instructions still cannot form it: an error.
- ADRL range is ±64KB for non-word-aligned addresses,
- and ±256KB for word-aligned addresses. (No Thumb ADRL; ARM code only.)- On success ADRL always assembles to two instructions.
- The assembler emits both even when a single instruction could have done the job.Labels used with ADR / ADRL must be in the same code section
- Assembler error: label in the same section but out of range.
- Linker error: label in a different code section and out of range.
- In Thumb, ADR can only produce word-aligned addresses.
- ADRL is unavailable in Thumb; use it in ARM code only.
Exercise 1
- Code shapes the assembler emits when assembling ADR / ADRL.
AREA adrlabel, CODE,READONLY
ENTRY ; first ENTRY
Start
BL func ; branch to subroutine (func)
stop MOV r0, #0x18 ; angel_SWIreason_ReportException
LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit
SWI 0x123456 ; ARM semihosting SWI
LTORG ; emit literals
func ADR r0, Start ; => SUB r0, PC, #offset to Start
ADR r1, DataArea ; => ADD r1, PC, #offset to DataArea
; ADR r2, DataArea+4300 ; fails: offset not representable as ADD operand2
ADRL r2, DataArea+4300 ; => ADD r2, PC, #offset1
; ADD r2, r2, #offset2
MOV pc, lr ; Return
DataArea SPACE 8000 ; Starting at the current location,
; clears a 8000 byte area of memory
; to zero
ENDJump tables with ADR
- Exercise 2 shows ARM code for a jump table.
- ADR loads the table address.
arithfunctakes three arguments and returns in r0.- The first argument selects the operation on the second and third:
arg1=0
Result = arg2 + arg3
arg1=1
Result = arg2 - arg3- The table is built with the following instructions and directives.
EQU
- Assembler directive that gives a symbol a value.
- Here
numgets 2; later uses ofnumsubstitute 2. - Same idea as
#definefor constants in C.
DCD
- Declares one or more stored words.
- Each DCD holds the address of the routine that handles one table entry.
LDR
LDR pc, [r3, r0, LSL #2]loads the chosen table entry into pc.- Multiplying the index in r0 by 4 yields a word offset.
- That offset is added to the table base; the word at the combined address becomes the new PC.
AREA Jump, CODE, READONLY
CODE32
num EQU 2 ; number of jump-table entries
ENTRY
start
MOV r0, #0
MOV r1, #3
MOV r2, #2
BL arithfunc
stop MOV r0, #0x18 ; angel_SWIreason_ReportException
LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit
SWI 0x123456 ; ARM semihosting SWI
arithfunc ; Label the function
CMP r0, #num ; treat function code as unsigned
MOVHS pc, lr ; If code is >= num then simply return
ADR r3, JumpTable ; load JumpTable into r3
LDR pc, [r3,r0,LSL#2] ; jump to the matching routine
JumpTable
DCD DoAdd
DCD DoSub
DoAdd ADD r0, r1, r2 ; Operation 0
MOV pc, lr ; Return
DoSub SUB r0, r1, r2 ; Operation 1
MOV pc, lr ; Return
END ; Mark the end of this fileConverting to Thumb
- Same jump-table idea in Thumb.
- Thumb cannot:
- Auto-increment the base register of LDR/STR the same way
- Load a value into PC with LDR in this pattern
- Apply an inline shift from a register on the LDR
AREA Jump, CODE, READONLY
CODE16
num EQU 2
ENTRY
start
MOV r0, #0
MOV r1, #3
MOV r2, #2
BL arithfunc
stop MOV r0, #0x18
LDR r1, =0x20026
SWI 0xAB ; Thumb semihosting SWI
arithfunc
CMP r0, #num
BHS exit ; MOV pc, lr cannot be conditional
ADR r3, JumpTable
LSL r0, r0, #2 ; 3 instructions needed to replace
LDR r0, [r3,r0] ; LDR pc, [r3,r0,LSL#2]
MOV pc, r0
ALIGN ; Ensure that the table is aligned on a
; 4-byte boundary
JumpTable
DCD DoAdd
DCD DoSub
DoAdd ADD r0, r1, r2
exit MOV pc, lr
DoSub SUB r0, r1, r2
MOV pc, lr
ENDLoading addresses with LDR Rd, =label
-
The
LDR Rd, =pseudo-instruction can load a 32-bit constant into a register. -
It also accepts program-relative expressions such as labels and labels-with-offsets.
-
It places the label address in a literal pool (a patch of memory in the code that holds constants).
-
Then it emits a PC-relative LDR that reads that pool entry.
LDR rn [pc, #offset to literal pool] ; load register n with one word
; from the address [pc + offset]-
Unlike ADR/ADRL, LDR can target labels outside the current section.
-
If the label is elsewhere, the assembler plants a relocation directive in the object code.
-
The linker resolves the address at link time.
-
Wherever the linker places the section that holds the LDR and its literal pool, the address stays valid.
-
Exercise 3
AREA LDRlabel, CODE,READONLY
ENTRY
start
BL func1
BL func2
stop MOV r0, #0x18 ; angel_SWIreason_ReportException
LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit
SWI 0x123456 ; ARM semihosting SWI
func1
LDR r0, =start ; => LDR R0,[PC, offset to literal pool 1]
LDR r1, =Darea + 12 ; => LDR R1,[PC, offset to literal pool 1]
LDR r2, =Darea + 6000 ; => LDR R2, [PC, offset to literal pool 1]
MOV pc,lr ; Return
LTORG ; Literal Pool 1
func2
LDR r3, =Darea + 6000 ; => LDR r3, [PC, offset to literal pool 1]
; shares the earlier literal
; LDR r4, =Darea + 6004 ; uncommenting errors:
; literal pool 2 is out of range
MOV pc, lr ; Return
Darea SPACE 8000 ; start at current location
; clear 8000 bytes
END ; literal pool 2 is out of range
; the LDR instructions aboveString-copy exercise with LDR Rd, =label
- ARM routine that overwrites one string with another.
- LDR pseudo-instructions load both string addresses from the data section.
DCB
- Defines one or more stored bytes.
- Besides integers, DCB accepts quoted strings; each character lands in a successive byte.
LDR / STR
- LDR and STR here use post-indexed addressing to bump the address registers.
LDRB r2,[r1],#1- Load r2 from the address in r1, then add 1 to r1.
AREA strCopy, CODE, READONLY
ENTRY
start LDR r1, =srcstr ; pointer to first string
LDR r0, =dststr ; pointer to second string
BL strCopy ; call copy subroutine
stop MOV r0, #0x18 ; angel_SWIreason_ReportException
LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit
SWI 0x123456 ; ARM semihosting SWI
strCopy
LDRB r2, [r1],#1 ; load byte and update address
STRB r2, [r0],#1 ; store byte and update address
CMP r2, #0 ; Check for zero terminator
BNE strCopy ; Keep going if not
MOV pc,lr ; Return
AREA Strings, DATA, READWRITE
srcstr DCB "First string - source",0
dststr DCB "Second string - destination",0
ENDThumb conversion
- Thumb LDR/STR have no post-indexed addressing mode.
- After each LDR/STR, bump the address with ADD.
LDRB r2, [r1] ; load register 2
ADD r1, #1 ; increment the address in
; register 1.