Detailed Summary
Today I took a deep, hands-on dive into x86-64 Linux system calls by writing and debugging a pure assembly “Hello, Assembly!” program with no C library.
I started with a simple NASM program that uses the modern syscall instruction. The program does two things:
- Calls sys_write (syscall number 1) to print a string to stdout
- Calls sys_exit (syscall number 60) to terminate cleanly
I loaded the program into GDB + GEF and stepped through it one assembly instruction at a time using si. At every step I watched the registers change in real time:
- $rdi received the file descriptor (1 = stdout)
- $rsi received the address of the string
- $rdx received the length of the string
- $rax received the syscall number
After the first syscall, the message “Hello, Assembly!” appeared and $rax was updated with the number of bytes written (0x11).
I continued stepping until the very last instruction. In the final moment before the program exited, the registers looked like this:
- $rax = 0x3c (60 → sys_exit)
- $rdi = 0 (exit code 0)
- $rip pointing at the final syscall
One last si and the process exited normally. This was the first time I truly saw a program talk to the Linux kernel — register by register, instruction by instruction — all the way to a clean exit.
