The operating system loader is responsible for preparing executables or shared libraries for execution by loading them into memory, resolving dependencies, and configuring their execution environment. “

Loading Process for Executables

When an executable is run, the loader performs the following steps:

  • Parsing the ELF Header: The loader reads the ELF header to identify the file type (executable or shared object), target architecture, and the entry point for execution.
  • Interpreting the Program Header: The program header specifies memory segments to load, their offsets in the file, and permissions such as read-only, writable, or executable. Segments group contiguous sections to optimize memory usage.
  • Setting Up the Stack: The stack is initialized with program arguments (argv), environment variables (envp), and auxiliary data required by the runtime.
  • Jump to Entry Point: The loader transfers control to the program’s entry point (e_entry), where execution begins.

Handling Dynamic Linked Libraries

If the program relies on dynamically linked libraries, additional steps are performed by the dynamic linker (for runtime loading via dlopen(), see Dynamic Loading (dlopen)):

  • Resolving Dependencies: The dynamic linker identifies required shared libraries by parsing the .dynamic section of the ELF file.
  • Mapping Libraries: Libraries are mapped into the program’s virtual address space. Shared libraries are not globally loaded into a single shared memory space but instead mapped into each process’s memory.

Shared libraries (e.g., .so files) leverage shared physical memory to optimize resource usage:

  1. Code Sharing: The read-only .text section (containing machine instructions) of a shared library is mapped into multiple processes’ virtual memory but backed by the same physical memory pages. This ensures efficient memory usage.
  2. Writable Sections: Writable sections, like .data, use copy-on-write (COW). If a process modifies the data, a private copy is made, ensuring isolation.

Position-Independent Code (PIC)
Shared libraries are typically compiled as position-independent code, allowing them to be loaded at any address in memory. This flexibility avoids conflicts between multiple libraries and supports modern security features like Address Space Layout Randomization (ASLR).

Address Space Layout Randomization (ASLR)

ASLR enhances security by randomizing the memory locations of key regions, such as the stack, heap, and libraries. When used with PIE executables and PIC libraries, ASLR makes it harder for attackers to predict memory addresses, mitigating exploits like buffer overflows.