Master the fundamental concepts of x86 assembly (intel syntax) through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksUserspace cannot read disks or map memory directly. On x86-64 Linux you load a number into rax, arguments into rdi/rsi/rdx/r10/r8/r9, and execute syscall. glibc's write wrapper adds cancellation points and error handling; your assembly calls the kernel raw. strace logs these crossings; seccomp filters them in Docker.
nasmLoading…
| Syscall | rax | arg1 (rdi) | arg2 (rsi) | arg3 (rdx) |
|---|---|---|---|---|
| read | 0 | fd | buf | count |
| write | 1 | fd | buf | count |
| exit | 60 | status |
Errors return -errno in rax (negative values). Compare against zero after syscall.
For this exercise, you will wrap read, write, and exit as callable functions. This task asks you to preserve the AMD64 argument registers your caller sets, because botching the wrapper is how bare-metal demos write to the wrong fd silently.
Keep the relevant man page, ABI doc, or Rust reference chapter open while you work. When your output disagrees with the reference implementation on the same machine, the mismatch is usually an alignment rule, an off-by-one terminator, or a register slot you misread in GDB. Skim the official documentation for the tool or ABI named in the exercise; the prose changes, but register roles, syscall numbers, and ownership rules stay stable across releases.
The syscall instruction is the whole user/kernel interface on x86-64 Linux: the number goes in rax, the arguments in rdi, rsi, rdx, r10, r8, r9 (r10, not rcx, because syscall itself overwrites rcx and r11), and the result comes back in rax, where -4095..-1 means -errno. Write the kernel side as a tiny dispatcher: each input line is the register state at a syscall instruction. Execute it against a small fake kernel, and print what strace would show.
One syscall per line: REG=VALUE pairs in any order. REG is rax rdi rsi rdx r10 r8 r9 (or rcx, which the kernel ignores). A VALUE is an integer (decimal, 0x hex, or negative) or, only for write's rsi and open's rdi, a double-quoted string with \n \t \\ \" escapes. Unset registers are 0. rax is required.
| rax | call | arguments |
|---|---|---|
| 0 | read | fd, buf, count |
| 1 | write | fd, "data", count |
| 2 | open | "path", flags |
| 3 | close | fd |
| 8 | lseek | fd, offset, whence (0 SET, 1 CUR, 2 END) |
| 17 | pread64 | fd, buf, count, offset (r10), leaving the file offset unchanged |
| 39 | getpid | always 1234 |
| 60 / 231 | exit / exit_group | status (stop reading input) |
/etc/hostname holds box\n, and /etc/motd holds hello, world\n.flags & 3 (0 O_RDONLY, 1 O_WRONLY, 2 O_RDWR; 3 is EINVAL), plus 64 O_CREAT (create an empty file), 512 O_TRUNC (with write access) and 1024 O_APPEND. A new fd is the lowest free one (up to 16 fds, and up to 8 files including the two above).Errors, returned as -errno:
lseek on fds 0-2 returns -1 ESPIPE (Illegal seek).cLoading…
rcx, print note: rcx is ignored (syscall overwrites rcx and r11); the 4th argument goes in r10 first.+++ no exit call: the process would run off the end of its code +++.stdout: "..." and stderr: "...": everything written to fds 1 and 2.\n \t \\ \" escapes, and other non-printable bytes as \xNN.error: bad line: LINE and is skipped.Input:
cLoading…
Output:
cLoading…
rax and read the arguments in ABI order. The rcx trap is the classic hand-written-syscall bug.-errno, the way the kernel does, and render them the way strace does.Hidden tests cover O_CREAT/O_APPEND/O_TRUNC files that are written and read back, lseek from the end, pread64 not moving the offset, EMFILE, stdin at EOF, exit_group with a negative status, lines after exit, and malformed lines.