Step 1 of 5 · Reading · ~4 min
Learn
First Steps
Hello via syscall
By the end of this lesson your program will read a line from standard input and answer on standard output with two kernel calls and nothing else. A CPU only moves bytes and does arithmetic; the operating system owns the screen and the keyboard, and a syscall is how a program asks it for something.
On Linux x86_64 you put a request number and its arguments into specific registers and run the syscall instruction; the CPU traps into the kernel, which does the work and hands control back. This course uses NASM (the Netwide Assembler). There is no libc and no printf: every effect your program has on the outside world is a syscall.
Anatomy of a NASM program
section .data
banner db "Build OK", 10
banner_len equ $ - banner
section .text
global _start
_start:
mov rax, 1 ; syscall number: 1 = write
mov rdi, 1 ; arg 1: file descriptor, 1 = stdout
mov rsi, banner ; arg 2: address of the first byte
mov rdx, banner_len ; arg 3: how many bytes
syscall
mov rax, 60 ; syscall number: 60 = exit
xor rdi, rdi ; arg 1: exit status 0
syscall
section .dataholds initialized bytes.db("define bytes") lays them out exactly as written: the 8 characters ofBuild OK, then the raw byte10, which is the ASCII newline. Assembly has no string type, only bytes you choose to treat as text.banner_len equ $ - banneris computed while assembling.$means "the address right here", so subtractingbannergives the byte count, 9. Edit the text and the length follows.section .textholds code.global _startexports the entry label. The kernel starts you at_start, notmain:mainis a C convention that libc's startup code calls, and no libc is linked here.
The syscall convention
The number goes in rax. The arguments go, in order, in rdi, rsi, rdx, r10, r8, r9. The numbers you need now are 0 = read, 1 = write, 60 = exit. File descriptor 0 is standard input, 1 is standard output, 2 is standard error.
- The numbers are 64-bit specific. On 32-bit Linux write was 4; on x86_64,
rax = 4isstat. The wrong table runs a different syscall instead of failing loudly. syscallclobbersrcxandr11. The kernel parks your return address and flags there. Every other register survives.writeignores null terminators. It sends exactlyrdxbytes fromrsi. A length of 0 succeeds and prints nothing.
Syscalls hand an answer back
When syscall returns, rax holds the result. For read and write it is the number of bytes actually transferred, or a small negative error code (-9 means bad file descriptor). For read, 0 means end of input.
To read you need somewhere to put the bytes. section .bss reserves zeroed space without storing it in the file:
section .bss
buf resb 16 ; 16 bytes, all zero at start
Suppose stdin holds Bob and a newline, the four bytes 42 6F 62 0A, and you call read with rdi = 0, rsi = buf, rdx = 16. You asked for up to 16 but the line has 4, so rax comes back as 4 and buf holds those four bytes.
Now the catch. The next syscall needs its own number in rax, which destroys your count. Copy it somewhere that survives, such as rbx, before you set up the next call:
mov rbx, rax ; rax is about to become 1
mov rax, 1
mov rdi, 1
mov rsi, buf
mov rdx, rbx ; echo exactly what was read
syscall
That is a complete echo program body. A read may return fewer bytes than you asked for, so use the returned count.
Why exit is not optional
After your last instruction the CPU keeps executing whatever bytes come next in memory, usually ending in a segfault. exit (rax = 60, status in rdi) ends the process on purpose. xor rdi, rdi zeroes a register, shorter than mov rdi, 0.
Common mistakes
- Reusing
raxafter it was overwritten. Runmov rax, 1and thenmov rdx, rax, andrdxis 1: you print one byte instead of the line. - Counting the newline you did not want. The count from
readincludes the0Aat the end of the line. Write all of it and a line break lands in the middle of your output. - Counting characters.
writecounts bytes:Joséis 5 of them. - No exit. The program falls off the end and crashes even though the text appeared.
Your exercise
The starter already reads one line into name and then exits without printing anything. Turn it into a greeter using the data labels provided. The detail the tests lean on is the line break at the end of what read returned.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…