Lesson 1 of 28

Hello, ARM64!

Your First ARM64 Program

ARM64 (also called AArch64) is the 64-bit instruction set used by billions of devices worldwide -- from smartphones to Apple's M-series chips to cloud servers. Learning ARM64 assembly gives you a direct understanding of how processors actually execute code.

Why Learn Assembly?

"Space: the final frontier" -- and assembly is the final frontier of programming. Welcome aboard.

Every program you write -- whether in Python, JavaScript, or Rust -- eventually becomes machine instructions. Assembly is the human-readable form of those instructions. Understanding it lets you:

  • Debug performance-critical code at the lowest level
  • Understand what compilers actually generate
  • Write operating system and embedded code
  • Reverse-engineer binaries and analyze security vulnerabilities

Registers

ARM64 has 31 general-purpose 64-bit registers named X0 through X30, plus a few special ones:

RegisterPurpose
X0-X7Function arguments and return values
X8Syscall number
X9-X15Temporary (caller-saved)
X16-X18Platform reserved
X19-X28Callee-saved
X29 (FP)Frame pointer
X30 (LR)Link register (return address)
SPStack pointer
XZRZero register (always reads as 0)

Tip: You do not need to memorize this table right now. We will cover each register group in detail in later lessons. For now, just know that X0-X7 hold arguments and X8 holds the syscall number.

Program Structure

Every ARM64 program has two sections:

  • .data -- holds your data (strings, numbers, buffers)
  • .text -- holds your code (instructions)

The _start label marks where execution begins. The .global _start directive makes it visible to the system.

Writing to the Screen

On Linux AArch64, you print text using the write syscall. To invoke a syscall, you:

  1. Set X8 to the syscall number (64 for write)
  2. Set the arguments in X0, X1, X2
  3. Execute SVC #0 (supervisor call)

For write, the arguments are:

  • X0 = file descriptor (1 = stdout)
  • X1 = pointer to the string in memory
  • X2 = length of the string

The .data Section

String data lives in the .data section. You define a label and use .asciz (null-terminated string) or .ascii (no null terminator):

.data
msg:
    .ascii "Hello, ARM64!\n"

Loading Addresses

To load the address of a label into a register, use LDR with the = prefix:

LDR X1, =msg    // X1 now holds the address of msg

A Complete Program

.data
msg:
    .ascii "Hello, ARM64!\n"

.text
.global _start
_start:
    MOV X0, #1        // fd = stdout
    LDR X1, =msg      // buf = address of msg
    MOV X2, #14        // count = 14 bytes
    MOV X8, #64        // syscall = write
    SVC #0             // invoke syscall

    MOV X0, #0         // exit code = 0
    MOV X8, #93        // syscall = exit
    SVC #0

Exiting Cleanly

Every program must end with the exit syscall (number 93). Without it, the CPU would continue executing whatever bytes happen to follow your code in memory, causing a crash. The value in X0 becomes the exit code (0 = success).

Common mistake: Forgetting to exit. If your program prints nothing or crashes, check that you have the exit syscall at the end.

Your Task

Write a program that prints exactly Hello, ARM64!\n (14 characters) to stdout using the write syscall, then exits with code 0.

ARM64 runtime loading...
Loading...
Click "Run" to execute your code.