Talking to the OS
x86-64 Assembly
Chapter 10 · Talking to the OS
assembly2-8 established that ring-3 code can't do privileged things directly — it has to ask. This chapter covers how it actually asks, and the answer genuinely splits in two depending on which operating system is listening.
Recap — assembly1-8's Clean Abstraction
LC-3's TRAP was one unified mechanism: a single instruction, one vector table, a small fixed set of OS services (GETC/OUT/PUTS/HALT) defined by the LC-3 specification itself, identical on every LC-3 system that ever exists. Real x86-64 has no equivalent single mechanism — what "asking the OS" even looks like depends entirely on which operating system the code is running under.
Linux — The SYSCALL Instruction
x86-64 Linux provides a dedicated SYSCALL instruction — a fast, purpose-built replacement for the older, slower software-interrupt-based mechanism (INT 0x80) that 32-bit x86 Linux used. The convention: the syscall number goes in RAX, and arguments go in RDI, RSI, RDX, R10, R8, R9.
MOV RAX, 1 ; syscall number for write() MOV RDI, 1 ; file descriptor 1 = stdout MOV RSI, msg ; pointer to the string MOV RDX, 5 ; length SYSCALL ; make the call — return value comes back in RAX
assembly2-5's own System V AMD64 ABI uses RCX as the 4th argument register for ordinary function calls. Syscalls use R10 in that same 4th argument position instead — because the SYSCALL instruction itself internally clobbers RCX as part of how it works. Assuming syscall arguments follow the exact same register order as a normal function call is a real, understandable mistake that silently passes the wrong value.
The return value comes back in RAX — the one point of real consistency with assembly2-5's own ordinary function-call convention.
Windows — No Direct Syscalls for Applications
Windows takes a genuinely different approach. It does not expose a stable, documented, directly-invokable syscall interface to ordinary application code the way Linux does — Windows's own internal syscall numbers are considered private implementation detail, and can (and do) change between versions and even updates. Instead, applications call into the Windows API (Win32 API): a large collection of ordinary functions, living in system DLLs like kernel32.dll, which themselves make the real, internal, unstable syscalls on the application's behalf.
The practical consequence: an x86-64 Windows assembly program calling something like WriteFile or ExitProcess does so with an ordinary CALL instruction, using assembly2-5's own Microsoft x64 calling convention, to a function imported from a DLL — not via any direct syscall-style instruction the programmer writes.
A Genuine, Important Divergence
| LC-3 (assembly1-8) | Linux x86-64 | Windows x86-64 | |
|---|---|---|---|
| Mechanism | TRAP + vector table | SYSCALL instruction | CALL into a Windows API DLL function |
| Stability | Fixed by the LC-3 spec itself | A stable, documented, directly-invokable interface | Internal syscalls unstable — only the Windows API surface is the real contract |
| Argument passing | Fixed register (R0) | RDI, RSI, RDX, R10, R8, R9 | Ordinary Microsoft x64 calling convention (assembly2-5) — RCX, RDX, R8, R9 |
This is the real point: LC-3 had one portable OS abstraction, identical everywhere. x86-64 genuinely has two different models depending on target OS — code written to make Linux syscalls directly has no Windows equivalent to fall back on, and vice versa, even though the underlying CPU instructions covered in every prior chapter of this course work identically on both.
assembly2-11 covers the real toolchains (NASM, GAS, ELF, PE) needed to actually assemble and link a program that uses either of these mechanisms — the OS divergence covered here shows up again at the linking stage, not just the instruction level.
Hands-On Exercises
Explain specifically why the Linux syscall convention uses R10 instead of RCX for the 4th argument, even though assembly2-5's own regular System V calling convention uses RCX in that exact position.
📄 View solutionExplain why a Windows assembly program calling a Windows API function uses an ordinary CALL instruction rather than anything resembling Linux's own SYSCALL — what's fundamentally different about how each OS exposes its own services to application code?
📄 View solutionExplain why this chapter describes x86-64's OS interface situation as "genuinely two different models" rather than "one interface with two syntaxes," using assembly1-8's own single unified TRAP mechanism as the contrast point.
📄 View solutionChapter 10 Quick Reference
- LC-3's TRAP was one unified, spec-defined OS abstraction — x86-64 has no equivalent single mechanism
- Linux: the SYSCALL instruction, syscall number in RAX, arguments in RDI/RSI/RDX/R10/R8/R9, return value in RAX
- Syscalls use R10, not RCX, for the 4th argument — because SYSCALL itself clobbers RCX internally
- Windows: no stable, documented direct syscall interface for applications — internal syscall numbers can change between versions
- Windows applications call the Windows API (DLL functions like kernel32.dll) via an ordinary CALL, using assembly2-5's own Microsoft x64 convention
- This is a genuine divergence, not just a syntax difference — code targeting one OS's mechanism has no equivalent on the other
- assembly2-11 covers the real, OS-specific tooling (NASM/GAS, ELF/PE) needed to actually build a working program using either mechanism