A hands-on exploration of what actually happens when a C program becomes a running process. From source code, to assembly, to object files, to a linked executable, all the way down to the operating system via syscalls.
This project walks through the full toolchain that turns human-readable C code into something a CPU can actually execute. Rather than treating the compiler as a black box, each stage is inspected and manipulated directly:
- Compiling C source into x86 assembly
- Assembling that assembly into relocatable object files
- Linking object files (including assembly) into a final executable
- Automating the build with a Makefile
- Interfacing with the OS directly through raw syscalls
The environment is Linux running on Intel x86_64, since the generated assembly and syscall behavior are architecture-specific.
mini.c— three small C functions (sort,branch,loop) used to explore compilation and control flow at the assembly levelmini_mod.S— a hand-modified version of the compiled assembly, where thesortfunction's logic is rewritten directly in assembly (returns11ifx > 5, else17)calc.c/mystery.S— a very small calculator that dispatches on an operation struct, where one function is implemented in assembly and linked into the C program via the C calling conventioncaller.c— a program extended withwritesyscalls to standard out and standard error, inspected withstraceto see exactly what the OS is doing under the hoodMakefile— build automation for producingtest-mini,test-mini2, andcalcfrom their respective sourcesquestions.txt— notes on syscall behavior observed viastrace
C source (.c) → [compiler] → Assembly (.s/.S) → [assembler] → Object file (.o) → [linker] → Executable
gcc -Sstops after compiling to assembly, letting you read/edit the generated.sfile directlygcc -cstops after assembling, producing a relocatable.ogcc(no flags) orldlinks object files into a final executableasinvokes the assembler directly on a.s/.Sfile
- Registers & the x86 ISA — how instructions like
addmap to real operations on the 8 general-purpose registers - The C calling convention — how a function written entirely in assembly (
mystery.S) can be called seamlessly from C, thanks to shared conventions for argument passing, return values, and stack frame setup/teardown - Syscalls — how a user-space process asks the kernel to do privileged work (like writing to a file descriptor), observed directly with
strace
# Build and run the basic C program
gcc test.c mini.c -Wall -o test-mini
./test-mini ALL
# Build and run the assembly-modified version
make test-mini2
./test-mini2 3
# Build and run the calculator (C + written assembly)
make calc
./calc
# Inspect syscalls made by the syscall demo
strace ./callerModern development happens mainly above the level of assembly and syscalls, but understanding what's underneath demystifies a lot of magic: why a segfault happens, what a stack frame actually is, why calling conventions exist, and how a program talks to the OS at all. This project was a chance to get hands dirty with that layer directly, instead of just reading about it.