I Watched Every Instruction of a Linux Program Run — Here’s What Actually Happens

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:

  1. Calls sys_write (syscall number 1) to print a string to stdout
  2. 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.