---
# System prepended metadata

title: ARM64 Assembly System Call Tutorial

---

# 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)

