Tale of Executable programs

Published on: April 18, 2025

Tale of Executable programs

This article explains how a piece of code transforms from being written in your code editor to being executed by the CPU, focusing on the journey up to memory loading.

From Code to Executable

You start by writing source code in a human-readable programming language of your choice. What happens next depends on whether you’re using a compiled or interpreted language:

  • Compiled Language: A compiler translates the source code into low-level machine code (sequences of 0s and 1s). This machine code, combined with data like initial values of global variables, memory offsets, and metadata, is packaged into an executable file, commonly an ELF file on Linux.
  • Interpreted Language: An interpreter translates the source code into machine code dynamically, executing it one statement at a time.

Since this series is focused on Go, a compiled language, we’ll focus on the compilation process.

Executable File Sections

An executable file, such as an ELF file, is organized into distinct sections:

  • .text: Contains the machine code instructions.
  • .data: Stores initialized global and static variables.
  • .bss: Holds uninitialized global and static variables.
  • Metadata: Includes file information, symbols, and references to shared libraries.

For a deeper dive into ELF files, check out this article.

Launching the Program

Once the executable is ready, the operating system (OS) steps in to run it. Here’s the process:

1. Request

You initiate execution by double-clicking the file or running it via the command line.

2. Process Creation

The OS creates a process, assigning it a unique Process ID (PID). A process is an instance of the program in execution, managed by the OS to allocate resources like memory and CPU time.

3. Virtual Address Space

The OS assigns the process a private virtual address space, a large, linear range of memory addresses starting at 0. These addresses are virtual—an abstraction mapped to physical memory by the OS.

Why Virtual Address Space?

  • Isolation: Ensures processes don’t interfere with each other’s memory, preventing errors or security issues.
  • Mapping: The OS, aided by the CPU’s Memory Management Unit (MMU), translates virtual addresses to physical ones for efficient memory use.

4. Loading

The OS loader reads the ELF file’s sections (e.g., .text, .data) and places them into the process’s virtual address space, preparing them for CPU access and execution.

5. Dynamic Linking

If you are working with a programming language like C, linking process becomes very crucial. Whenever you write a software, you often rely on external libraries and packages. For example, you use #include<stdio.h> pre-processor, that makes sure your compiled software also includes the entire code present in the stdio.h header file.

Then, while writing your code, you can use functions like printf which are provided by the stdio.h header file. It is actually at the linking step, that all the external libraries and dependencies are loaded into the virtual memory, to make sure the actual implementation of the printf function is available when it is used anywhere.

This concludes the life-cycle of a program all the way from being written to being loaded into the memory.

Next articles in the series go more in-depth. They are about how the execution works. This would involve understanding things like

  • Threads and their execution flow.
    • Single Threaded vs Multi threaded applications
    • Thread components including program counter, temporary storage, CPU registers, Stack etc.
    • Shared resources: How resources are shared between multiple threads running under the same process.
    • Multi-threading and Go Routines.
    • CPU scheduling algorithms, interrupts and time sharing.
  • Execution Stack in detail.
    • Managing function calls, passing arguments and handling returns.
    • Application Binary Interface (ABI) of a language.
    • Stack Frame.
  • The Heap in detail (Dynamic Memory).