I/O#
Assembly’s I/O surface is the syscall instruction (or
svc #0 on ARM64). The kernel exposes a numbered table of
syscalls; the operator loads the number plus the arguments into
the right registers and invokes the trap. Userland libraries
(libc) wrap these for convenience, but the operator who writes
shellcode or freestanding binaries calls them directly.
For errno handling, see
Errors. For socket syscalls, see Networking.
Linux x86-64 syscall ABI#
Syscall number:
rax.Args 1-6:
rdi rsi rdx r10 r8 r9. (Note:r10, notrcx; thesyscallinstruction clobbersrcx.)Result:
rax(negativeerrnoon failure; see Errors).Clobbered:
rcx(return address shadow),r11(RFLAGS shadow).
; write(1, msg, len)
mov rax, 1 ; syscall number
mov rdi, 1 ; fd = stdout
lea rsi, [rel msg]
mov rdx, len
syscall
The syscall numbers use daily.
Num |
Name |
Args |
|---|---|---|
0 |
|
|
1 |
|
|
2 |
|
|
3 |
|
|
9 |
|
|
10 |
|
|
11 |
|
|
12 |
|
|
13 |
|
|
39 |
|
|
41 |
|
|
42 |
|
|
59 |
|
|
60 |
|
|
231 |
|
|
Full table at
/usr/include/asm/unistd_64.h or in the kernel’s
arch/x86/entry/syscalls/syscall_64.tbl.
ARM64 Linux syscall ABI#
Syscall number:
x8.Args 1-6:
x0..x5.Result:
x0(negativeerrnoon failure).Trap instruction:
svc #0.
// write(1, msg, len)
mov x0, #1 // fd
adr x1, msg
mov x2, #len
mov x8, #64 // syscall number for write on arm64
svc #0
ARM64 Linux uses a different numbering than x86-64; see
arch/arm64/include/asm/unistd.h for the canonical table.
hello world (Linux x86-64)#
section .rodata
msg: db "hello, operator", 0x0a
len equ $ - msg
section .text
global _start
_start:
mov rax, 1 ; write
mov rdi, 1 ; stdout
lea rsi, [rel msg]
mov rdx, len
syscall
mov rax, 60 ; exit
xor rdi, rdi ; status 0
syscall
Build and run.
$ nasm -felf64 hello.asm -o hello.o
$ ld -o hello hello.o
$ ./hello
hello, operator
Reading from stdin#
section .bss
buf: resb 4096
section .text
; read(0, buf, 4096)
mov rax, 0
mov rdi, 0 ; stdin
lea rsi, [rel buf]
mov rdx, 4096
syscall
; rax now holds bytes read (or negative errno)
The operator handles short reads by looping until the desired
length is read or EOF (rax == 0).
File open / read / close#
; fd = open(path, O_RDONLY)
mov rax, 2
lea rdi, [rel path]
xor rsi, rsi ; O_RDONLY (0)
xor rdx, rdx ; mode unused
syscall
mov r12, rax ; save fd
; n = read(fd, buf, 4096)
mov rax, 0
mov rdi, r12
lea rsi, [rel buf]
mov rdx, 4096
syscall
; close(fd)
mov rax, 3
mov rdi, r12
syscall
O_RDONLY = 0, O_WRONLY = 1, O_RDWR = 2,
O_CREAT = 0x40, O_APPEND = 0x400. The operator combines
with or.
Calling libc#
When the operator targets a hosted binary (the C runtime is linked), going through libc beats raw syscalls for portability.
extern printf, exit
section .rodata
fmt: db "n=%d", 10, 0
section .text
global main
main:
sub rsp, 8 ; align to 16
lea rdi, [rel fmt]
mov rsi, 42
xor rax, rax ; no vector args
call printf
xor edi, edi
call exit
Exiting#
exit_group (231) terminates every thread in the process;
exit (60) terminates only the calling thread. For
single-threaded binaries either works.
; exit_group(0)
mov rax, 231
xor rdi, rdi
syscall
Formatting and parsing#
The kernel does no formatting; read and write move raw
bytes. The operator either calls into printf / sprintf
via libc, or hand-rolls integer-to-string conversion.
A minimal itoa for unsigned 64-bit values.
; itoa: rdi = value, rsi = out buffer (>= 21 bytes); rax = length
global itoa
itoa:
mov rax, rdi
mov rcx, 10
lea r8, [rsi + 21] ; write from end
mov rsi, r8
.loop:
xor rdx, rdx
div rcx ; rdx = digit
add dl, '0'
dec rsi
mov [rsi], dl
test rax, rax
jnz .loop
mov rax, r8
sub rax, rsi ; length
ret
References#
Errors for
errnohandling.Functions for libc calling convention details.
Networking for the socket syscalls.
Runtime for ELF entry and dynamic linking.