ARM64 Instructions
ARM instructions can be either uppercase or lowercase, and typically this is controlled by the compiler's linker. However, there can never be a mixture of upper/lower case or camelCase!
Branch Instructions
Operator | Example Call | Description |
---|---|---|
B | B label | Branch to address at label |
BEQ | BEQ X0, X4, label | Branch if equal |
BGT | BGT X0, X4, label | Branch if greater than |
BGE | BGE X0, X4, label | Branch if greater than or equal |
BLE | BLE X0, X4, label | Branch if less than or equal |
BLT | BLT X0, X4, label | Branch if less than |
BNE | BNE X0, X4, label | Branch if not equal |
BL | BL label | Branch and Link; jumps to label - writes address of next instruction to X30 (link register) |
BLR | BLR X1 | Branch and Link Register; jumps to address in X1 & writes address of next instruction to X30 (link register) |
BR | BR X1 | Branch Register; unconditional jump to address in X1 |
RET | RET {X1} | Return; jumps to register X1 address |
CBNZ | CBNZ X0, label | Compare and Branch if Not Zero |
CBZ | CBZ X0, label | Compare and Branch if Zero |
TBNZ | TBNZ X0, label | Test and Branch if Not Zero |
TBZ | TBZ X0, label | Test and Branch if Zero |
Branch Conditions
Condition | Description | Example |
---|---|---|
eq | Equal | If x0 = x1 |
ne | Not Equal | If x0 ≠ x1 |
lt | Less Than | If x0 < x1 |
le | Less Than or Equal | If x0 ≤ x1 |
gt | Greater Than | If x0 > x1 |
ge | Greater Than or Equal | If x0 ≥ x2 |
Memory Access Instructions
Operator | Example Call | Description |
---|---|---|
LDR | LDR X0, addr | Load register; loads a word/doubleword from memory addressed by addr |
LDR | LDR X0, label | Load register; loads from memory addressed by label |
STR | STR X0, addr | Store register; stores word/doubleword from memory addressed by addr |
LDP | LDP X1, X2, addr | Load register pair; loads two word/doubleword values from memory addressed by addr to X1 and X2 |
STP | STP X1, X2, addr | Store register pair; stores two word/doubleword from memory addressed by addr |
Basic Integer Instructions
Operator | Example Call | Description |
---|---|---|
ADD | ADD x0, x1, x2 | Add x1 to x2 , and store the result in x0 |
ADD | ADD X0, X1, #0x0 | Add the value “0” to the value of X1 and store in X0 |
SUB | SUB x0, x1, x2 | Subtract x1 from x2 , and store the result in x0 |
SUB | SUB X0, X1, #0x5 | Subtract “5” from the value of X1 and store in X0 |
MUL | MUL x1, x1, x2 | Multiply x1 by x2 , and store the result in x0 |
SDIV | SDIV x0, x1, x2 | Signed division: Divide x1 by x2 , and store in x0 |
UDIV | UDIV x0, x1, x2 | Unsigned division: Divide x1 by x2 , and store in x0 |
CMP | CMP X0, #0x0 | Compare “0” to X0 |
MOV | MOV X0, X1 | Move the value of X1 to X0 |
The stack is a critical structure in computer memory management, particularly for executing programs and managing function calls, local variables, and control flow. In the context of ARM64 architecture, understanding how the stack works is essential for both development and security analysis.
When an ARM64 application starts, the operating system allocates a block of contiguous memory for the stack. The size of this block can vary depending on the operating system and the application's requirements. The stack pointer (SP) register points to the top of the stack; in ARM64, this is typically the sp register.
The stack is generated with an initial size determined by the linker settings and can be adjusted by the operating system or the application itself at runtime. When a new thread is created, it also gets its own stack, separate from other threads, ensuring that each thread's execution context is isolated.
The ARM64 stack grows downwards, meaning that as data is pushed onto the stack, the stack pointer (SP) decreases. This is opposite to how data grows in memory (upwards), where higher addresses are used for dynamically allocated memory (heap).
When a function is called in ARM64:
-
Preparation: The caller prepares arguments for the callee, according to the ARM64 calling convention, which may involve passing arguments in registers or pushing them onto the stack if there are more arguments than available registers.
-
Call Instruction: The BL (Branch with Link) instruction is used to call another function. This instruction saves the return address (the address of the next instruction to execute after the function returns) in the link register (LR), and then branches to the target function's starting address.
-
Prologue: At the beginning of the function, the prologue instructions adjust the stack pointer to allocate space for local variables and save the return address and any callee-saved registers onto the stack.
-
Function Body Execution: The function executes its instructions, using the stack space allocated for its local variables as needed.
-
Epilogue: Before returning, the function restores any saved registers and the return address from the stack, adjusts the stack pointer back to its original position, and executes the RET instruction to return to the caller, using the address in the link register (LR).
When a function completes its execution, the stack space it used must be cleaned up to prevent leaks and maintain the integrity of the program's execution:
- Local Variable Cleanup: Local variables stored on the stack are not explicitly destroyed by ARM64 instructions. Instead, their space is simply reclaimed by adjusting the stack pointer back up. It's the programmer's responsibility to ensure that any sensitive information is wiped from the stack if necessary.
- Stack Pointer Restoration: The function's epilogue instructions will move the stack pointer (sp) back to its position before the function call, effectively "destroying" the function's stack frame and reclaiming the space.
- Thread Termination: When a thread finishes execution, the operating system reclaims the stack memory allocated for that thread.
- Application Exit: Upon application termination, the operating system reclaims all memory resources used by the application, including the stack memory.