Який системний виклик використовується для завантаження бібліотек в Linux?


23

У straceвисновках шляхи до бібліотек, до яких виконується виклик, знаходяться у викликах open(). Це системний виклик, який використовують виконувані файли, які динамічно пов'язані? Про що dlopen()? open()це не дзвінок, як я здогадався, зіграє роль у виконанні програм.

Відповіді:


33

dlopenце не системний виклик, це функція бібліотеки в бібліотеці libdl . У системі відображаються лише системні дзвінки strace.

На Linux та на багатьох інших платформах (особливо на тих, які використовують формат ELF для виконуваних файлів), dlopenреалізується шляхом відкриття цільової бібліотеки open()та відображення її в пам'яті mmap(). mmap()насправді важлива частина тут, це те, що включає в себе бібліотеку в адресний простір процесу, щоб процесор міг виконувати свій код. Але ви маєте до open()файлу, перш ніж зможете mmap()!


2
"mmap () - це дійсно важлива частина": І тоді динамічний лінкер повинен робити переїзди, ініціалізацію і так одне (але це не спостерігається на рівні системного виклику).
ysdx

1
Оскільки завантаження бібліотек здійснюється за допомогою функції бібліотеки, я вважаю, що доречно додати, що сам виконуваний файл і ld-linuxядро відображається у складі execveсистемного виклику.
kasperd

mmap відповідно до цієї відповіді. Зауважимо також, що після "відкриття" кожної бібліотеки перед викликом mmap читаються деякі (832) байти, я припускаю перевірити, чи справжня бібліотека.
Йоган

@kasperd Тож ядро ​​Linux знає про динамічний завантажувач? Чи викликає це при запуску програми? Або сама програма це робить? Якщо останній, як інший виконуваний файл має доступ до пам'яті програми?
Мелаб

@Melab Так, ядро ​​знає про динамічний лінкер. Ядро прочитає шлях до динамічного лінкера з заголовка виконуваного файлу. І ядро ​​буде відображати обидва в пам'яті. Я не знаю, чи вхідна точка, до якої спочатку керує передача ядра, знаходиться в лінкері чи виконується. Якби я його реалізував, я, ймовірно, мав би керування передачею ядра в точку входу в лінкері з зворотною адресою на стеку, що вказує на точку входу виконуваного файлу.
kasperd

5

dlopen не має нічого спільного з спільними бібліотеками, як ви думаєте про них. Існує два способи завантаження спільного об’єкта:

  1. Ви повідомляєте лінкер часу компіляції (ld, хоча зазвичай його називають через компілятор), що ви хочете використовувати функції певної спільної бібліотеки. При такому підході ви повинні знати, яким буде ім’я бібліотеки, коли запускається лінк часу компіляції, але ви можете викликати функції бібліотеки так, ніби вони були статично пов'язані з вашою програмою. Коли програма запущена, динамічний посилання для виконання часу (ld.so) буде викликаний безпосередньо перед викликом mainфункції та встановити процесний простір програми, щоб програма знайшла функції бібліотеки. Це включає open()в себе лубрарій, а потім mmap()його, після чого слід встановити кілька таблиць пошуку.
  2. Ви повідомляєте лінкер часу компіляції, з яким ви хочете зв’язатися libdl, з якого ви потім (використовуючи перший метод) можете викликати dlopen()іdlsym()функції. За допомогою dlopen ви отримуєте ручку до бібліотеки, яку потім можете використовувати за допомогою dlsym для отримання покажчика функції на певну функцію. Цей метод набагато складніший для програміста, ніж перший метод (оскільки вам доведеться робити налаштування вручну, а не змусити лінкер зробити це автоматично для вас), а також він є більш крихким (оскільки ви не отримуєте компіляцію -час перевіряє, що ви викликаєте функції з правильними типами аргументів, як ви отримуєте в першому методі), але перевага полягає в тому, що ви можете вирішити, який спільний об’єкт завантажувати під час виконання (або навіть взагалі завантажувати його), роблячи цей інтерфейс призначений для функціональності типу плагінів. Нарешті, інтерфейс dlopen також менш портативний, ніж інший спосіб, оскільки його механіка залежить від точної реалізації динамічного лінкера (звідси libtool'slibltdl, який намагається абстрагувати ці відмінності).

цікавий; тому динамічно завантажену бібліотеку краще називати динамічно зв'язаними бібліотеками, оскільки завантаження бінарних файлів у пам'ять не є важкою частиною, тому сенс використання в ній сенсу є. Коли я прошу завантажити динамічну бібліотеку, я насправді прошу зв’язати (або від’єднати) бібліотеку в (або поза) мого адресного простору.
Дмитро

4

Сьогодні більшість операційних систем використовують метод спільних бібліотек, запроваджений наприкінці 1987 року SunOS-4.0. Цей метод заснований на відображенні пам'яті через mmap ().

Зважаючи на той факт, що на початку 1990-х компанія Sun навіть пожертвувала старий код на основі a.out (Solaris в той час вже базувався на ELF) людям FreeBSD і що цей код згодом був переданий багатьом іншим системам (включаючи Linux) Ви можете зрозуміти, чому немає великої різниці між платформами.


3

ltrace -Sаналіз мінімального прикладу показує, що mmapвикористовується в glibc 2.23

У glibc 2.23, Ubuntu 16.04, працює latrace -Sна мінімальній програмі, яка використовує dlopen:

ltrace -S ./dlopen.out

показує:

dlopen("libcirosantilli_ab.so", 1 <unfinished ...>
SYS_open("./x86_64/libcirosantilli_ab.so", 524288, 06267650550)      = -2
SYS_open("./libcirosantilli_ab.so", 524288, 06267650550)             = 3
SYS_read(3, "\177ELF\002\001\001", 832)                              = 832
SYS_brk(0)                                                           = 0x244c000
SYS_brk(0x246d000)                                                   = 0x246d000
SYS_fstat(3, 0x7fff42f9ce30)                                         = 0
SYS_getcwd("/home/ciro/bak/git/cpp-cheat"..., 128)                   = 54
SYS_mmap(0, 0x201028, 5, 2050)                                       = 0x7f1c323fe000
SYS_mprotect(0x7f1c323ff000, 2093056, 0)                             = 0
SYS_mmap(0x7f1c325fe000, 8192, 3, 2066)                              = 0x7f1c325fe000
SYS_close(3)                                                         = 0
SYS_mprotect(0x7f1c325fe000, 4096, 1)                                = 0

тому ми відразу бачимо, що dlopenдзвінки open+ mmap.

Дивовижний ltraceінструмент відстежує як виклики бібліотеки, так і системні виклики, і тому ідеально підходить для вивчення того, що відбувається в даному випадку.

Більш детальний аналіз показує, що openповертає дескриптор файлу 3(наступний вільний після stdin, out та err).

readпотім використовує цей дескриптор файлів, але TODO, чому mmapаргументи обмежені чотирма, і ми не можемо побачити, який fd там використовувався, оскільки це 5-й аргумент . straceпідтверджує, як очікувалося, 3це один, і порядок Всесвіту відновлюється.

Хоробрі душі також можуть вписатись у код glibc, але я не зміг знайти mmapпісля швидкого грепу і я лінивий.

Випробуваний на цьому мінімальному прикладі з побудовою котлопластину на GitHub .


2

straceзвіти про системні виклики (тобто функції, реалізовані безпосередньо ядром). Динамічні бібліотеки не є функцією ядра; dlopenє частиною бібліотеки С, а не ядром. Реалізація dlopenвиклику call open(який є системним викликом) для відкриття бібліотечного файлу, щоб його можна було прочитати.


5
Виклики з бібліотеки можна побачити за допомогою ltrace.
kasperd

@kasperd ltrace -Sідеально аналізує це, оскільки він також показує системні дзвінки
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.