Executable Formats: ELF, Mach-O, PE
This chapter surveys the three dominant executable/object formats: ELF (Linux/Unix), Mach-O (macOS), and PE (Windows). They all implement the same core concepts — sections, symbols, relocations — with different layouts.
Why This Matters
Executable formats are the concrete representation of everything the compiler and linker produce. Knowing their structure helps you debug link/load issues, inspect binaries, and understand platform differences.
Prerequisites
c.build.2— sections, symbols, relocations.
Core Concept
All three formats share these ideas:
- a header describing the file;
- sections/segments holding code and data;
- a symbol table;
- relocations (for objects) or dynamic info (for executables);
- an entry point.
They differ in structure, terminology, and tooling.
Comparison
| Feature | ELF | Mach-O | PE |
|---|---|---|---|
| Platform | Linux, BSD, Unix | macOS, iOS | Windows |
| Object ext | .o | .o | .obj |
| Shared lib ext | .so | .dylib | .dll |
| Executable ext | (none) | (none) | .exe |
| Inspect tool | readelf, objdump | otool, llvm-objdump | dumpbin, llvm-readobj |
Examples
Inspecting an ELF binary
readelf -h app # header
readelf -S app # sections
readelf -l app # program headers (segments)
readelf -s app # symbols
Inspecting a Mach-O binary (macOS)
otool -l app
otool -L app # linked libraries
Inspecting a PE binary (Windows)
dumpbin /headers app.exe
dumpbin /imports app.exe
How It Works
The linker writes the executable in the platform's format, merging sections into loadable segments, recording the entry point, and (for dynamic executables) listing required shared libraries. The loader reads the header to map segments and begin execution.
Variations
Object vs. executable vs. shared
The same format is used for all three, with different flags and structures (e.g., an executable has an entry point; a shared library has dynamic relocations).
Position independence
PIE/PIC affect which relocations are needed and how the loader can randomize the load address (c.sec.5, c.link.2).
Common Mistakes
- Confusing sections (link-time) with segments (load-time).
- Using the wrong tool to inspect a binary format.
- Assuming a binary built on one platform runs on another.
Undefined Behavior
- None inherent to file formats; corrupt binaries are rejected by the loader
or crash at run time, outside ISO C's model.
Portability
- These formats are platform-specific; ISO C defines none of them.
Under the Hood
An ELF file has an ELF header, a section header table, and a program header table. Mach-O uses "load commands." PE uses a DOS header, a PE header, and sections. All map code/data to memory and record relocations.
Practical Usage
- Use the right inspection tool for the platform.
- Understand sections vs. segments when reading linker scripts and loader
errors.
Exercises
1. On Linux, compile a program and inspect it with readelf -h -S -l -s. 2. Identify the entry point and the .text/.data sections. 3. Compare the dynamic dependencies (ldd on Linux) with what readelf -d shows.
Deep Challenge
Explain how an ELF executable is loaded: program headers, segment mapping, entry point transfer, and how the dynamic loader resolves shared libraries. Relate this to c.link.3.
Related Concepts
c.build.2— sections/symbols/relocations.c.link.1— object files.c.link.3— dynamic loader.
References
- ELF specification, Mach-O documentation (Apple), PE format (Microsoft).
Verification
- Format descriptions match official specifications.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] ELF
- [ ] Mach-O
- [ ] PE
- [ ] Sections vs. segments
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.build.elf | 0 | 5 |
| c.build.macho | 0 | 4 |
| c.build.pe | 0 | 4 |