Компиляторы

Линковка и загрузка

Почему 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?

Связанные уроки

  • os-01-intro
Линковка и загрузка

0

1

Войти