ARM64 Assembly — Introduction to AArch64 Architecture
In this tutorial, you will learn about ARM64 Assembly. We cover key concepts, practical examples, and best practices to help you master this topic.
ARM64 (AArch64) is a RISC architecture with 31 general-purpose registers, fixed 4-byte instructions, and load-store memory architecture dominating mobile and Embedded Systems.
What You'll Learn
- ARM64 vs x86-64 differences
- Register set and calling convention
- Instruction format
- Toolchain for ARM64
Why It Matters
ARM powers most smartphones, tablets, IoT devices, and increasingly laptops (Apple Silicon, Windows ARM). Doda Browser has ARM64-native builds for Android and iOS devices.
Real-World Use
Mobile app development, embedded systems, IoT firmware, Apple Silicon Mac applications, and server ARM CPUs (AWS Graviton, Ampere).
flowchart LR
A["ARM64"] --> B["RISC Design"]
A --> C["31 Registers"]
A --> D["Load-Store"]
B --> E["Simple Instructions"]
C --> F["X0-X30"]
D --> G["ldr/str only"]
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#dbeafe,stroke:#2563eb,color:#1e40af
ARM64 Register Set
General-purpose registers: X0-X30 (64-bit)
W0-W30: lower 32 bits of X0-X30
Special registers:
SP: Stack Pointer
X30 (LR): Link Register (return address)
X29 (FP): Frame Pointer
XZR: Zero Register (reads as 0)
Vector registers: V0-V31 (128-bit NEON/SIMD)
Your First ARM64 Program
// hello.s
.global _start
.section .data
msg: .ascii "Hello, ARM!\n"
len = . - msg
.section .text
_start:
// write(1, msg, len)
mov x0, #1 // fd = stdout
adr x1, msg // buffer
mov x2, #len // length
mov x8, #64 // syscall: write
svc #0
// exit(0)
mov x0, #0
mov x8, #93 // syscall: exit
svc #0
Assemble: aarch64-linux-gnu-as hello.s -o hello.o && aarch64-linux-gnu-ld hello.o -o hello
Key Differences from x86-64
| Aspect | x86-64 | ARM64 |
|---|---|---|
| Architecture | CISC | RISC |
| Instruction size | 1-15 bytes | Fixed 4 bytes |
| Registers | 16 GP | 31 GP |
| Memory access | Many instructions | Only ldr/str |
| Conditional execution | Via flags + jcc | Conditional instructions |
| Return address | On stack | In X30 (LR) |
| Push/pop | push/pop instructions | stp/ldp pair |
ARM64 Calling Convention
X0-X7: Argument registers (integer/pointer)
X0: Return value
X8: Indirect result location
X9-X15: Caller-saved temporaries
X19-X28: Callee-saved registers
X29 (FP): Frame pointer
X30 (LR): Link register (return address)
SP: Stack pointer
Data Movement
// Moving values
mov x0, #42 // x0 = 42
mov x1, x2 // x1 = x2 (register copy)
mvn x0, x1 // x0 = ~x1 (bitwise NOT)
// Moving with sign/zero extension
mov w0, #-1
sxtw x1, w0 // sign extend w0 to x1
// Loading addresses
adr x0, label // PC-relative address
adrp x0, label // page-aligned address (4KB)
add x0, x0, :lo12:label // add lower 12 bits
Memory Access
// Load/store (only way to access memory)
ldr x0, [x1] // x0 = *x1 (64-bit load)
ldr w0, [x1] // w0 = *(int*)x1 (32-bit)
ldrb w0, [x1] // w0 = *(byte*)x1
ldrh w0, [x1] // w0 = *(short*)x1
ldrsb x0, [x1] // sign-extending byte load
str x0, [x1] // *x1 = x0
strb w0, [x1] // *(byte*)x1 = w0
// With offset
ldr x0, [x1, #16] // x0 = *(x1 + 16)
ldr x0, [x1, x2] // x0 = *(x1 + x2)
ldr x0, [x1, x2, lsl #3] // x0 = *(x1 + x2 * 8)
// Pre/post-index
ldr x0, [x1, #8]! // x1 += 8, then x0 = *x1
ldr x0, [x1], #8 // x0 = *x1, then x1 += 8
Stack Operations
// ARM64 has no push/pop instructions
// Use store/load pair instead
// Push two registers
stp x29, x30, [sp, #-16]! // save fp,lr, decrement sp
// Pop two registers
ldp x29, x30, [sp], #16 // restore fp,lr, increment sp
// Allocate stack space
sub sp, sp, #64 // 64 bytes of locals
// Deallocate
add sp, sp, #64
Common Mistakes
1. Forgetting ARM has no push/pop
Use stp/ldp for register pairs. Single register pushes use str with pre-decrement.
2. Mistaking W and X registers
W registers access lower 32 bits and zero-extend to 64 bits. X registers access all 64 bits.
3. x86-style memory operands
ARM64 cannot do arithmetic directly on memory. Must load, operate, store.
4. Wrong syscall numbers
ARM64 Linux syscall numbers differ from x86-64. Check /usr/include/aarch64-linux-gnu/asm/unistd.h.
5. Not using X30 for return address
ret uses X30 (not stack). Save X30 on stack before bl (branch with link) if the function calls others.
Practice Questions
1. How many general-purpose registers does ARM64 have?
31 (X0-X30), plus SP and XZR (zero register).
2. Why is ARM called a load-store architecture?
Only ldr and str instructions access memory. All other operations work on registers.
3. What register holds the return address after a bl instruction?
X30 (Link Register). bl func stores the return address in X30.
4. How do you push/pop registers without push/pop instructions?
Use stp reg1, reg2, [sp, #-16]! (push pair) and ldp reg1, reg2, [sp], #16 (pop pair).
Challenge: Write an ARM64 program that computes the sum of numbers 1 to 100.
Solution
.global _start
.section .text
_start:
mov x0, #0 // sum = 0
mov x1, #1 // counter = 1
loop:
add x0, x0, x1 // sum += counter
add x1, x1, #1 // counter++
cmp x1, #100
ble loop
// exit with sum
mov x8, #93
svc #0
FAQ
{{< faq question="Can I run ARM64 code on x86-64?" >}}
Yes, using QEMU user-mode emulation: qemu-aarch64 ./program. Also through cross-compilation toolchains.
{{< /faq >}}
{{< faq question="Is ARM64 easier than x86-64?" >}} Many find ARM64 simpler due to RISC design, fixed instruction size, and fewer instruction forms. The tradeoff is more instructions per task. {{< /faq >}}
{{< faq question="What is Apple Silicon?" >}} Apple's M1/M2/M3/M4 chips use ARM64 architecture. They run macOS, iOS, and iPadOS with excellent performance per watt. {{< /faq >}}
{{< faq question="What toolchain do I need for ARM64 assembly?" >}}
The aarch64-linux-gnu-* cross-toolchain: aarch64-linux-gnu-as (assembler) and aarch64-linux-gnu-ld (linker).
{{< /faq >}}
{{< faq question="Does ARM64 support floating-point in hardware?" >}} Yes, all ARM64 CPUs include hardware floating-point (FPU) and NEON SIMD for both single and double precision. {{< /faq >}}
Mini Project
Write an ARM64 program that prints the first 20 Fibonacci numbers using a loop.
.global _start
.section .data
.section .text
_start:
mov x0, #0 // first
mov x1, #1 // second
mov x2, #20 // count
loop:
// print or store x0 here
add x3, x0, x1 // next = first + second
mov x0, x1
mov x1, x3
sub x2, x2, #1
cmp x2, #0
bgt loop
mov x8, #93
svc #0
What's Next
Now that you understand ARM64 basics, proceed to ARM64 instructions.
| Topic | Description | Link |
|---|---|---|
| ARM instructions | ARM64 instruction set | {{< ref "22-arm-instructions" >}} |
| ARM memory | ARM64 memory access | {{< ref "23-arm-memory" >}} |
| x86-64 | Compare with x86-64 | {{< ref "02-x86-64" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro