Skip to content

Latest commit

 

History

History
223 lines (170 loc) · 17.3 KB

File metadata and controls

223 lines (170 loc) · 17.3 KB

Динамически разделяемые библиотеки

1. Понятие библиотеки

Библиотека — это набор уже скомпилированного кода и данных (функций, процедур, констант, таблиц), который может использоваться несколькими программами.

Выделяют два основных типа библиотек:

  • Статически подключаемые библиотеки (static libraries, .a, .lib)
  • Динамически разделяемые библиотеки (shared / dynamic libraries, .so, .dll, .dylib)

2. Определение динамически разделяемой библиотеки

Динамически разделяемая библиотека (DRB) — это библиотека, код которой не встраивается в исполняемый файл на этапе линковки, а загружается в память отдельно и может разделяться (share) между несколькими процессами.

Примеры:

  • В Linux / Unix: файлы с расширением .so (например, libc.so)
  • В Windows: файлы с расширением .dll
  • В macOS: .dylib или .framework

Ключевые особенности:

  • Код DRB обычно загружается в момент запуска программы или во время её работы.
  • Один экземпляр кода библиотеки в памяти может использоваться несколькими процессами, что экономит RAM.
  • Связь с библиотекой происходит динамически — через механизмы загрузчика ОС и динамического линковщика.

3. Сравнение статических и динамически разделяемых библиотек

3.1. Статические библиотеки

  • Код библиотеки копируется в исполняемый файл на этапе линковки.
  • Исполняемый файл больше по размеру, но не требует внешних библиотек.
  • Изменение библиотеки не влияет на уже собранные программы (их нужно пересобирать заново, чтобы использовать новую версию).

3.2. Динамически разделяемые библиотеки

  • Код библиотеки не копируется внутрь исполняемого файла.
  • Вместо этого в исполняемом файле сохраняются символические ссылки на функции библиотеки (имена функций, таблицы импорта и т.д.).
  • При запуске программы ОС и динамический линковщик/загрузчик:
    • находят нужные библиотеки;
    • загружают их в память;
    • связывают адреса функций библиотеки с вызовами в программе.

Основные отличия:

Характеристика Статическая библиотека Динамическая библиотека
Подключение На этапе линковки При загрузке/во время выполнения
Размер исполняемого файла Большой Меньший
Обновление библиотеки Требуется пересборка программы Можно обновлять отдельно
Память (RAM) Код не разделяется Код разделяется между процессами
Зависимость от внешних файлов Нет (всё внутри .exe) Да (нужны .so/.dll)

4. Виды динамического связывания

4.1. Неявное (раннее) динамическое связывание

  • Библиотеки загружаются при старте программы.
  • Список необходимых библиотек и импортируемых функций хранится в специальных секциях исполняемого файла:
    • В Linux ELF: таблица динамических зависимостей (.dynamic), таблица импорта/экспорта символов.
    • В Windows PE: Import Table.
  • Если нужная библиотека не найдена, программа не запустится (ошибка загрузки).

Плюсы:

  • Простота использования.
  • Все зависимости проверяются при запуске.

Минусы:

  • Нельзя гибко выбирать, какие библиотеки подгрузить.
  • Ошибка при отсутствии библиотеки — программа вообще не запускается.

4.2. Явное (позднее) динамическое связывание

  • Программа сама явно загружает библиотеку во время выполнения.
  • В Linux:
    • dlopen() — загрузка библиотеки
    • dlsym() — получение адреса функции по имени
    • dlclose() — выгрузка библиотеки
  • В Windows:
    • LoadLibrary(), GetProcAddress(), FreeLibrary()

Плюсы:

  • Можно подгружать библиотеки по требованию (ленивая загрузка).
  • Возможна плагинная архитектура: программа загружает только те модули, которые нужны.
  • Можно выбирать библиотеку в зависимости от условий (настройки пользователя, ОС, версия).

Минусы:

  • Программист должен сам обрабатывать ошибки (нет библиотеки, нет функции).
  • Усложнение кода.

5. Механизм работы динамически разделяемых библиотек

5.1. Формат исполняемых файлов и библиотек

ОС использует специфичный формат:

  • Linux / Unix: ELF (Executable and Linkable Format)
  • Windows: PE (Portable Executable)
  • macOS: Mach-O

Файл динамической библиотеки содержит:

  • Машинный код функций.
  • Таблицу экспортируемых символов (что библиотека предоставляет).
  • Таблицу импортируемых символов (что самой библиотеке нужно от других библиотек).
  • Информацию для динамического линковщика (перемещения, зависимые библиотеки).

5.2. Динамический линковщик/загрузчик

При запуске программы:

  1. ОС читает заголовок исполняемого файла.
  2. Определяет список необходимых динамических библиотек.
  3. Передаёт управление динамическому линковщику (например, ld-linux.so в Linux).
  4. Линковщик:
    • находит файлы библиотек (/lib, /usr/lib, директории из LD_LIBRARY_PATH);
    • загружает их в виртуальную память процесса (обычно через mmap);
    • выполняет релокацию — корректирует адреса в коде/данных, чтобы указатели ссылались на правильные функции и переменные;
    • заполняет таблицы импорта (PLT/GOT в ELF).

После этого управление передаётся в main() программы.

Ключевые компоненты рантайма:

  • ld-linux.so* — динамический загрузчик, указан в сегменте INTERP; именно он ищет и мапит библиотеки.
  • libc.so (glibc/musl и т.п.) — стандартная библиотека, предоставляющая API и обёртки над системными вызовами.

5.3. Разделяемость кода

  • Код библиотеки загружается в память как разделяемая (read-only) область.
  • Разные процессы могут использовать один и тот же физический код через механизм страничной организации памяти.
  • Данные библиотеки (глобальные переменные) обычно не разделяются, у каждого процесса — свой экземпляр данных (копия в его адресном пространстве).

Это позволяет:

  • Сильно уменьшить суммарное потребление оперативной памяти.
  • Ускорить запуск программ (код может уже быть в памяти после запуска другой программы).

6. Преимущества динамически разделяемых библиотек

  1. Экономия памяти (RAM)
    • Один экземпляр кода DRB разделяется между многими процессами.
  2. Меньший размер исполняемых файлов
    • В программу не включается код библиотек.
  3. Обновляемость и сопровождение
    • Можно обновить библиотеку (исправить баги, улучшить безопасность), не пересобирая все программы.
    • В Windows это часто приводит к эффекту “обновили DLL — сразу все программы получили фикс”.
  4. Гибкость архитектуры
    • Поддержка модулей и плагинов.
    • Возможность загружать функциональность по требованию.
  5. Кроссплатформенность на уровне ABI
    • Можно компилировать разные программы (даже на разных языках), которые используют одну и ту же библиотеку через единый интерфейс (ABI).

7. Недостатки и проблемы динамических библиотек

  1. Зависимости во время исполнения
    • Программе нужны соответствующие версии библиотек в системе.
    • Если библиотека отсутствует или несовместима, программа не запустится (ошибки типа “missing DLL”).
  2. Версионные конфликты (DLL Hell)
    • Разные программы могут требовать разные версии одной и той же библиотеки.
    • Обновление или замена библиотеки может “сломать” старые программы.
    • Для борьбы с этим вводятся:
      • версионирование в имени файла (libpng16.so, vcruntime140.dll);
      • системные каталоги с политикой загрузки;
      • механизмы “side-by-side” в Windows.
  3. Немного более сложная загрузка
    • Время запуска программы может увеличиться (нужно загрузить и связать библиотеки).
  4. Сложность отладки
    • Ошибки могут проявляться только при определённых версиях библиотек.
    • Возможны проблемы при подмене библиотек, несовместимости ABI (Application Binary Interface).

8. Загрузка библиотек и поиск файлов

ОС и динамический линковщик используют определённые правила поиска библиотек.

8.1. Linux / Unix

Типичные места поиска:

  • Каталоги по умолчанию: /lib, /usr/lib, /lib64, /usr/lib64
  • Каталоги из конфигурации (/etc/ld.so.conf, ldconfig)
  • Переменная окружения LD_LIBRARY_PATH — задаёт дополнительные пути.
  • Путь, зашитый при линковке (rpath, runpath).

8.2. Windows

Поиск DLL обычно идёт:

  • В каталоге самого .exe.
  • В системных каталогах (System32 и т.п.).
  • В каталогах, указанных в переменной PATH.

8.3. Практические инструменты

  • ldd <бинарник> — показать, какие .so нужны исполняемому файлу и откуда они будут подхвачены. Фактически запускает бинарник через динамический загрузчик, поэтому небезопасно использовать на подозрительных файлах.
  • ldconfig и /etc/ld.so.conf.d/ — формируют кеш ld.so.cache для быстрого поиска библиотек.
  • LD_LIBRARY_PATH — временный способ добавить директории поиска (аккуратно применять в продакшене, чтобы не словить «DLL hell»).

8.4. Два «ld» и прямой запуск загрузчика

  • ld (link editor) из binutils используется на этапе сборки для склейки объектных файлов и библиотек.
  • Динамический загрузчик (/lib64/ld-linux.so.2 или аналогичный для архитектуры) можно вызвать напрямую, чтобы «ручками» запустить бинарник:
    /lib64/ld-linux.so.2 /usr/bin/echo. Это удобно для отладки путей и зависимостей.

9. Динамические библиотеки и безопасность

  • Динамические библиотеки — важная точка атаки:
    • Подмена библиотеки (DLL injection, DLL hijacking).
    • Загрузка библиотеки из ненадёжного пути.
  • Для защиты:
    • Используются цифровые подписи библиотек.
    • Жёстко фиксируются пути к системным библиотекам.
    • Применяются механизмы контроля целостности.

10. Итоги (краткий конспект для запоминания)

  1. Динамически разделяемая библиотека — это модуль кода, который загружается во время выполнения и разделяется между процессами.
  2. В отличие от статических библиотек:
    • не встраивается в .exe;
    • уменьшает размер программ;
    • позволяет обновлять функциональность без пересборки.
  3. Связывание может быть:
    • неявным (при старте программы);
    • явным (по требованию через dlopen, LoadLibrary и т.п.).
  4. Механизм работы основан на:
    • формате исполняемых файлов (ELF, PE);
    • динамическом линковщике;
    • таблицах импорта/экспорта и релокации.
  5. Плюсы: экономия памяти, обновляемость, модульность, плагинность.
  6. Минусы: зависимости от версий библиотек, возможные конфликты, усложнение отладки и безопасности.

Этот конспект можно использовать как готовый ответ на экзаменационный билет по теме «Динамически разделяемые библиотеки».