Day 1
I’m starting a project to get back closer to the machine where I love to be. I'm not going to start with anything ambitious, but by coming back to something simple: compiling a tiny C program and inspecting the binary that drops out.
I’ve spent decades building systems where performance, architecture, concurrency, and distributed correctness mattered — but it’s amazing how grounding it can be to step back and revisit the basics. Sometimes the smallest artifacts remind us of the real machinery beneath all the abstractions.
So today’s exercise was straightforward:
#include <stdio.h>
int main(int argc, char ** argv){
(void)argc;
(void)argv;
printf("Hello world!\n");
return 0;
}
Compile it, and now we have a binary. Nothing special. But it’s a good excuse to remember what actually happens when we run a program.
Programs don’t start at main
Even for a tiny program, the binary is more than just my code. It represents the entire journey:
C source → compilation → linker → assembly → machine instructions
→ stored in an ELF binary → loaded by the OS → environment and runtime initialized → then main() is called
Having spent a good amount of time in a higher level environment, it's good to remind myself of this.
In reality, when the OS runs this binary, it doesn’t jump straight to main. It jumps into _start, which:
Sets up the stack and registers
Prepares arguments and environment
Hooks up the dynamic loader
Transfers control to __libc_start_main
Which finally calls our main
A good reminder of binary internals
Ghidra helped me put this back into focus:
A container of machine code
A structured file format with defined sections (.text, .data, .rodata, etc.)
Metadata telling the kernel how to load it
Instructions designed to map cleanly onto CPU execution flow
It’s a small thing, but seeing that movement from structure → execution never stops being satisfying.
Why start here?
My goal is to grow this project into deeper territory:
Kernel modules
eBPF tracing
Malware analysis
Reverse engineering
System-level telemetry tools
And certainly some rabbit holes I don’t know about...
Starting simple is intentional.
It’s worth resetting my mental model of:
“What is a program, to the OS?”
Rebuilding that mental foundation helps everything else make more sense. At least for me.
Even if I’ve known these things forever, revisiting them cleanly helps switch the brain back into systems mode — away from frameworks, clouds, and abstractions and back into the machine. Snuggled up spooning with the hardware.
What’s next
Next steps will be just as incremental:
Writing my first kernel module again
Loading it with insmod
Reading logs through dmesg
Nothing grand, just onward progress.
This is a slow burn, but a good reminder for me.
Top comments (1)
This really resonates. I remember dusting off
objdumpafter years in web-land and being oddly delighted just stepping through the ELF headers of a “Hello, world.” It snapped my mental model back into place the same way you describe. Looking forward to seeing your kernel module and eBPF explorations unfold.