Skip to content

Tools and Your First AArch64 Program

Begin with a program you can build and explain, not a catalogue of instructions. You will produce a Linux AArch64 executable that writes one line and exits.

Before you start

You need terminal navigation and a Raspberry Pi with 64-bit Linux userspace. No electronics, C++ knowledge or root access is needed to run the examples. This course is Linux user-space assembly, not Pico firmware or a bootloader.

Outcome: build and inspect an executable, identify its entry point and distinguish kernel exit from a function return.

AArch64 names the 64-bit execution state; Linux tools often call the platform Arm64. A board model alone does not identify the installed toolchain.

Check and prepare the tools

Run on the Pi:

1
2
3
4
5
6
uname -m
getconf LONG_BIT
gcc -dumpmachine
as --version
ld --version
gdb --version

Expect aarch64, 64 and an AArch64 compiler target. A 64-bit kernel can host 32-bit programs, so check all three, not just uname. If packages are absent, installing them is an administrative change; review the package-manager prompt:

sudo apt update
sudo apt install build-essential gdb

Create a new practice directory for this lesson. Repeat this pattern with a different suffix for later lessons; do not overwrite useful files.

1
2
3
4
5
6
if lesson_dir=$(mktemp -d "$HOME/aarch64-first.XXXXXX"); then
    cd "$lesson_dir" || exit 1
else
    echo "Could not create practice directory" >&2
    exit 1
fi

Save and run the first program

Save as hello.S. GNU assembler directives begin with a dot; labels end in a colon. Instructions perform operations; comments beginning with // explain intent.

// file: hello.S
.section .rodata
message:
    .ascii "Hello, AArch64!\n"
.equ message_length, . - message

.text
.global _start
.type _start, %function
_start:
    mov x0, #1                  // stdout file descriptor
    adr x1, message             // address, not the message bytes
    mov x2, #message_length     // byte count
    mov x8, #64                 // Linux AArch64 write
    svc #0
    mov x9, #message_length
    cmp x0, x9
    b.ne .Lfailed               // short/failed write is a failure here
    mov x0, #0
    b .Lexit
.Lfailed:
    mov x0, #1
.Lexit:
    mov x8, #93                 // Linux AArch64 exit
    svc #0
.size _start, .-_start
.section .note.GNU-stack,"",%progbits
1
2
3
4
5
6
7
as -g hello.S -o hello.o
ld hello.o -o hello
./hello
echo "$?"
file hello
readelf -h hello
objdump -d hello

Expected output before the inspection commands:

Hello, AArch64!
0

The .o file is relocatable input, not the runnable program. ld resolves symbols and creates the executable; _start is its process entry. This deliberately small static example does not use a C runtime. Its adr reaches this nearby message; a later lesson covers range and relocation choices.

System calls are not function calls

Here svc enters the kernel, x8 selects a Linux operation and x0 receives its result. The numbers 64 and 93 are specific to Linux AArch64. They are not portable service numbers for bare-metal firmware or other operating systems. Check the target's installed asm-generic/unistd.h definitions rather than borrowing a table for x86.

A later C-callable routine instead enters with bl and returns with ret; it must not call exit when its caller expects a return. This is why the next lessons use a C driver for printing and checking results.

When the result differs

  • Unknown x0 or instructions: check the native assembler target.
  • Exec format error: check the executable architecture and userspace.
  • Missing tool: install the relevant package, not a random compiler repository.
  • Exit 1: the example did not complete its single write; this is not a general retrying I/O implementation.
  • Permission denied: check the directory mount and execute permissions before using sudo.

Do not continue until you can rebuild in this directory and identify the entry symbol in disassembly.

Check your understanding

Which file is runnable, and why is replacing the last system call with ret incorrect here?

Compare your reasoning

The linked executable is runnable. No function caller placed a normal return address in LR at process entry; the program ends through Linux exit, not a return to C.

Continue the course

Course roadmap · Next: Registers and Integer Operations

Donate