Skip to main content

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

OperatorExample CallDescription
BB  labelBranch to address at label
BEQBEQ X0, X4, labelBranch if equal
BGTBGT X0, X4, labelBranch if greater than
BGEBGE X0, X4, labelBranch if greater than or equal
BLEBLE X0, X4, labelBranch if less than or equal
BLTBLT X0, X4, labelBranch if less than
BNEBNE X0, X4, labelBranch if not equal
BLBL labelBranch and Link; jumps to label - writes address of next instruction to X30 (link register)
BLRBLR X1Branch and Link Register; jumps to address in X1 & writes address of next instruction to X30 (link register)
BRBR X1Branch Register; unconditional jump to address in X1
RETRET {X1}Return; jumps to register X1 address
CBNZCBNZ X0, labelCompare and Branch if Not Zero
CBZCBZ X0, labelCompare and Branch if Zero
TBNZTBNZ X0, labelTest and Branch if Not Zero
TBZTBZ X0, labelTest and Branch if Zero

Branch Conditions

ConditionDescriptionExample
eqEqualIf x0 = x1
neNot EqualIf x0 ≠ x1
ltLess ThanIf x0 < x1
leLess Than or EqualIf x0 ≤ x1
gtGreater ThanIf x0 > x1
geGreater Than or EqualIf x0 ≥ x2

Memory Access Instructions

OperatorExample CallDescription
LDRLDR X0, addrLoad register; loads a word/doubleword from memory addressed by addr
LDRLDR X0, labelLoad register; loads from memory addressed by label
STRSTR X0, addrStore register; stores word/doubleword from memory addressed by addr
LDPLDP X1, X2, addrLoad register pair; loads two word/doubleword values from memory addressed by addr to X1 and X2
STPSTP X1, X2, addrStore register pair; stores two word/doubleword from memory addressed by addr

Basic Integer Instructions

OperatorExample CallDescription
ADDADD x0, x1, x2Add x1 to x2, and store the result in x0
ADDADD X0, X1, #0x0Add the value “0” to the value of X1 and store in X0
SUBSUB x0, x1, x2Subtract x1 from x2, and store the result in x0
SUBSUB X0, X1, #0x5Subtract “5” from the value of X1 and store in X0
MULMUL x1, x1, x2Multiply x1 by x2, and store the result in x0
SDIVSDIV x0, x1, x2Signed division: Divide x1 by x2, and store in x0
UDIVUDIV x0, x1, x2Unsigned division: Divide x1 by x2, and store in x0
CMPCMP X0, #0x0Compare “0” to X0
MOVMOV X0, X1Move 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:

  1. 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.
  2. 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.
  3. Thread Termination: When a thread finishes execution, the operating system reclaims the stack memory allocated for that thread.
  4. Application Exit: Upon application termination, the operating system reclaims all memory resources used by the application, including the stack memory.