Hey, Welcome to the article one of the series “Into-the-OS”. First, i thought we should think about the process(instructions that runs on the computer).
What is a Process ?
The set of instructions that runs on a computer or machine is called a process in the terms of Operating systems. Let’s take a code
Code is available in the github repository for the Series of Article : https://github.com/c0d3l0v3r-HeHe/Into-the-OS
Now, is this a Process ?
It might shock you , No it is not. It is not a process until we really run the object file created. Let’s figure out the files created by the command
gcc -o process process.c --save-temps
Files created by running the command above ( Compilation with — save-temps
process.i:
This is a pre-compiled file that contains the added implementations from the stdio.h file as shown in the main process.c file
process.s:
This is the assembly file that contains the instructions in RISC-V or other architectures. Here is the demo of the above:
Scary. Isn’t it ?
process.o:
It is the object file that we can run.
So, nothing fancy ?
objdump -d -s process > objDump.txt
by doing this you get another scary file objDump.txt
Now, let’s dig into the structure for some time.
Image showing the objdump -d -s for the process.o
Sections
See the things like Contents of section .dynsym, .gnu.hash, etc. These are the sections stored in a structure i will talk about later and you will be suprised.. Hehe.
Image showing .text and .rosection
We see .rodata section we see our “Hello World !!”
Let’s move to the execution. Apparently when the Program Runs it starts with the <_start> not <main> 😲:
Start of the progam
Lines showing the loading of the main in the rip and then running the __lib_start_main using the location of the main function
Rest of the instructions above and below are for security, holding argc and argv and other stuff which we might not need so we will skip it for now. But, if you are interested, you can GPT it 😉
Here we push the rbp inside the stack and store the value of the earlier stack pointer to the rbp.
Then we use lea (Load Effective Address) to calculate the location of our string.
The math: 0x1158 + 0x0EAC = 0x2004.
This 2004 is the exact address of our string "Hello World !!" living in the .rodata section. All these statements do is set up the plumbing:
-
**1151**: Calculate the address of the string and load it into%rax. -
**1158**: Move that address from%raxinto%rdi(the first argument register for any function in Linux). -
**115b**: Move0to%eax(tellingprintfwe aren't using floating-point vector registers). -
**1160**: Call theprintfstub.
If we look closely at that printf call, it does a jmp to *0x2f76(%rip).
More math: 0x105a + 0x2f76 = 0x3FD0.
This location (3FD0 in the Global Offset Table) will eventually point to the real printf implementation in your system's C library when the program actually runs. For now, because we are just looking at a static file, the real address isn't there yet!
What did we learn?
After getting a little drifted in the assembly code, let’s zoom back out and look at the structure. What exactly did we just see in this file?
- Sections: distinct blocks of memory (
.rodata,.plt,.got) that store specific information and data needed for the program to function. - Executable Code: The
.textsection, holding the raw assembly instructions for_start,main, and the PLT stubs that will eventually run on the CPU. - A whole lot of hex codes…
But wait. Here is the most important question of this entire article: Did we ever actually run the program?
No, we didn’t. We just inspected a static .o object file sitting dead on our hard drive.
So… where did all those memory addresses (1060, 2004, 3FD0) come from if the program isn't even loaded in RAM yet?
Revelation: The Illusion of Memory
This is the core magic of the Operating System.
The addresses you see inside the objdump are not real physical RAM addresses. They are virtual addresses. The executable file we compiled is nothing more than a static blueprint (an ELF file).
When you finally type ./process and hit enter, the Operating System steps in. It creates a completely isolated, private Address Space for this specific execution. It reads our blueprint, copies the .text and .rodata sections into your actual physical RAM, and then lies to the program—making it think it owns a perfect, continuous block of memory starting from those exact virtual addresses we saw in the dump.
A program is just a dead file on a disk. A Process is the active, breathing instance of that program, wrapped in a beautiful illusion of private memory orchestrated by the OS.
One more important thing to note is if i run all these 3 programs together, they should technically crash because all of their main addresses at the same point. Right?
But, they will not. Because they are just virtual addresses and the OS gives the illusion of the infinite memory. OS will give different physical memory locaiton to each one of the program that we would run.
Let’s map what we just saw in our objdump directly to this X-ray:
1. The Code & Data (The Static Stuff)
These sit at the very bottom, in the lowest memory addresses.
- Remember our
mainfunction sitting at address1149? That is loaded right into the Code (.text) block. - Remember our
"Hello World !!"string sitting at address2004? That gets loaded into the Data block right above it. Because the OS reads these directly from our ELF file on the disk, their sizes are fixed. They never change while the program is running.
2. The Stack (The Missing Piece)
If the code and data are static, how does a program actually do things dynamically? Look back at the very first instruction inside our main function: 114d: push %rbp 114e: mov %rsp,%rbp
This is the code interacting with the Stack! The Stack lives at the very top of the Address Space (the highest memory addresses). Every time you call a function, the OS pushes a new “frame” onto the stack to hold its local variables. When the function finishes (ret), the frame is popped off. The Stack actively grows downward as your program runs deeper into function calls.
3. The Heap
Right below the stack is the Heap. If you’ve ever used malloc() in C or new in C++, this is where that memory comes from. While the Stack grows downward, the Heap grows upward. (And yes, if you write a terrible infinite loop that allocates too much memory, the Heap and the Stack will eventually crash into each other, blowing up your program with a Stack Overflow or Out of Memory error. Scary, isn't it?)
Conclusion: The Brain of the Process (The PCB)
We’ve seen the memory, the assembly instructions, and the grand illusion of the Address Space. But how does the Operating System actually keep track of all this?
If the Address Space is the physical house the process lives in, the OS needs a master ledger to keep track of who owns which house, what they are doing, and what they own. In OS terms, this data structure is called the Process Control Block (PCB) (or Process Descriptor).
Every time you execute ./process, the OS doesn't just hand out virtual memory. It creates a massive C-structure deep inside the OS kernel to track everything about this specific running instance. What exactly does it store?
- Process State: Is the program actively running on the CPU? Is it blocked waiting for you to type something on the keyboard? Is it terminated?
- Process ID (PID): The unique integer identifier (like
4092) you see when you run commands likehtoporpsin your terminal. - CPU Register State: This is the most crucial part. When the OS temporarily kicks this process off the CPU to let another program run, it saves the exact, freeze-framed values of
%rax,%rsp,%rip, and all the other registers right here in the PCB. When the process gets the CPU back, the OS restores these values, and the program resumes as if it never stopped. - I/O Status Information: A list of all open files, network sockets, and connected devices the process is actively using.
A process is vastly more than just compiled code. It is an intricate, highly managed data structure maintained by the invisible engine of the Operating System kernel.
Hopefully, this deep dive gives you a clear picture of what a process truly is under the hood. We stripped away the abstraction, dumped the raw binaries, and exposed the Virtual Memory illusion that makes modern software possible.
Next up, we are going to see this PCB in action. If every process thinks it owns the CPU forever, how does the OS secretly pause them, swap their registers, and run 100 programs at once without any of them noticing?
Image showing the task struct on https://elixir.bootlin.com/linux/v7.1.1/source/include/linux/sched.h#L820
While that massive task_struct is the official way the Linux kernel tracks a process, it contains hundreds of fields we don't need to worry about right now. Moving forward in this series, we are going to stick to the classic, simplified textbook model we just built: Stack, Heap, Data, and Code. As an engineer, this is the core mental framework you will actually need.
See you in Part 2 for the math and magic of CPU Scheduling!










Top comments (2)
That _start → main reveal always gets people 😂 You also handled the GOT part well — that "address isn't there yet" bit is where most OS tutorials lose people, but yours actually made sense. Good read.
Thanks for the support...😁