Компиляторы
Линковка и загрузка
Почему lld линкует Chrome за 2 секунды, а GNU ld за 10? Почему `undefined symbol: _ZN3FooC1Ev` - это вовсе не ошибка кода, а вопрос порядка аргументов линковщику? Линковка - часто самая непонятная фаза сборки. Но именно она собирает миллионы строк кода в один файл.
- **lld** (LLVM linker) используется по умолчанию в Android NDK, iOS toolchain, Rust cross-compilation. В 3-5x быстрее GNU ld за счёт многопоточности и более умного алгоритма symbol resolution.
- **LD_PRELOAD jemalloc** использует Rust, Firefox, Cassandra для замены системного malloc. Просто установить `LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2` - и память выделяется через jemalloc без перекомпиляции.
- **Gold linker** от Google (2008) был создан специально для ускорения линковки Chrome - GNU ld слишком медленный для проекта с 30+ миллионами строк C++. Сегодня lld вытеснил gold.
Static Linking
Статическая линковка объединяет объектные файлы (.o) и статические библиотеки (.a) в один исполняемый файл. Все символы разрешаются во время линковки; результат - самодостаточный бинарник без внешних зависимостей в runtime.
lld (LLVM linker) в 3-5x быстрее GNU ld на больших C++ проектах. Chrome линкуется lld за ~2 секунды против ~10 секунд для GNU ld. mold (современный параллельный линковщик, 2021) ещё в 2-3x быстрее lld для очень больших проектов - использует параллельный алгоритм объединения секций. Rust использует lld по умолчанию при кросс-компиляции.
Почему статически слинкованный бинарник больше динамически слинкованного?
Dynamic Linking
Динамическая линковка разрешает символы в runtime: исполняемый файл содержит ссылки на shared libraries (.so/.dll), которые загружаются динамическим линковщиком при запуске. Экономит память (одна копия libc для всех процессов) и позволяет обновлять библиотеки без перекомпиляции.
LD_PRELOAD - переменная окружения позволяющая подставить свою реализацию функций вместо системных. `LD_PRELOAD=./mymalloc.so ./app` - все вызовы malloc пойдут в mymalloc.so. Используется для: профилирования (heaptrack), замены аллокатора (tcmalloc, jemalloc), тестирования (fake сетевые вызовы). Это возможно благодаря тому что PLT разрешает символы в runtime.
Что такое GOT (Global Offset Table) в контексте динамической линковки?
ELF Format
ELF (Executable and Linkable Format) - стандартный формат объектных файлов, исполняемых файлов и shared libraries на Linux/Unix. Структура: ELF header, Program headers (segments для загрузки), Section headers (секции для линковки).
ELF был разработан в AT&T Bell Labs в 1990-х и стал стандартом System V ABI. Интересная деталь: `.bss` секция (Block Started by Symbol) не занимает места в файле - только помечается как 'N байт нулей'. ОС выделяет нулевые страницы при загрузке. Это экономит размер бинарников: `static char buf[1024*1024]` в C не увеличит размер .o файла.
Почему `.bss` секция не занимает место в ELF файле?
Relocations
Relocation - процесс исправления адресов в объектном файле. Компилятор не знает финальных адресов функций и данных (может быть несколько .o файлов, .so загружается в произвольный адрес). Линковщик/loader применяет relocations - заменяет заглушки на реальные адреса.
ASLR (Address Space Layout Randomization) - случайный base address для .so в каждом процессе. Это безопасность: атаки типа ROP (Return-Oriented Programming) требуют знать адреса функций. PIC + ASLR = основа DEP+ASLR защиты на Linux. PIE (Position Independent Executable) расширяет это на сам исполняемый файл - по умолчанию включено в большинстве дистрибутивов. `gcc -fPIE -pie` для PIE бинарника.
Зачем нужен Position Independent Code (PIC) для shared libraries?
Итоги
- **Статическая линковка** включает код библиотек в бинарник. Самодостаточный файл, больший размер, нет runtime зависимостей.
- **Динамическая линковка** разрешает символы в runtime через PLT/GOT. LD_PRELOAD позволяет подменять функции. Экономия памяти через shared pages.
- **ELF** - стандартный формат на Linux: секции (.text, .data, .bss, .plt, .got), сегменты (загрузочные). `.bss` не хранится в файле - только размер.
- **Relocations** исправляют адреса. PIC использует RIP-relative addressing для позиционно-независимого кода - обязателен для .so с ASLR.
Связанные темы
Линковка - финальная фаза компиляции перед запуском:
- Целевые архитектуры — ABI и формат объектных файлов (ELF, Mach-O, PE) определяются целевой архитектурой и ОС
- Таблица символов — Линковщик объединяет таблицы символов из разных .o файлов - это extension symbol table из frontend
- JVM: архитектура — JVM загружает .class файлы с lazy linking - концептуально схоже с динамической линковкой
Вопросы для размышления
- Rust по умолчанию статически линкует stdlib, но не libc (на Linux). Почему полная статическая линковка с musl libc более портабельна - и какие компромиссы она несёт?
- LTO (Link-Time Optimization) позволяет оптимизировать через границы .o файлов - например, инлайнить функции из разных единиц компиляции. Как это меняет традиционную модель компиляции?
- ASLR рандомизирует базовый адрес .so. Как debugger (gdb) знает, по какому адресу загружена библиотека - и как source-level отладка работает при ASLR?