What Really Happens When You Run a Program?

What Really Happens When You Run a Program?

You type ./hello into your terminal. It seems simple. But under the hood, an incredible chain of events begins — orchestrated by your OS kernel, guided by the binary format, and made possible by compilers and runtime systems.

This is the story of how an executable binary like ELF becomes a living, breathing process.

Let’s break it down step by step. 👇


🔹 1. The Shell Makes a System Call

When you execute a binary, your shell doesn’t run it directly. Instead, it makes a syscall like:

execve("./hello", argv, envp);        

This hands control to the Linux kernel — the core of the operating system.


🔹 2. The Kernel Recognizes the ELF Format

The kernel inspects the first few bytes of the file. If they start with:

0x7F 45 4C 46   // .ELF        

…it knows it's dealing with an ELF (Executable and Linkable Format) binary — the standard format for executables, libraries, and object code on Linux and other Unix-like systems.


📦 ELF File Format Overview

An ELF file is like a neatly labeled container of sections and segments. Its layout includes:

Article content

There are segments (for loading at runtime) and sections (for linking and debugging).

Not all sections are loaded into memory — some are just metadata for tools.


🔹 3. The Kernel Parses ELF Headers

The kernel reads the ELF header and program header table, which tells it:

  • The target architecture (e.g., x86_64)
  • The entry point address
  • The layout of segments (code, data, etc.)
  • The interpreter if it's a dynamically linked executable

This metadata is essential — it’s a contract between the binary and the OS.


🔹 4. Memory Is Mapped for the Process

Using the ELF program headers, the kernel:

  • Maps the .text (code), .data (initialized data), and .bss (zero-initialized data) segments into memory
  • Allocates space for the stack and heap
  • Initializes the stack with:

  • ➡️ argc, argv[], and envp[]
  • ➡️ Auxiliary vectors (auxv) — hints like page size, CPU features, Virtual Dynamic Shared Object (VDSO)

At this point, the process’s virtual memory layout is ready.


🔹 5. The Dynamic Linker Is Invoked (If Needed)

If the binary is dynamically linked, the kernel doesn't jump directly to the program’s code.

Instead, it loads the dynamic linker, usually:

/lib64/ld-linux-x86-64.so.2        

This ELF binary is itself an executable — a special loader that:

  • Loads shared libraries like libc.so
  • Resolves external symbols (e.g., printf)
  • Applies relocations to adjust addresses
  • Sets up the Procedure Linkage Table (PLT) and Global Offset Table (GOT)
  • Finally jumps to the program’s entry point


🔹 6. Execution Starts at _start, Not main()

Here’s the part many overlook: the kernel does not call your main() function.

Instead, execution begins at a low-level entry point — often _start. This startup code is provided by the runtime system or standard library and is responsible for:

  • Parsing the stack to extract argc, argv, and envp
  • Calling initialization routines (e.g., C++ constructors or Rust global state)
  • Calling main(argc, argv, envp)
  • Passing its return value to exit()

So main() is just part of the story — _start is the true first step.


🔹 7. The Process Exits

When main() returns, its exit code is passed to the exit() syscall. The kernel cleans up resources, and the shell receives the return value as the program’s exit status.


🤝 The System Contract: Compiler ↔ Runtime ↔ Kernel

When a compiler produces an executable (like an ELF binary), it must follow certain ABI-level contracts that make the binary runnable by the OS kernel. These contracts are language-agnostic but enforced at the binary and calling convention level.


🧾 The Key Contracts

1. File Format: Must be a valid ELF

  • Must begin with the ELF magic number.
  • Must include an ELF header describing:
  • ➡️ Machine type (e.g., x86_64)
  • ➡️ Entry point address
  • ➡️ Offsets to program/section headers
  • ➡️ Flags, bitness (32/64-bit), endian

2. Program Header Table

  • Tells the kernel how to map segments (code, data) into memory.
  • Common segment types:
  • ➡️ PT_LOAD: Loadable segments like .text, .data
  • ➡️ PT_INTERP: If dynamically linked, path to dynamic linker (like /lib/ld-linux.so.2)
  • ➡️ PT_PHDR, PT_TLS, etc.

3. Entry Point

  • This is where the kernel starts execution after loading the ELF.
  • Typically a function like _start, not main().
  • Must follow the system’s calling convention (e.g., stack pointer, registers).

4. Startup Environment

  • The kernel sets up:
  • ➡️ The stack, with argc, argv[], envp[]
  • ➡️ The auxiliary vector (auxv) — kernel hints like page size, random seed, VDSO address
  • It’s the responsibility of _start to extract this and prepare main().


Every executable binary is part of a carefully defined system contract:

Article content

This contract ensures that any conforming ELF binary can run reliably — regardless of language or build tool.

Languages like C, C++, Rust, Go, and Zig all work within this contract, though their runtimes differ. Some use libc. Others (like Go or Zig) bring their own runtime and don’t depend on libc at all.


🛠️ Want to Explore an ELF Binary?

Try these commands to peek inside:

 # ELF header
readelf -h ./hello

# Program headers (segments)
readelf -l ./hello          

# Sections (for linking)
readelf -S ./hello         

# Disassembly
objdump -d ./hello     

# Linked libraries 
ldd ./hello                             

You'll see the structure that your compiler and linker produced — and what the kernel understands.


Even today, after years of building systems, I find it fascinating that an executable is more than just “something that runs.” It’s a carefully structured artifact — a contract that allows the kernel to map bytes on disk into a live process in memory.

From the ELF headers to the dynamic linker, from _start to main() — it’s all part of a beautifully designed machine.

Let’s keep learning how things really work under the hood.

Moon Hee Lee

Linux Systems Engineer | Linux kernel Bug Fixing

3mo

For the full series, see The Kernel In the Mind 🐧 https://www.linkedin.com/pulse/kernel-mind-moon-hee-lee-miwze

Like
Reply

To view or add a comment, sign in

Others also viewed

Explore topics