$npx -y skills add mohitmishra786/low-level-dev-skills --skill linkers-ltoLinker and Link-Time Optimisation (LTO) skill. Use when configuring GNU ld, gold, or lld linker flags, diagnosing link-order issues or undefined symbols at link time, enabling LTO safely in real projects, or understanding inter-module optimisation trade-offs. Activates on queries
| 1 | # Linkers and LTO |
| 2 | |
| 3 | ## Purpose |
| 4 | |
| 5 | Guide agents through linker selection, common linker flags, link-order issues, LTO setup, and symbol-visibility management. |
| 6 | |
| 7 | ## Triggers |
| 8 | |
| 9 | - "I'm getting `undefined reference` at link time" |
| 10 | - "How do I enable LTO for a real project?" |
| 11 | - "Which linker should I use: ld, gold, or lld?" |
| 12 | - "How do I reduce binary size with `--gc-sections`?" |
| 13 | - "How do I write or understand a linker script?" |
| 14 | - "I have duplicate symbol or weak symbol issues" |
| 15 | |
| 16 | ## Workflow |
| 17 | |
| 18 | ### 1. Linker selection |
| 19 | |
| 20 | | Linker | Invocation | Strengths | |
| 21 | |--------|-----------|-----------| |
| 22 | | GNU ld (BFD) | default on Linux | Universal, stable | |
| 23 | | gold | `-fuse-ld=gold` | Faster than ld for C++; supports LTO plugins | |
| 24 | | lld (LLVM) | `-fuse-ld=lld` | Fastest, parallel, required for Clang LTO | |
| 25 | |
| 26 | ```bash |
| 27 | # Use lld with GCC or Clang |
| 28 | gcc -fuse-ld=lld -o prog ... |
| 29 | clang -fuse-ld=lld -o prog ... |
| 30 | |
| 31 | # Check which linker is used |
| 32 | gcc -v -o prog main.c 2>&1 | grep 'Invoking' |
| 33 | ``` |
| 34 | |
| 35 | ### 2. Essential linker flags |
| 36 | |
| 37 | ```bash |
| 38 | # Pass linker flags via compiler driver: |
| 39 | # -Wl,flag1,flag2 (comma-separated, no spaces) |
| 40 | # -Wl,flag1 -Wl,flag2 (separate -Wl options) |
| 41 | |
| 42 | gcc main.c -o prog \ |
| 43 | -Wl,-rpath,/opt/mylibs/lib \ # runtime library search path |
| 44 | -Wl,--as-needed \ # only link libraries that are actually used |
| 45 | -Wl,--gc-sections \ # remove unused sections (requires -ffunction-sections -fdata-sections) |
| 46 | -Wl,-z,relro \ # mark relocations read-only after startup |
| 47 | -Wl,-z,now \ # resolve all symbols at startup (full RELRO) |
| 48 | -L/opt/mylibs/lib -lfoo |
| 49 | ``` |
| 50 | |
| 51 | ### 3. Link order matters (GNU ld) |
| 52 | |
| 53 | GNU ld processes archives left-to-right. A library must come after the objects that need it. |
| 54 | |
| 55 | ```bash |
| 56 | # Wrong: libfoo provides symbols needed by main.o |
| 57 | gcc main.o -lfoo libdep.a -o prog # can fail if libdep.a needs libfoo |
| 58 | |
| 59 | # Correct: dependencies after dependents |
| 60 | gcc main.o -lfoo -ldep -o prog |
| 61 | |
| 62 | # If there are circular deps between archives: |
| 63 | gcc main.o -Wl,--start-group -lfoo -lbar -Wl,--end-group -o prog |
| 64 | # --start-group/--end-group: repeat search until no new symbols resolved |
| 65 | ``` |
| 66 | |
| 67 | ### 4. LTO with GCC |
| 68 | |
| 69 | ```bash |
| 70 | # Compile |
| 71 | gcc -O2 -flto -ffunction-sections -fdata-sections -c foo.c -o foo.o |
| 72 | gcc -O2 -flto -ffunction-sections -fdata-sections -c bar.c -o bar.o |
| 73 | |
| 74 | # Link (must pass -flto again) |
| 75 | gcc -O2 -flto -Wl,--gc-sections foo.o bar.o -o prog |
| 76 | |
| 77 | # Archives: must use gcc-ar / gcc-ranlib, not plain ar |
| 78 | gcc-ar rcs libfoo.a foo.o |
| 79 | gcc-ranlib libfoo.a |
| 80 | ``` |
| 81 | |
| 82 | Parallel LTO: |
| 83 | |
| 84 | ```bash |
| 85 | gcc -O2 -flto=auto foo.o bar.o -o prog # uses jobserver |
| 86 | gcc -O2 -flto=4 foo.o bar.o -o prog # 4 parallel jobs |
| 87 | ``` |
| 88 | |
| 89 | ### 5. LTO with Clang / lld |
| 90 | |
| 91 | ```bash |
| 92 | # Full LTO |
| 93 | clang -O2 -flto -fuse-ld=lld foo.c bar.c -o prog |
| 94 | |
| 95 | # ThinLTO (faster, nearly same quality) |
| 96 | clang -O2 -flto=thin -fuse-ld=lld foo.c bar.c -o prog |
| 97 | |
| 98 | # LTO with cmake: set globally |
| 99 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) # enables -flto |
| 100 | ``` |
| 101 | |
| 102 | ThinLTO caches work: subsequent builds that reuse unchanged modules are faster. |
| 103 | Cache location: specify with `-Wl,--thinlto-cache-dir=/tmp/thinlto-cache`. |
| 104 | |
| 105 | ### 6. Dead-code stripping |
| 106 | |
| 107 | ```bash |
| 108 | # Compile with per-function/per-data sections |
| 109 | gcc -O2 -ffunction-sections -fdata-sections -c foo.c -o foo.o |
| 110 | |
| 111 | # Link with garbage collection |
| 112 | gcc -Wl,--gc-sections foo.o -o prog |
| 113 | |
| 114 | # Verify what was removed |
| 115 | gcc -Wl,--gc-sections -Wl,--print-gc-sections foo.o -o prog 2>&1 | head -20 |
| 116 | ``` |
| 117 | |
| 118 | On macOS, the linker strips dead code by default (`-dead_strip`). |
| 119 | |
| 120 | ### 7. Symbol visibility |
| 121 | |
| 122 | Controlling visibility reduces DSO size and enables better LTO: |
| 123 | |
| 124 | ```bash |
| 125 | # Hide all symbols by default, export explicitly |
| 126 | gcc -fvisibility=hidden -O2 -shared -fPIC foo.c -o libfoo.so |
| 127 | |
| 128 | # In source, mark exports: |
| 129 | __attribute__((visibility("default"))) int my_public_function(void); |
| 130 | ``` |
| 131 | |
| 132 | Or use a version script: |
| 133 | |
| 134 | ```text |
| 135 | # foo.ver |
| 136 | { |
| 137 | global: my_public_function; my_other_public; |
| 138 | local: *; |
| 139 | }; |
| 140 | ``` |
| 141 | |
| 142 | ```bash |
| 143 | gcc -Wl,--version-script=foo.ver -shared -fPIC -o libfoo.so foo.o |
| 144 | ``` |
| 145 | |
| 146 | ### 8. Common linker errors |
| 147 | |
| 148 | | Error | Cause | Fix | |
| 149 | |-------|-------|-----| |
| 150 | | `undefined reference to 'foo'` | Missing library or wrong order | Add `-lfoo`; move after the object that needs it | |
| 151 | | `multiple definition of 'foo'` | Symbol defined in two TUs | Remove duplicate; use `static` or `extern` | |
| 152 | | `cannot find -lfoo` | Library not in search path | Add `-L/path/to/lib`; install dev package | |
| 153 | | `relocation truncated` | Address overflow in relocation | Use `-mcmodel=large`; restructure image | |
| 154 | | `version 'GLIBC_2.33' not found` | Binary needs newer glibc | Link statically or rebuild o |