Project 7: Linker Error Simulator - The Linker Error Museum

Build a comprehensive collection of crafted source files that produce every common linker error, with explanations and debugging strategies.


Quick Reference

Attribute Value
Language C
Difficulty Intermediate (Level 3)
Time 1 Week
Chapters Expert C Programming Ch. 5
Coolness 3/5 - Educational Workhorse
Portfolio Value Interview Ready

Learning Objectives

By completing this project, you will:

  1. Identify all major linker error categories: Recognize undefined references, multiple definitions, and symbol conflicts instantly
  2. Understand symbol visibility and linkage: Explain extern, static, weak symbols, and how they affect linking
  3. Debug linker errors systematically: Use nm, readelf, objdump to trace symbol resolution failures
  4. Master library ordering rules: Know why library order matters and how to fix “undefined reference” errors that appear correct
  5. Distinguish static vs dynamic linking errors: Understand runtime symbol resolution failures vs link-time failures
  6. Apply best practices for symbol management: Design headers and source files to avoid common pitfalls

The Core Question You’re Answering

“What are all the ways linking can fail, and how do I debug each type of failure?”

Linking is the least understood phase of compilation. Most developers treat linker errors as mysterious incantations and resort to trial-and-error fixes. This project forces you to understand the linker’s symbol resolution algorithm so deeply that you can predict linker errors before they happen and fix them systematically rather than randomly.


Concepts You Must Understand First

Before starting this project, ensure you understand these concepts:

Concept Why It Matters Where to Learn
Object files and symbol tables Linker works with symbols, not source CS:APP 7.1-7.4
Declaration vs definition The root of most linker errors Expert C Ch. 4
extern keyword Controls symbol visibility across files K&R 4.4
static keyword (for linkage) Creates file-local symbols Expert C Ch. 5
Header file best practices Declarations go in headers, definitions in .c Any C book
Strong vs weak symbols How linker resolves conflicts CS:APP 7.6

Key Concepts Deep Dive

  1. Symbol Table Basics
    • What is a symbol and what information does the symbol table store?
    • What’s the difference between a defined symbol and an undefined symbol?
    • How can you view symbol tables with nm and readelf?
    • CS:APP Ch. 7.1-7.5
  2. Symbol Resolution Algorithm
    • How does the linker match undefined references to definitions?
    • What happens when there are multiple definitions?
    • What are strong vs weak symbols and how do they affect resolution?
    • CS:APP Ch. 7.6
  3. Library Ordering
    • Why does the order of .o and .a files matter on the link command line?
    • What is a “single pass” linker algorithm?
    • How do static libraries (.a) differ from object files (.o) in symbol resolution?
    • CS:APP Ch. 7.6.3
  4. Linkage in C
    • What does “external linkage” mean and which symbols have it?
    • What does “internal linkage” (static) mean?
    • How does “no linkage” apply to local variables?
    • C Standard section 6.2.2
  5. Dynamic Linking Considerations
    • How does symbol resolution differ at runtime vs link-time?
    • What is a symbol conflict in shared libraries?
    • How does LD_PRELOAD exploit symbol resolution?
    • CS:APP Ch. 7.10-7.12

Theoretical Foundation

The Linker’s Job

The linker performs two critical tasks:

  1. Symbol Resolution: Match every undefined symbol reference to exactly one symbol definition
  2. Relocation: Merge sections and fix up addresses
┌─────────────────────────────────────────────────────────────────────────────┐
│                         THE LINKING PROCESS                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   main.o                              math.o                                │
│   ────────                            ────────                              │
│   ┌──────────────────┐               ┌──────────────────┐                   │
│   │ DEFINED:         │               │ DEFINED:         │                   │
│   │   main (T)       │               │   add (T)        │                   │
│   │                  │               │   helper (t)     │  ← static        │
│   │ UNDEFINED:       │               │                  │                   │
│   │   add (U)        │──────────────▶│                  │                   │
│   │   printf (U)     │               │ UNDEFINED:       │                   │
│   └──────────────────┘               │   (none)         │                   │
│                                      └──────────────────┘                   │
│                                                                             │
│   Symbol Resolution:                                                        │
│   - 'add' in main.o ──────▶ matches 'add' in math.o ✓                       │
│   - 'printf' in main.o ───▶ matches 'printf' in libc ✓                      │
│   - 'helper' in math.o ───▶ NOT visible to other files (static)            │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Categories of Linker Errors

┌─────────────────────────────────────────────────────────────────────────────┐
│                         LINKER ERROR TAXONOMY                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. UNDEFINED REFERENCE                                                     │
│     └─ Symbol is used but never defined                                     │
│        └─ Missing .o file on command line                                   │
│        └─ Forgot to implement function                                      │
│        └─ Typo in function name                                             │
│        └─ Library not linked (-l flag missing)                              │
│        └─ Wrong library order                                               │
│                                                                             │
│  2. MULTIPLE DEFINITION                                                     │
│     └─ Symbol is defined more than once                                     │
│        └─ Definition in header (included multiple times)                    │
│        └─ Same function in multiple .c files                                │
│        └─ Strong symbol vs strong symbol conflict                           │
│                                                                             │
│  3. SYMBOL CONFLICT                                                         │
│     └─ Compatible but unexpected resolution                                 │
│        └─ Weak vs strong (weak loses, no error)                             │
│        └─ Common vs initialized (common loses, no error)                    │
│        └─ Type mismatch (compiles but crashes)                              │
│                                                                             │
│  4. LIBRARY ORDERING                                                        │
│     └─ Symbols exist but linker didn't find them                            │
│        └─ Library listed before the .o that needs it                        │
│        └─ Circular library dependencies                                     │
│                                                                             │
│  5. RUNTIME LINKING ERRORS                                                  │
│     └─ Symbol not found at load/run time                                    │
│        └─ Shared library not in LD_LIBRARY_PATH                             │
│        └─ Symbol version mismatch                                           │
│        └─ Wrong .so version                                                 │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Strong vs Weak Symbols

┌─────────────────────────────────────────────────────────────────────────────┐
│                         STRONG VS WEAK SYMBOLS                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  STRONG SYMBOLS:                                                            │
│  ───────────────                                                            │
│  - Functions                                                                │
│  - Initialized global variables                                             │
│                                                                             │
│  WEAK SYMBOLS:                                                              │
│  ─────────────                                                              │
│  - Uninitialized global variables (tentative definitions)                   │
│  - Functions declared with __attribute__((weak))                            │
│                                                                             │
│  RESOLUTION RULES:                                                          │
│  ─────────────────                                                          │
│  Rule 1: Multiple strong symbols → ERROR                                    │
│  Rule 2: One strong + any weak   → Strong wins (silently!)                  │
│  Rule 3: Multiple weak symbols   → Pick any (usually first)                 │
│                                                                             │
│  EXAMPLE:                                                                   │
│  ─────────                                                                  │
│  // file1.c                        // file2.c                               │
│  int x = 1;  // STRONG             int x = 2;  // STRONG                   │
│                                                                             │
│  Linking file1.o + file2.o → ERROR: multiple definition of 'x'             │
│                                                                             │
│  // file1.c                        // file2.c                               │
│  int x = 1;  // STRONG             int x;      // WEAK                      │
│                                                                             │
│  Linking file1.o + file2.o → OK! x = 1 (strong wins)                       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Library Ordering Matters

┌─────────────────────────────────────────────────────────────────────────────┐
│                         LIBRARY ORDERING ALGORITHM                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  The linker processes files LEFT TO RIGHT, maintaining:                     │
│  - E: Set of relocatable object files for the final executable             │
│  - U: Set of unresolved symbols                                             │
│  - D: Set of defined symbols                                                │
│                                                                             │
│  For each input file:                                                       │
│                                                                             │
│  If .o file:                                                                │
│    - Add all defined symbols to D                                           │
│    - Add all undefined symbols to U                                         │
│    - Add file to E                                                          │
│                                                                             │
│  If .a library:                                                             │
│    - For each archive member:                                               │
│      - If member defines a symbol in U:                                     │
│        - Add member to E                                                    │
│        - Update D and U with member's symbols                               │
│      - If member doesn't define any symbol in U:                            │
│        - SKIP IT                                                            │
│                                                                             │
│  PROBLEM:                                                                   │
│  ─────────                                                                  │
│  gcc main.o -lm -o prog    # WRONG: libm searched before main.o processed  │
│  gcc -lm main.o -o prog    # WRONG: same problem                            │
│                                                                             │
│  gcc main.o -lm            # Could work if libm.a member is pulled in      │
│                            # But usually: libm processed when U is empty!   │
│                                                                             │
│  gcc main.o -lm -o prog    # Often fails: "undefined reference to sin"     │
│                                                                             │
│  SOLUTION: Put libraries AFTER the objects that need them                   │
│                                                                             │
│  gcc main.o helper.o -lm -o prog   # Correct                                │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

The extern Keyword and Declaration vs Definition

┌─────────────────────────────────────────────────────────────────────────────┐
│                    DECLARATION VS DEFINITION                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  DECLARATION: Tells the compiler a symbol exists (produces no code/data)   │
│  DEFINITION:  Creates the actual storage or code for the symbol            │
│                                                                             │
│  For FUNCTIONS:                                                             │
│  ───────────────                                                            │
│  int foo(int x);           // Declaration (prototype)                       │
│  int foo(int x) { ... }    // Definition (has body)                         │
│                                                                             │
│  For VARIABLES:                                                             │
│  ──────────────                                                             │
│  extern int x;             // Declaration (extern required!)                │
│  int x;                    // Definition (tentative, weak symbol)           │
│  int x = 5;                // Definition (initialized, strong symbol)       │
│                                                                             │
│  HEADER FILE RULE:                                                          │
│  ─────────────────                                                          │
│  Headers should contain DECLARATIONS only.                                  │
│  One .c file should contain the DEFINITION.                                 │
│                                                                             │
│  // globals.h                                                               │
│  extern int counter;       // Declaration - goes in header                  │
│                                                                             │
│  // globals.c                                                               │
│  int counter = 0;          // Definition - goes in ONE .c file              │
│                                                                             │
│  // main.c                                                                  │
│  #include "globals.h"      // Gets declaration                              │
│  void increment(void) {                                                     │
│      counter++;            // Uses the symbol                               │
│  }                                                                          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Project Specification

What You Will Build

A “Linker Error Museum” - a collection of carefully crafted source files that demonstrate every common linker error, accompanied by:

  1. An explanation of WHY the error occurs
  2. The exact error message produced
  3. Debug commands to diagnose the issue
  4. The fix for each error

Functional Requirements

  1. Error Exhibit Collection (exhibits/):
    • At least 15 distinct linker error scenarios
    • Each exhibit in its own subdirectory
    • Makefile for each exhibit that reproduces the error
  2. Error Documentation (docs/):
    • Explanation of each error type
    • Debugging flowchart
    • Quick reference cheat sheet
  3. Interactive Guide (linker_museum):
    • CLI tool that lists all exhibits
    • Can attempt to build each exhibit and capture error
    • Shows explanation and debugging steps
  4. Debugging Toolkit (tools/):
    • Scripts to analyze symbol tables
    • Scripts to compare object files
    • Library dependency checker

Non-Functional Requirements

  • Works on Linux x86-64 (primary) and macOS (stretch goal)
  • Clear documentation for learning purposes
  • Each exhibit is self-contained and independently buildable
  • Error messages are captured accurately

Real World Outcome

When complete, you will have an educational tool that demonstrates linker errors:

$ ./linker_museum --list

================================================================================
                    THE LINKER ERROR MUSEUM
                 "Every Error, Explained"
================================================================================

EXHIBIT HALL: UNDEFINED REFERENCE ERRORS
─────────────────────────────────────────
  E01. basic_undefined         - Forgot to compile a source file
  E02. typo_in_name            - Misspelled function name
  E03. missing_library         - Library not linked (-l flag)
  E04. wrong_library_order     - Library before object file
  E05. static_function         - Called a static function from another file
  E06. cpp_mangling            - C++ name mangling mismatch
  E07. missing_vtable          - C++ virtual function not implemented

EXHIBIT HALL: MULTIPLE DEFINITION ERRORS
─────────────────────────────────────────
  E08. definition_in_header    - Function defined in .h file
  E09. duplicate_global        - Same variable in two files
  E10. include_guard_missing   - Header included multiple times
  E11. inline_not_static       - Inline function in header without static

EXHIBIT HALL: SILENT SYMBOL CONFLICTS
─────────────────────────────────────────
  E12. weak_vs_strong          - Uninitialized vs initialized global
  E13. type_mismatch           - Same name, different types
  E14. common_symbols          - Multiple tentative definitions

EXHIBIT HALL: LIBRARY ISSUES
─────────────────────────────────────────
  E15. circular_dependency     - Libraries that depend on each other
  E16. archive_member_unused   - Symbol in archive not pulled in
  E17. version_mismatch        - Shared library version conflict

EXHIBIT HALL: RUNTIME LINKING
─────────────────────────────────────────
  E18. missing_so              - Shared library not found at runtime
  E19. symbol_not_found_dlopen - Plugin symbol resolution failure
  E20. interposition_conflict  - LD_PRELOAD symbol override

================================================================================

$ ./linker_museum --exhibit E01

================================================================================
EXHIBIT E01: BASIC UNDEFINED REFERENCE
================================================================================

SCENARIO:
─────────
main.c calls a function defined in helper.c, but helper.c is not compiled.

SOURCE FILES:
─────────────
// main.c
extern int helper(int x);
int main(void) {
    return helper(42);
}

// helper.c
int helper(int x) {
    return x * 2;
}

ATTEMPTING BUILD:
─────────────────
$ gcc main.c -o program

ERROR PRODUCED:
───────────────
/usr/bin/ld: /tmp/ccXXXXXX.o: in function `main':
main.c:(.text+0x10): undefined reference to `helper'
collect2: error: ld returned 1 exit status

DIAGNOSIS:
──────────
The linker sees main.o contains:
  - DEFINED: main (T)
  - UNDEFINED: helper (U)

But helper is never provided.

$ nm main.o
0000000000000000 T main
                 U helper    <-- 'U' means undefined

DEBUGGING STEPS:
────────────────
1. Check if helper.o exists: ls *.o
2. Check symbol table: nm helper.o | grep helper
3. Verify helper is on command line: check your Makefile

FIX:
────
$ gcc main.c helper.c -o program   # Compile both files
# OR
$ gcc -c main.c && gcc -c helper.c && gcc main.o helper.o -o program

================================================================================

$ ./linker_museum --analyze exhibits/E01/
Analyzing object files in exhibits/E01/...

main.o symbol table:
  Symbol     Type   Bind     Section
  main       FUNC   GLOBAL   .text
  helper     NOTYPE GLOBAL   *UND*    <- UNDEFINED, needs resolution

Resolution status:
  helper: UNRESOLVED - no object file provides this symbol

Suggested fix: Add helper.c to compilation

Questions to Guide Your Design

Work through these questions BEFORE writing code:

  1. How will you organize the exhibit files so each error is reproducible in isolation?

  2. What metadata will you store for each exhibit (error type, difficulty, related exhibits)?

  3. How will you capture and format error messages from different compilers (GCC vs Clang)?

  4. For exhibits that require specific library configurations (like circular dependencies), how will you set those up portably?

  5. How will you demonstrate runtime linking errors (they require actually running code)?

  6. What’s the best way to show “silent” errors that don’t produce error messages but cause wrong behavior?

  7. How will you teach users to use nm, readelf, objdump, and ldd effectively?


Thinking Exercise

Before writing any code, analyze these scenarios on paper:

Scenario 1: Multiple Definition Mystery

// config.h
int debug_level = 0;  // Is this a problem?

// file1.c
#include "config.h"
void init(void) { debug_level = 1; }

// file2.c
#include "config.h"
void check(void) { if (debug_level) printf("debug\n"); }

Questions:

  • Will this link? Why or why not?
  • What if you add extern before int debug_level?
  • What if you add static before int debug_level?
  • What’s the correct way to share this variable?

Scenario 2: Library Order Puzzle

# You have: main.c uses sin() from libm, and helper() from helper.c
# helper.c also uses sqrt() from libm

gcc main.c helper.c -lm -o prog     # Works?
gcc main.c -lm helper.c -o prog     # Works?
gcc -lm main.c helper.c -o prog     # Works?

Trace through the linker’s algorithm for each command line.

Scenario 3: The Weak Symbol Trap

// file1.c
int counter;          // Uninitialized (weak)
void inc(void) { counter++; }

// file2.c
int counter = 100;    // Initialized (strong)
void dec(void) { counter--; }

// main.c
extern int counter;
extern void inc(void);
extern void dec(void);
int main(void) {
    printf("counter = %d\n", counter);
    inc();
    printf("counter = %d\n", counter);
    return 0;
}

Questions:

  • What does this print?
  • Is there a linker error?
  • What if both files had int counter = 0;?

Hints in Layers

Hint 1: Organizing Exhibits

Create a directory structure like:

exhibits/
├── E01_basic_undefined/
│   ├── main.c
│   ├── helper.c
│   ├── Makefile
│   └── README.md
├── E02_typo_in_name/
│   ├── ...

Each Makefile should have targets:

  • error: Attempt the build that produces the error
  • fix: The corrected build
  • analyze: Run nm/readelf to show symbols
Hint 2: Capturing Error Messages

GCC and Clang produce different error formats. Capture both:

# Capture stderr
gcc main.c 2> error.txt

# Normalize error messages for comparison
grep -E "undefined reference|multiple definition" error.txt

Consider storing expected error patterns as regexes for verification.

Hint 3: Creating Library Ordering Issues

To demonstrate library ordering, create your own static library:

# Create archive
ar rcs libhelper.a helper.o utils.o

# This fails (library before object):
gcc -L. -lhelper main.o -o prog

# This works (library after object):
gcc main.o -L. -lhelper -o prog
Hint 4: Runtime Linking Errors

For shared library errors:

# Create shared library
gcc -shared -fPIC -o libplugin.so plugin.c

# Compile main to use it
gcc main.c -L. -lplugin -o prog

# Run without proper path (error!)
./prog   # Error: cannot open shared object file

# Fix with LD_LIBRARY_PATH
LD_LIBRARY_PATH=. ./prog
Hint 5: The Museum CLI Tool

Structure your tool as:

struct exhibit {
    char *id;               // "E01"
    char *name;             // "basic_undefined"
    char *category;         // "UNDEFINED_REFERENCE"
    char *description;      // Short description
    char *directory;        // Path to exhibit
};

void list_exhibits(void);
void run_exhibit(const char *id);
void analyze_exhibit(const char *id);
void show_fix(const char *id);
Hint 6: Demonstrating Silent Conflicts

For weak/strong symbol conflicts that don’t produce errors:

// Show the "surprise" value:
// file1.c
int x = 1;

// file2.c
int x;  // Weak, will be merged with strong

// main.c
extern int x;
printf("x = %d\n", x);  // Will print 1, not 0!

Use nm to show symbol types:

  • B or b for BSS (weak, uninitialized)
  • D or d for Data (strong, initialized)

Solution Architecture

High-Level Design

┌─────────────────────────────────────────────────────────────────────────────┐
│                         LINKER ERROR MUSEUM                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐   ┌───────────────┐│
│  │   Exhibit    │   │    Build     │   │   Symbol     │   │   Report      ││
│  │   Manager    │──▶│   Runner     │──▶│   Analyzer   │──▶│   Generator   ││
│  └──────────────┘   └──────────────┘   └──────────────┘   └───────────────┘│
│         │                  │                  │                   │        │
│         ▼                  ▼                  ▼                   ▼        │
│  ┌─────────────────────────────────────────────────────────────────────────┐│
│  │                        exhibits/ directory                              ││
│  │  E01/  E02/  E03/  ...  E20/                                            ││
│  │   │     │     │          │                                              ││
│  │   ├─ main.c, helper.c, Makefile, README.md                              ││
│  │   └─ expected_error.txt, fix.md, analysis.md                            ││
│  └─────────────────────────────────────────────────────────────────────────┘│
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Key Components

Component Responsibility Implementation
Exhibit Manager Load and navigate exhibit metadata Parse exhibit directories, read READMEs
Build Runner Execute Makefile targets, capture output Fork/exec, redirect stderr
Symbol Analyzer Run nm/readelf, parse output Same as P01 or P06
Report Generator Format educational output Template-based text generation

Exhibit Structure

Each exhibit should contain:

exhibits/E01_basic_undefined/
├── Makefile              # Build rules with 'error' and 'fix' targets
├── README.md             # Explanation of this error type
├── main.c                # Primary source file
├── helper.c              # Secondary source (if needed)
├── expected_error.txt    # Error pattern to match
├── analysis.sh           # Script to show debugging commands
└── metadata.json         # Category, difficulty, related exhibits

Data Structures

typedef enum {
    ERROR_UNDEFINED_REFERENCE,
    ERROR_MULTIPLE_DEFINITION,
    ERROR_SYMBOL_CONFLICT,
    ERROR_LIBRARY_ORDER,
    ERROR_RUNTIME_LINKING
} ErrorCategory;

typedef struct {
    char *id;
    char *name;
    char *description;
    ErrorCategory category;
    int difficulty;  // 1-5
    char *directory;
    char **related;  // Related exhibit IDs
    char *fix_summary;
} Exhibit;

typedef struct {
    Exhibit *exhibits;
    size_t count;
} Museum;

// Symbol analysis for debugging display
typedef struct {
    char *name;
    char type;      // 'T', 'U', 'D', 'B', etc.
    char *section;
    int is_defined;
} Symbol;

Implementation Guide

Development Environment Setup

# Required tools
sudo apt-get install gcc gdb binutils build-essential

# Verify installations
gcc --version
nm --version
readelf --version

# Create project structure
mkdir -p linker_museum/{exhibits,docs,tools,src}
cd linker_museum

Project Structure

linker_museum/
├── src/
│   ├── main.c              # Entry point, CLI parsing
│   ├── exhibit.c           # Exhibit loading and management
│   ├── builder.c           # Build execution and error capture
│   ├── analyzer.c          # Symbol table analysis
│   └── report.c            # Output formatting
├── exhibits/
│   ├── E01_basic_undefined/
│   ├── E02_typo_in_name/
│   └── ... (20+ exhibits)
├── docs/
│   ├── debugging_guide.md
│   └── quick_reference.md
├── tools/
│   ├── analyze_symbols.sh
│   └── compare_objects.sh
├── Makefile
└── README.md

Implementation Phases

Phase 1: Create Core Exhibits (Days 1-3)

Goals:

  • Create at least 10 fundamental exhibits
  • Each with working Makefile and README

Exhibits to Create:

  1. E01 - Basic Undefined Reference
    // main.c - calls helper() which doesn't exist in compilation
    
  2. E02 - Typo in Function Name
    // main.c calls helpr(), helper.c defines helper()
    
  3. E03 - Missing Library
    // Uses sin() but no -lm flag
    
  4. E04 - Wrong Library Order
    # gcc -lm main.c  (wrong)
    # gcc main.c -lm  (right)
    
  5. E05 - Static Function Access
    // helper.c has: static int secret(void) { ... }
    // main.c tries to call secret()
    
  6. E08 - Definition in Header
    // config.h: int value = 42;  // included by multiple files
    
  7. E09 - Duplicate Global
    // file1.c: int count = 0;
    // file2.c: int count = 0;
    
  8. E12 - Weak vs Strong
    // file1.c: int x;      // weak
    // file2.c: int x = 5;  // strong, wins silently
    
  9. E13 - Type Mismatch
    // file1.c: int arr[100];
    // file2.c: extern int *arr;  // different type!
    
  10. E15 - Circular Dependency
    // liba needs symbol from libb
    // libb needs symbol from liba
    

Phase 2: Build the Museum CLI (Days 4-5)

Goals:

  • Interactive exhibit browser
  • Build and capture error functionality
  • Symbol analysis display

Tasks:

  1. Implement exhibit discovery (scan exhibits/ directory)
  2. Implement exhibit listing with categories
  3. Implement build runner with stderr capture
  4. Implement symbol analysis display
  5. Create formatted output for explanations

Phase 3: Add Advanced Exhibits (Days 6-7)

Goals:

  • Add runtime linking errors
  • Add C++ specific errors
  • Add shared library errors

Additional Exhibits:

  1. E06 - C++ Name Mangling
  2. E07 - Missing vtable
  3. E16 - Archive Member Not Pulled
  4. E17 - SO Version Mismatch
  5. E18 - Missing Shared Library at Runtime
  6. E19 - dlopen Symbol Not Found
  7. E20 - LD_PRELOAD Interposition

Testing Strategy

Test Categories

Category Purpose Examples
Error Reproduction Verify exhibits produce expected errors E01 should show “undefined reference to helper”
Fix Verification Verify fixes work E01 fix should compile and run
Cross-compiler Same error on GCC and Clang Compare error message formats
Platform Works on different Linux distros Ubuntu, Fedora, Alpine

Critical Test Cases

  1. Each exhibit reproduces its error
    for exhibit in exhibits/*/; do
        make -C "$exhibit" error 2>&1 | grep -q "error\|undefined\|multiple"
        echo "$exhibit: error reproduced"
    done
    
  2. Each exhibit fix compiles successfully
    for exhibit in exhibits/*/; do
        make -C "$exhibit" fix && echo "$exhibit: fix works"
    done
    
  3. Symbol analysis matches expected
    nm exhibits/E01/main.o | grep -q "U helper"
    

Common Pitfalls & Debugging

Frequent Mistakes

Pitfall Symptom Solution
Assuming linker processes all files “I added the library!” Check order: objects before libraries
Confusing declaration/definition Multiple definition error extern in header, definition in ONE .c
Forgetting -fPIC for shared libraries Relocation error Always use -fPIC for .so files
Static library ordering Undefined reference Put -l flags at the END
Mixing C and C++ Undefined reference (mangled name) Use extern “C” wrapper

Debugging Strategies

For Undefined Reference:

# 1. Find where symbol is defined
nm *.o | grep " T your_symbol"

# 2. Check if object is on command line
# Look at your gcc command

# 3. For libraries, check if symbol is there
nm -A /usr/lib/*.a 2>/dev/null | grep your_symbol

For Multiple Definition:

# 1. Find all definitions
nm *.o | grep " [DT] your_symbol"

# 2. Check headers for definitions
grep -r "int your_var =" *.h

# 3. Look for missing include guards
grep -L "#ifndef" *.h

For Library Order Issues:

# 1. Visualize dependencies
for lib in *.a; do
    echo "=== $lib needs: ==="
    nm $lib | grep " U "
done

# 2. Try different orderings
gcc main.o -lA -lB -lA  # Repeat if circular

Extensions & Challenges

Beginner Extensions

  • Add a “quiz mode” that shows an error and asks user to identify the cause
  • Create a cheat sheet PDF summarizing all error types
  • Add colorized output for error messages

Intermediate Extensions

  • Support for Clang-specific error messages
  • Integration with a build system (CMake) to show equivalent errors
  • Web interface for the museum

Advanced Extensions

  • Automated exhibit generation from Stack Overflow questions
  • Support for cross-compilation errors (ARM, etc.)
  • Integration with static analysis tools to predict linker errors before linking

Self-Assessment Checklist

Before considering this project complete, verify:

Understanding

  • I can explain the linker’s symbol resolution algorithm
  • I know the difference between strong and weak symbols
  • I understand why library order matters
  • I can predict when a linker error will occur
  • I know when to use extern, static, and inline

Implementation

  • At least 15 exhibits are working
  • Each exhibit reproduces its error reliably
  • Each exhibit has a working fix
  • Symbol analysis works for all exhibits
  • Documentation explains each error type clearly

Growth

  • I can debug linker errors in real projects now
  • I use nm and readelf confidently
  • I understand the difference between link-time and runtime errors

The Interview Questions They’ll Ask

After completing this project, you’ll be ready for these questions:

  1. “You get ‘undefined reference to foo’. How do you debug it?”
    • Check if foo.o is compiled: ls *.o
    • Check if foo is defined: nm *.o | grep foo
    • Check library order on command line
    • Check for name mangling (C vs C++)
  2. “Explain the difference between declaration and definition”
    • Declaration: tells compiler symbol exists (extern int x;)
    • Definition: creates storage (int x = 5;)
    • Functions: prototype vs body
    • Multiple declarations OK, one definition only
  3. “Why does library order matter in linking?”
    • Linker processes left-to-right
    • Archives only pull in members that resolve current undefined symbols
    • Object files always included, libraries are conditional
    • Solution: put libraries after objects that need them
  4. “What’s a weak symbol?”
    • Uninitialized globals (int x;)
    • Functions with attribute((weak))
    • Linker prefers strong over weak
    • Multiple weaks → linker picks one (usually first)
  5. “How would you debug a runtime linking error?”
    • ldd to check dependencies
    • LD_DEBUG=libs,symbols to trace resolution
    • Check LD_LIBRARY_PATH
    • Verify .so version matches

Real-World Connections

Industry Applications

  • Build Systems: Understanding linker behavior helps debug Bazel, CMake, Meson issues
  • Package Management: Why RPM/DEB dependencies exist
  • Container Images: Why static linking sometimes preferred
  • Security: Symbol interposition for monitoring/sandboxing
  • Plugin Systems: dlopen/dlsym symbol resolution
  • GNU ld: The linker you’re learning about
  • gold: Alternative linker (faster)
  • lld: LLVM linker (even faster)
  • mold: Modern linker (fastest)
  • patchelf: Modify ELF files post-link

Resources

Essential Reading

  • CS:APP Chapter 7: “Linking” - Complete coverage of symbol resolution
  • Expert C Programming Chapter 5: “Thinking About Linking”
  • “Linkers and Loaders” by John Levine: Definitive reference

Tools Documentation

  • man ld - Linker documentation
  • man nm - Symbol table lister
  • man readelf - ELF file analyzer
  • man ldd - Shared library dependencies

Books That Will Help

Topic Book Chapter
Symbol resolution CS:APP Ch. 7.6
Static libraries CS:APP Ch. 7.6.2
Dynamic linking CS:APP Ch. 7.10-7.12
C declarations Expert C Programming Ch. 4-5
Library creation Advanced C and C++ Compiling Ch. 4-6

Submission / Completion Criteria

Minimum Viable Completion:

  • 10 working exhibits with documented errors
  • CLI can list and run exhibits
  • Each exhibit has README with explanation
  • Basic symbol analysis works

Full Completion:

  • 15+ exhibits covering all major error categories
  • CLI provides full exhibit experience
  • Debugging guide documentation complete
  • Works on both GCC and Clang

Excellence (Going Above & Beyond):

  • 20+ exhibits including runtime and C++ errors
  • Quiz/interactive mode
  • Web interface
  • Contribution guide for adding exhibits
  • Cross-platform support (macOS, BSDs)

This project is part of the Expert C Programming Mastery series. For the complete learning path, see the project index.