Динамічне зв’язування у Windows та Linux

У цій статті обговорюється концепція бібліотек, що розділяються як у Windows так і Linux, надається огляд різних структур даних, щоб пояснити, як працює динамічне зв'язування (linking) у цих системах

Переклад Олександр Тімаков

У цій статті обговорюється концепція бібліотек, що розділяються як у Windows так і Linux, надається огляд різних структур даних, щоб пояснити, як працює динамічне зв'язування (linking) в цих системах. Документ буде корисним для розробників, зацікавлених у забезпеченні безпеки, які потребують високої швидкості динамічного зв'язування, та передбачає наявність деяких попередніх знань про процес динамічного зв'язування.

У першій частині представлені основи роботи процедури у Windows та Linux, але фокус буде зміщений у бік Linux. Наступного разу, у другій частині статті, ми обговоримо, як здійснюється виконання проїдури зв'язування в Windows, а потім перейдемо до порівняння обох систем.

Статичні та динамічні бібліотеки.

У першому наближенні бібліотеки можна розділити на статичні та динамічні.

Статичні бібліотеки є набором об'єктних файлів і зазвичай мають розширення «.а» у UNIX-подібних ОС і «.lib» у Windows. Коли програма зв'язується зі статичною бібліотекою, машинний код з об'єктних файлів для кожної програми бібліотеки, що використовується програмою, копіюється з бібліотеки в кінцевий виконуваний файл.

Адресно-незалежний код (Win32 DLL і «.so»)

Динамічне зв'язування в Linux.

Структури даних в ELF

Глобальна таблиця зсувів (Global Offset Table).

Таблиця зв'язування процедур (Procedure Linkage Table).

У секції .dynamic, важливими для нас типамиє:

DT_NEEDED:Елемент містить зміщення в таблиці рядків, що закінчуються на NULL, рядки містять назви необхідних бібліотек. Це зсув є індексом для записів у таблиці DT_STRTAB.

DT_HASH:Елемент містить зміщення для хеш-таблиці символів, на яку вказує DT_SYMTAB.

Хеш - таблиця символів

Обидва масиви Buckets та Chain містять індекси таблиці символів. Для символу, який потрібно знайти, обчислюється хеш-функція і хеш %nBuckets використовується як індекс масиву bucket[]. Елемент з bucket[] містить індекс symindx як масиву chain[] так таблиці символів. Якщо елемент таблиці символів не підходить, відбувається вибірка наступного елемента таблиці символів з таким самим значенням хеш-функції з використанням індексу, отриманого з chain[symindex].

Як це працює

Ми маємо два методи для того, щоб вказати об'єкти, які потрібно завантажити заздалегідь: через змінну оточення LD_PRELOAD або через файл /etc/ld.so.preload. Останній може бути використаний, коли з міркувань безпеки ви не використовуєте змінні оточення. Завантажувач додає до вказаного списку попереднього завантаження бібліотеки DT_NEEDED (із заголовка файлу – прим. перев.).

Редактор зв'язків містить у пам'яті список вже пов'язаних (“linked”) бібліотек кожного файлу (з типом запису списку struct link_map, описаним параметром dl_loaded в struct rtld_global). Редактор використовує хеш-таблицю (DT_HASH) представлену в ELF-файлі, щоб прискорити процедуру пошуку символів.

Після завершення побудови списку пов'язаних бібліотек з усіма залежностями завантажувач звертається до кожної бібліотеки та обробляє її список зсувів, заповнюючи для бібліотеки глобальну таблицю зсувів(GOT) та проводячи необхідні зсуви.

Огляд процедури зв'язування за запитом.

Малюнок 1 . Вихідний код, дизассембльований gdb.

Давайте почнемо трасування з інструкції виклику, показаної на малюнку 1.

Адреса, вказана в інструкції, є записом у таблиці зв'язування процедур (PLT). Перші 4 записи PLT (з яких останні 2 зарезервовані) однакові для всіх викликів процедур. Інші згруповані в блоки по 3 записи, один блок на кожну процедуру. Це показано малюнку 2.

Малюнок 2 . Декілька записів на початку таблиці зв'язування процедур (PLT).

Малюнок 3 . Глобальна таблиця зсувів (GOT) зчитана з диска.

язування

Рисунок 4. Об'єкт у таблиці зміщень (довжина 8 байт).

язування

Малюнок 5. Точка зупинки (breakpoint).

Розглянемо її докладніше:

Погляньмо на ELF _ MACHINE _ RUNTIME _ TRAMPOLINE , визначений у dl - machine . h. У даному коді зберігаються регістри та робиться виклик функції fixup ():

У свою чергу, функція fixup визначена в dl-runtime.c. Масив l_info заданий усередині структури struct link_map містить індексовані покажчики на динамічну секцію.

З reloc-r_info отримаємо індекс вже для таблиці символів щоб, використовуючи його, отримати інформацію з таблиці (reloc-r_offset+1-l_addr).

Функція fixup() викликає _dl_lookup_symbol() для кожного запису у масиві бібліотек. Масив містить елементи типу r_scope_elem для бібліотек і становить частину загального поля пошуку. Ця структура заповнюється під час завантаження.

do_lookup визначено у FCT у файлі do-loopup.h. Поглянемо на цю процедуру так, ніби вона була написана звичайною англійською мовою:

Повернемося до fixup():

Повертаючись до dl-machine. h, вона відновлює збережені регістри:

Підбиваючи підсумки першої частини

У цій частині ми обговорили використання динамічного зв'язування для Linux та Windows, зупиняючись переважно на Linux. У другій частині ми ближче розглянемо цей процес у Windows у цьому ключі, згадаємо “ lazy linking ” (“зв'язування на запит”) і Delay Load Helper , та був спробуємо прискорити процес зв'язування обох системах. Залишайтеся на зв'язку.

  1. Linkers and Loaders by John Levine.
  2. Glibc-2.3.3 source code.
  3. Prelink by Jakub Jelinek, RedHat Inc
  4. Inside Windows An In-Depth виглядає в Portable Executable File format, part2
  5. The MSDN Library
  6. UNIX Man pages

Reji Thomas інженер-розробник із команди Symnatec Corp. Серед його інтересів математичні науки, комп'ютерна безпека, компілятори, створення фінансових моделей. Bhasker Reddy також залучений до розробки програмного забезпечення для Symantec Corp.; До його інтересів входять компілятори, системне програмування і теорія операційних систем.