# 7. ARM64 Assembly System Call Tutorial
## Overview
This tutorial demonstrates how to write a simple ARM64 (AArch64) assembly program that uses system calls to print "Hello World!" to the console.
## What is a System Call?
A system call (syscall) is a way for user programs to request services from the operating system kernel. On ARM64 Linux, system calls are invoked using the `svc` (Supervisor Call) instruction.
## Complete Source Code
### hello.S
```assembly
// hello.S - ARM64 Assembly Hello World using system calls
// This program demonstrates the use of the SVC (Supervisor Call) instruction
// to make system calls on AArch64 Linux
.global _start // Make _start visible to the linker
.section .data
msg:
.ascii "Hello World!\n"
msg_len = . - msg // Calculate message length
.section .text
_start:
// System call: write(fd, buffer, count)
// On AArch64 Linux:
// - System call number goes in x8
// - Arguments go in x0, x1, x2, x3, x4, x5
// - syscall number for write is 64
mov x0, #1 // x0 = file descriptor (1 = stdout)
ldr x1, =msg // x1 = pointer to message buffer
mov x2, #msg_len // x2 = message length
mov x8, #64 // x8 = syscall number for write (64 on AArch64)
svc #0 // Supervisor call - trigger system call
// System call: exit(status)
// syscall number for exit is 93
mov x0, #0 // x0 = exit status (0 = success)
mov x8, #93 // x8 = syscall number for exit (93 on AArch64)
svc #0 // Supervisor call - trigger system call
```
### Makefile
```makefile
# Makefile for ARM64 Assembly Programs
# This Makefile compiles and links AArch64 assembly programs
# Assembler and Linker
AS = as
LD = ld
# Flags
ASFLAGS =
LDFLAGS =
# Target executable
TARGET = hello
# Source file
SRC = hello.S
# Object file
OBJ = hello.o
# Default target - build the executable
all: $(TARGET)
# Link object file to create executable
$(TARGET): $(OBJ)
@echo "Linking $(OBJ) to create $(TARGET)..."
$(LD) $(LDFLAGS) -o $(TARGET) $(OBJ)
@echo "Build successful! Run with: ./$(TARGET)"
# Assemble source file to create object file
$(OBJ): $(SRC)
@echo "Assembling $(SRC)..."
$(AS) $(ASFLAGS) -o $(OBJ) $(SRC)
# Run the program
run: $(TARGET)
@echo "Running $(TARGET)..."
@./$(TARGET)
@echo "Exit code: $$?"
# Clean up generated files
clean:
@echo "Cleaning up..."
rm -f $(OBJ) $(TARGET)
@echo "Clean complete."
# Phony targets (not actual files)
.PHONY: all run clean help
# Help target
help:
@echo "ARM64 Assembly Makefile"
@echo "======================="
@echo "Available targets:"
@echo " make - Build the executable (default)"
@echo " make all - Build the executable"
@echo " make run - Build and run the program"
@echo " make clean - Remove generated files"
@echo " make help - Show this help message"
@echo ""
@echo "Files:"
@echo " Source: $(SRC)"
@echo " Object: $(OBJ)"
@echo " Target: $(TARGET)"
```
## ARM64 System Call Convention
### User Mode vs Kernel Mode
```mermaid
flowchart LR
subgraph USER["USER MODE (EL0)"]
B[Setup Registers<br/>x8=64 x0=1 x1=msg x2=13]
end
B -->|svc #0| SVC[SVC Instruction<br/>Privilege Switch]
subgraph KERNEL["KERNEL MODE (EL1)"]
C[Exception Handler]
D[System Call Table<br/>syscall_table 64]
E[sys_write Function]
F[Return x0]
C --> D --> E --> F
end
SVC -->|EL0 to EL1| C
F -->|EL1 to EL0| B
```
### Calling Convention on AArch64 Linux:
- **x8**: System call number
- **x0-x5**: Arguments (up to 6 arguments)
- **x0**: Return value after the syscall
### The SVC Instruction:
```assembly
svc #0
```
The `svc` (Supervisor Call) instruction triggers a synchronous exception, transferring control to the kernel. The immediate value (#0) is ignored on Linux but required by the instruction format.
## Important AArch64 Linux System Call Numbers
| System Call | Number | Arguments |
|-------------|--------|-----------|
| read | 63 | (fd, buf, count) |
| write | 64 | (fd, buf, count) |
| exit | 93 | (status) |
| openat | 56 | (dirfd, pathname, flags, mode) |
| close | 57 | (fd) |
**Note**: You can find the syscall number in Linux source code [unistd.h](https://elixir.bootlin.com/linux/v6.18.6/source/include/uapi/asm-generic/unistd.h#L174)
## The Hello World Program
### Program Structure:
```assembly
.global _start // Entry point for the linker
.section .data
msg:
.ascii "Hello World!\n"
msg_len = . - msg // Calculate length at assembly time
.section .text
_start:
// write(1, msg, msg_len)
mov x0, #1 // stdout file descriptor
ldr x1, =msg // address of message
mov x2, #msg_len // length of message
mov x8, #64 // syscall number for write
svc #0 // invoke system call
// exit(0)
mov x0, #0 // exit status
mov x8, #93 // syscall number for exit
svc #0 // invoke system call
```
## How to Compile and Run
### Using the Makefile (Recommended):
```bash
# Build the program
make
# Build and run
make run
# Clean up generated files
make clean
# Show help
make help
```
### Manual Compilation (Alternative):
```bash
# Assemble the program
as -o hello.o hello.S
# Link the object file
ld -o hello hello.o
# Run the program
./hello
```
### Expected Output:
```
Hello World!
```
## Detailed Explanation
### 1. Data Section
```assembly
.section .data
msg:
.ascii "Hello World!\n"
msg_len = . - msg
```
- Defines a read-only data section
- `msg:` is a label pointing to the string
- `.ascii` directive stores the string in memory
- `msg_len = . - msg` calculates the length (current position minus msg start)
### 2. Write System Call
```assembly
mov x0, #1 // File descriptor: 1 = stdout
ldr x1, =msg // Load address of message into x1
mov x2, #msg_len // Number of bytes to write
mov x8, #64 // System call number for write
svc #0 // Execute system call
```
### 3. Exit System Call
```assembly
mov x0, #0 // Exit status: 0 = success
mov x8, #93 // System call number for exit
svc #0 // Execute system call
```
## Key Differences from x86-64
| Feature | x86-64 | ARM64 (AArch64) |
|---------|--------|-----------------|
| Syscall instruction | `syscall` | `svc #0` |
| Syscall number register | rax | x8 |
| Argument registers | rdi, rsi, rdx, r10, r8, r9 | x0, x1, x2, x3, x4, x5 |
| write syscall number | 1 | 64 |
| exit syscall number | 60 | 93 |
## Additional Notes
- ARM64 uses 64-bit registers (x0-x30) and 32-bit registers (w0-w30, lower 32 bits of x registers)
- The `svc` instruction was called `swi` (Software Interrupt) in older ARM architectures
- Always remember to call exit, or the program will crash when it runs past the end
- The immediate value in `svc #0` can be 0-65535, but Linux ignores it
## References
- ARM Architecture Reference Manual
- Linux System Call Table for AArch64: https://github.com/torvalds/linux/blob/master/arch/arm64/include/asm/unistd.h
- AArch64 Procedure Call Standard (AAPCS64)