5. Memory Access & Addressing Modes
Immediate, Register, and Offset Addressing
ARM provides several ways to access memory. These addressing modes are used to specify how the memory address for a load/store instruction is computed.
Addressing Mode | Description | Example |
---|---|---|
Immediate Addressing | The memory address is determined by an immediate value. This is often used for loading constants or small values directly. | LDR R0, =0x1000 (Load the address 0x1000 into R0) |
Register Addressing | The memory address is computed using the value stored in a register. This is useful when accessing data that is already in a register. | LDR R0, [R1] (Load the value from the memory address in R1 into R0) |
Offset Addressing | The memory address is calculated by adding an offset (either an immediate value or a register value) to the base address stored in a register. | LDR R0, [R1, #4] (Load the value from the address R1 + 4 into R0) |
Immediate Addressing: In this mode, the address is directly encoded as an immediate value. This is useful for small constants or for loading data into a register.
Example:
1
LDR R0, =0x2000 // Load the address 0x2000 into R0
Register Addressing: The address is stored in a register, and the data is loaded from that address. This is the most straightforward form of memory access.
Example:
1
LDR R0, [R1] // Load value from address in R1 into R0
Offset Addressing: The offset (a constant or register) is added to the base address in the register to calculate the memory address.
Example:
1
LDR R0, [R1, #4] ; Load value from address R1 + 4 into R0
Pre-indexing vs. Post-indexing
In ARM, pre-indexing and post-indexing refer to two distinct methods of calculating the memory address and updating the base register. These modes are used primarily in load/store operations, offering flexibility in how memory is accessed and how the base register is updated.
Pre-indexing
- The pre-indexing mode adds the offset to the base register before accessing memory and updates the base register with the new address after the memory access. The
!
symbol at the end of the address expression indicates that the base register is updated after the access.
Example:
1
LDR R0, [R1, #4]! ; Load value from memory at address R1 + 4 into R0, then update R1 to R1 + 4
In this case, the address used for the load is R1 + 4
, and once the memory is accessed, the base register (R1
) is incremented by 4.
Post-indexing
- In post-indexing, the memory address is calculated using the original base register value (before any offset is added). The base register is updated after the memory access, meaning the memory access happens first, and the register is updated afterward.
Example:
1
LDR R0, [R1], #4 ; Load value from address in R1 into R0, then update R1 to R1 + 4
In this case, the load instruction uses the address in R1
to load data into R0
. After the load operation, R1
is updated by adding the offset #4
.
Load/Store Multiple (LDMDB, STMIA)
ARM provides special instructions for transferring multiple registers to and from memory at once. These instructions are highly useful in situations where several registers need to be saved or restored, such as during function calls, interrupt handling, or context switching.
LDMDB (Load Multiple Decrement Before)
- LDMDB is used to load multiple registers from memory. The decrement before mode means that the base address is decremented before each load operation, which is particularly useful when loading multiple values from a stack or restoring saved register states in reverse order.
Example:
1
LDMDB R0!, {R1, R2, R3} // Load values into R1, R2, R3 from memory, decrement R0 before each load
In this case, the values of R1
, R2
, and R3
are stored in memory starting at the address in R0
. After each store, R0
is incremented by the size of the data stored (4 bytes).
STMIA (Store Multiple Increment After)
- STMIA is used to store multiple registers to memory. The increment after mode means that after storing each register’s value, the base address (in the register) is incremented by the size of the data type being stored (usually 4 bytes for ARM).
Example:
1
STMIA R0!, {R1, R2, R3} // Store values in R1, R2, R3 to memory starting at R0, increment R0 after each store
In this case, the values of R1
, R2
, and R3
are stored in memory starting at the address in R0
. After each store, R0
is incremented by the size of the data stored (4 bytes).
Example:
1
2
3
4
LDR r0, =0xABCD ; Load the address 0xABCD into register r0
LDR r1, [r0] ; Load the value at the address in r0 into r1
STR r1, [r0] ; Store the value in r1 to the address in r0
1
2
3
LDMIA r2!, {r3-r6} ; Load multiple registers from memory starting at address in r2
STMIA r2!, {r3-r6} ; Store multiple registers to memory starting at address in r2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
.section .data
source_array:
.word 0x00000001, 0x00000002, 0x00000003, 0x00000004 // Source data (4 words)
destination_array:
.space 16 // Space for 4 words (4 * 4 bytes)
.section .text
.global _start
_start:
LDR r0, =source_array // Load address of source_array into r0
LDR r1, =destination_array // Load address of destination_array into r1
// Load multiple registers from source_array into r2-r5
LDMIA r0!, {r2-r5} // Load values from source_array into r2, r3, r4, r5; increment r0
// Modify the loaded values (for example, increment each by 1)
ADD r2, r2, #1 // Increment value in r2
ADD r3, r3, #1 // Increment value in r3
ADD r4, r4, #1 // Increment value in r4
ADD r5, r5, #1 // Increment value in r5
// Store the modified values back to destination_array
STMIA r1!, {r2-r5} // Store values from r2-r5 into destination_array; increment r1
// Exit the program using sys_exit
MOV r0, #0 // Set exit code to 0 (success)
MOV r7, #1 // Load syscall number for sys_exit into r7
SWI 0 // Make the syscall to exit