A safe, local lab for discovering what happens between C source code and the Linux/x86-64 kernel boundary.
This is not a concept essay and it is not an OS project. It is a staged repo of small programs, Makefiles, broken TODOs, inspection commands, and notes. You are expected to edit code, run tools, inspect generated files, and write down what changed.
The path you will keep revisiting:
C source
-> preprocessor
-> compiler
-> assembler
-> object file
-> linker
-> ELF executable
-> dynamic loader
-> libc
-> syscall ABI
-> CPU syscall instruction
-> kernel syscall table
-> kernel implementation
Keep this map nearby, but do not memorize it up front. Each lab makes one edge visible with commands like gcc -S, nm, readelf, objdump, strace, and gdb.
Before the first lab, skim PREREQUISITES.md. It gives the background vocabulary, tool usage, ABI facts, and references you need without giving away the TODO implementations.
Build and run the starter demos:
make build
make demoThen run the intentionally failing TODO tracker:
make checkExpected first-run result:
make buildpasses.make demopasses for completed starter demos.make checkpasses labs00,01,02,03,09, and10.make checkfails labs04through08until you complete their TODOs.
If you want a non-failing sanity check before starting the TODO labs, run:
make check-starterUse Docker if your host does not have the tools installed:
docker build -t os-abi-cpu-lab -f docker/Dockerfile .
docker run --rm -it -v "$PWD:/workspaces/os-abi-cpu-lab" os-abi-cpu-labInside the container:
make build
make demo
make checkThe Docker image includes gcc, clang, ld, objdump, readelf, strace, gdb, make, and QEMU.
This repo is designed to be safe and local.
- No host
sudois required. - No kernel modules are loaded.
- No bootloader or host kernel configuration is modified.
- No raw block devices are accessed.
- File syscall demos operate only under
labs/sandbox/. - QEMU is used only for the tiny toy kernel in
labs/09_minimal_qemu_kernel/.
If a lab asks you to edit syscall code, inspect the syscall number and arguments before running it. Stay inside the listed demo files.
.
README.md
Makefile
docker/
scripts/
labs/
00_toolchain/
01_c_to_asm/
02_object_files/
03_linking_libc/
04_no_libc_start/
05_raw_syscalls/
06_tiny_libc/
07_fake_kernel_syscall_dispatcher/
08_cpu_trap_simulator/
09_minimal_qemu_kernel/
10_challenges/
sandbox/
notes/
solutions/
For every lab, run at least one inspection command before moving on:
objdump -d ./some_binary
readelf -h ./some_binary
readelf -s ./some_object.o
nm ./some_object.o
strace ./some_binaryThe lab READMEs list exact commands. The scripts under scripts/ are only convenience wrappers; the Makefiles keep the raw commands visible.
labs/00_toolchain/: prove the basic tools work and seeprintfturn into syscalls.labs/01_c_to_asm/: compare compiler output at-O0and-O2.labs/02_object_files/: see unresolved symbols before the linker resolves them.labs/03_linking_libc/: find imported libc symbols and compare them withstrace.labs/04_no_libc_start/: implement_startand exit without libc.labs/05_raw_syscalls/: write Linux x86-64 syscall ABI wrappers.labs/06_tiny_libc/: build a small libc-like adapter layer withtiny_errno.labs/07_fake_kernel_syscall_dispatcher/: simulate a kernel syscall table in user space.labs/08_cpu_trap_simulator/: simulate privilege transitions, traps, and returns.labs/09_minimal_qemu_kernel/: boot a tiny x86 kernel in QEMU and handle a breakpoint trap.labs/10_challenges/: extension prompts once the main path is done.
For each lab:
- Read that lab's
README.md. - Run
make build. - Run the inspection commands before editing.
- Fill in TODOs.
- Run that lab's
make check. - Record answers in
notes/questions.md.
Avoid jumping straight to solutions/. They are there for comparison after you have tried the exercise.
Record answers in notes/questions.md. Do not write long essays; one precise paragraph per question is enough.
After each lab, answer:
- What command showed the most important boundary in this lab?
- What information existed in the file before the next tool consumed it?
- What information was still unresolved?
- Which part was compiler behavior, linker behavior, libc behavior, CPU behavior, or kernel behavior?
Starter code is intentionally incomplete in later labs. Do not open solutions/ until you have attempted the TODOs and run that lab's make check.
The solutions are not the main artifact. The main artifact is your own notes and the inspected binaries.
make build
make demo
make check-starter
make check
make clean
./scripts/inspect.sh labs/00_toolchain/build/hello
./scripts/trace.sh labs/03_linking_libc/build/use_libcPer-lab examples:
make -C labs/01_c_to_asm asm
make -C labs/02_object_files broken-link
make -C labs/03_linking_libc imports
make -C labs/04_no_libc_start objdump
make -C labs/09_minimal_qemu_kernel inspectflowchart LR
C["C source"] --> PP["preprocessor"]
PP --> CC["compiler"]
CC --> AS["assembler"]
AS --> OBJ["object file"]
OBJ --> LD["linker"]
LD --> ELF["ELF executable"]
ELF --> DYN["dynamic loader"]
DYN --> LIBC["libc"]
LIBC --> ABI["Linux x86-64 syscall ABI"]
ABI --> CPU["syscall instruction"]
CPU --> KERN["kernel syscall handler"]