ARM LDR literal and pseudo-instruction demos
In the previous article, we've combed AMR LDR
PC-relative and Pseudo instruction to load a value or address to a register.
Here we collect some enlightening demos from some classic textbooks to consolidate knowledge that is not solid.
armasm User Guide#
Arm Compiler armasm User Guide | 7. Writing A32/T32 Assembly Language
- 7.7 Load immediate values using LDR Rd, =const
- 7.12 Load addresses to a register using LDR Rd, =label
LDR Rd,=const#
The LDR Rd,=const
pseudo-instruction generates the most efficient single instruction to load any 32-bit number.
You can use this pseudo-instruction to generate constants that are out of range of the MOV
and MVN
instructions.
The LDR
pseudo-instruction generates the most efficient single instruction for the specified immediate value:
- If the immediate value can be constructed with a single
MOV
orMVN
instruction, the assembler generates the appropriate instruction. -
If the immediate value cannot be constructed with a single
MOV
orMVN
instruction, the assembler:- Places the value in a literal pool (a portion of memory embedded in the code to hold constant values).
- Generates an
LDR
instruction with a PC-relative address that reads the constant from the literal pool.
For example:
LDR rn, [pc, #offset to literal pool]
; load register n with one word
; from the address [pc + offset]
You must ensure that there is a literal pool within range of the LDR
instruction generated by the assembler.
stack pointer initialized
LDR Rd,=label#
The LDR Rd,=label
pseudo-instruction places an address in a literal pool and then loads the address into a register.
The assembler converts an LDR Rd,=label
pseudo-instruction by:
- Placing the address of label in a literal pool (a portion of memory embedded in the code to hold constant values).
- Generating a PC-relative
LDR
instruction that reads the address from the literal pool, for example:
You must ensure that the literal pool is within range of the LDR
pseudo-instruction that needs to access it.
Example of loading using LDR Rd, =label
The following example shows a section with two literal pools. The final LDR
pseudo-instruction needs to access the second literal pool, but it is out of range. Uncommenting this line causes the assembler to generate an error.
The instructions listed in the comments are the A32 instructions generated by the assembler.
-
By default, a literal pool is placed at every END directive.
- Directive
LTORG
forces the assembler to build literal pool 1 between the two subroutines; - Literal Pool 2 is automatically inserted after the
END
directive;
- Directive
-
the first
LDR r3
infunc2
could use Literal Pool 1 to create a PC-relative offset, whereas the secondLDR r4
couldn't share Literal Pool 1, it would place in Literal Pool 2. -
distance between
LDR r4
and Literal Pool 2 is farther than 8000, overflow 4095.
Example of string copy
The example also shows how, unlike the ADR
and ADRL
pseudo-instructions, you can use the LDR
pseudo-instruction with labels that are outside the current section. The assembler places a relocation directive in the object code when the source file is assembled. The relocation directive instructs the linker to resolve the address at link time. The address remains valid wherever the linker places the section containing the LDR
and the literal pool.
classic textbooks#
Pseudo load#
ARM 64-Bit Assembly Language | 3 Load/store and branch instructions - 3.3 Instruction components - 3.3.3 Addressing modes
The pseudo addressing mode allows an immediate data value or the address of a label to be loaded into a register, and may result in the assembler generating more than one instruction.
This is a pseudo-instruction. The assembler will generate a mov
instruction if possible. Otherwise it will store the value of immediate or the address of symbol in a “literal pool”, or “literal table”, and generate a load instruction, using one of the previous addressing modes, to load the value into a register. This addressing mode can only be used with the ldr instruction.
An example pseudo-instruction and its disassembly are shown in Listing 3.1 and Listing 3.2.
Listing 3.1 LDR pseudo-instruction.
Listing 3.2 Disassembly of LDR pseudo-instruction
// little endian
0: 58000040 ldr x0, 8 <.text+0x8>
4: d65f03c0 ret
8: 9abcdef0 .word 0x9abcdef0
c: 12345678 .word 0x12345678
PC Relative Addressing#
Programming with 64-Bit ARM Assembly Language | Chapter 5: Thanks for the Memories - Loading a Register with an Address - PC Relative Addressing
PC relative addressing has one more trick up its sleeve; it gives us a way to load any 64-bit quantity into a register in only one instruction, for example, consider
This assembles into
The GNU Assembler is helping us out by putting the constant we want into memory, then creating a PC relative instruction to load it.
For PC relative addressing, it really becomes addressing relative to the current instruction. In the preceding example, “ldr X1, #8
” means 8 words(instructions) from the current instruction.
In fact, this is how the Assembler handles all data labels. When we specified
the Assembler did the same thing; it created the address of the hellostring in memory and then loaded the contents of that memory location, not the helloworld string.
These constants the Assembler creates are placed at the end of the .text
section which is where the Assembly instructions go, not in the .data
section. This makes them read-only in normal circumstances, so they can't be modified. Any data that you want to modify should go in a .data
section.
Literal (PC-Relative) Addressing#
Arm Assembly Internals and Reverse Engineering | Chapter 6 Memory Access Instructions - Addressing Modes and Offset Forms - Literal (PC-Relative) Addressing - Loading Constants
LDR
can also load a constant value or the address of a label using the specialized syntax LDR Rn,=value
. This syntax is also useful for cases when you write assembly and a constant cannot be directly encoded into a MOV
instruction.
; A32
_start:
ldr r0, =0x55555555 // Set r0 to 0x55555555
ldr r1, =_start // Set r1 to address of _start
; A64
_start:
ldr x1, =0xaabbccdd99887766 // Set x1 to 0xaabbccdd99887766
ldr x2, =_start // Set x2 to address of _start
This syntax is a directive to the assembler to place the constant in a nearby literal pool and to translate the instruction into a PC-relative load of this constant at runtime, as you can see in this disassembly output:
Disassembly of section .text:
0000000000400078 <_start>:
400078: 58000041 ldr X1, 400080 <_start+0x8>
40007C: 58000062 ldr x2, 400088 <_start+0x10>
400080: 99887766 .word 0х99887766 // <literal pool>
400084: aabbccdd .word 0xaabbccdd
400088: 00400078 .word 0х00400078
40008C: 00000000 .word 0x00000000 // </literal pool>
The assembler groups and deduplicates the constants in the literal pool and writes them at the end of the section, or “spills” them explicitly when it encounters an LTORG
directive in the assembly file.
Literal pools cannot be placed anywhere in memory; they must be close to the instruction using it. How close, and the direction, depends on the instruction and architecture using it, given in Table 6.15.
Table 6.15: LDR Literal Pool Locality Requirements
INSTRUCTION SET | INSTRUCTION | LITERAL POOL LOCALITY REQUIREMENT |
---|---|---|
A32 | LDR | PC ± 4KB |
T32 | LDR.W | PC ± 4KB |
idem | LDR (16-bit) | Within 1KB strictly forwards from PC |
A64 | LDR | PC ± 1MB |
By default, an assembler will try to rewrite literal loads into an equivalent MOV
or MVN
instruction. A PC-relative LDR
instruction will be used only if this is not possible.
LDR load address and value#
Modern Arm Assembly Language Programming: Covers Armv8-A 32-bit, 64-bit, and SIMD | Chapter 11: Armv8-64 Core Programming – Part 1 - Integer Operations - Load and Store Instructions
Listing 11-4 shows the source code for example Ch11_04, which explains how to use the LDR
(load register) instruction. It also illustrates the use of several Armv8-64 memory addressing modes.
Ch11_04.cpp + Ch11_04.s
The assembly language function TestLDR1_ begins its execution with a ldr x2,=A1
that loads the address array A1 into register X2
. Recall from the discussions in Chapter 2 that this form of the LDR
instruction is a pseudo instruction. The assembler replaces the ldr x2,=A2
instruction with a ldr x2,offset
instruction that loads the address of array A1 from a literal pool. Figure 11-1 illustrates this in greater detail. This figure contains output from the GNU debugger (with minor edits to improve readability) that shows the machine code for the TestLDRx_ functions. Note that the GNU debugger output displays runtime addresses for the LDR
pseudo instructions instead of the offsets that are embedded in the instruction encodings.
Function TestLDR3_ uses an adr x2,A3
(form PC
relative address) instruction to load the address of array A3 into register X2. An adr instruction can be used here since array A3 is defined in the same .text
section (just before function TestLDR1_) as the executable code. Note that in Figure 11-1, there is no literal pool entry for array A3 since the ADR
instruction uses PC relative offsets instead of literal pools.
Copyright clarification
Copyright belongs to the original author. 🫡
Excerpt/quotation for study only, non-commercial.