Бібліотеки, які спільно використовуються як виконувані файли

20 серпня 2022 р.

Зазвичай у вас є виконуваний файл або спільна бібліотека, але чи можете ви також мати виконувану спільну бібліотеку?

Заголовок файлу ELF має тег для типу об’єкта, який включає ET_EXEC або ET_DYN. Це означає, що файл ELF є виконуваним файлом або спільною бібліотекою. Однак на практиці багато виконуваних файлів насправді є об’єктами ET_DYN:

$ кіт привіт.c #зрозуміти void hello() { met("привіт"); } int main() { привіт(); привіт(); } $ gcc -o привіт привіт.c $./привіт привіт привіт $ файл привіт привіт: спільний об’єкт LSB 64-bit ELF, x86-64, версія 1 (SYSV), динамічно пов’язаний, інтерпретатор /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bec6318280e1ed6d7d271d870a7d22f589c233f2, для GNU/Linux 3.2 .0, без зачищення

Зверніть увагу, що у файлі показано спільний об’єкт, що означає, що виконуваний файл є об’єктом ET_DYN.

Причина в тому, що мій GCC налаштовано на ввімкнення -fpie за умовчанням.

Спроба посилання проти hello

Однак виконуваний файл hello не особливо корисний як спільна бібліотека, оскільки він не експортує символ hello:

$ nm --dynamic --defined-only привіт $# тут нічого.

Якщо ми скомпілюємо його за допомогою -shared, обидва символи будуть відкритими

$ gcc -o hello -shared hello.c $ nm --dynamic --defined-only привіт 0000000000001139 Привіт 0000000000001150 Головна Т

Основний символ у спільній бібліотеці виглядає дивно, але компонувальник не скаржиться:

$ cat main.c недійсний привіт(); int main(){ привіт(); } $ gcc -o main main.c -L. -l:привіт -Wl, -rpath,. $ ./основний привіт

Однак, вказавши -shared, ми значно втратили можливість запускати hello:

$./привіт Помилка сегментації (ядро скинуто) Надайте спільній бібліотеці програмний інтерпретатор

Одним із ефектів визначення -shared є те, що компонувальник не додаватиме програмний заголовок типу PT_INTERP до двійкового файлу. Цей заголовок програми вказує абсолютний шлях до інтерпретатора, яким зазвичай є динамічний компонувальник. Під час виконання файлу ELF ядро ​​аналізує файл ELF і шукає інтерпретатор. Коли він знаходить його, він фактично запускає інтерпретатор і надає йому дескриптор файлу ELF-файлу, щоб динамічний компонувальник міг продовжувати розбирати файл ELF, відображаючи відповідні його розділи.ci та його залежності в пам’яті перед виконанням точка входу.

Отже, щоб створити спільний виконуваний файл, нам потрібно буде вручну створити розділ PT_INTERP. У моєму випадку динамічний компонувальник знаходиться в /lib64/ld-linux-x86-64.so.2. Реєстрація відбувається наступним чином:

$ кіт привіт.c #зрозуміти const char interp[] __attribute__ ((section(".interp"))) = PT_INTERP; void hello() { met("привіт"); } int main() { привіт(); привіт(); } $ gcc -shared -o hello hello.c -D 'PT_INTERP="/lib64/ld-linux-x86-64.so.2"' $ файл привіт привіт: спільний об’єкт LSB 64-bit ELF, x86-64, версія 1 (SYSV), динамічно пов’язаний, інтерпретатор /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=492b8f47d786a0d3d7152e131fd2c096135f1e53, не видалено

Тепер у нас є спільна бібліотека з перекладачем. На жаль, цього недостатньо:

$./привіт Помилка сегментації (ядро скинуто) Визначення точки входу

Щоб виправити помилку segfault, нам потрібно повідомити компонувальнику, який символ використовувати як точку входу під час виконання:

$ gcc -shared -o hello -Wl,--entry,main hello.c $./привіт привіт привіт Помилка сегментації (ядро скинуто)

Тож це майже працює, за винятком segfault на виході. Причина в тому, що ми не маємо вихідного виклику:

$ кіт привіт.c #зрозуміти #зрозуміти const char interp[] __attribute__ ((section(".interp"))) = PT_INTERP; void hello() { met("привіт"); } int main() { привіт(); привіт(); вихід (EXIT_SUCCESS); } $ gcc -shared -o hello -Wl,--entry,main hello.c -D 'PT_INTERP="/lib64/ld-linux-x86-64.so.2"' $./привіт привіт привіт Знову пов’яжіть виконуваний файл із hello

Нарешті, давайте перевіримо, чи наш основний виконуваний файл можна зв’язати з виконуваним файлом hello:

$ gcc -o main main.c -L. -l:привіт -Wl, -rpath,. $ ldd головний | привіт привіт => ./привіт (0x00007fd419bb6000) $ ./основний привіт

Успіху!

Приклади в природі

Наразі я бачив два випадки, коли спільні бібліотеки використовуються як виконувані файли.

Перший і найбільш очевидний приклад — це сам динамічний компонувальник. Наприклад, динамічний компонувальник glibc дозволяє запускати виконуваний файл таким чином:

$ /lib64/ld-linux-x86-64.so.2 ./main привіт

Приклад використання полягає в тому, що ви можете запускати існуючі виконувані файли під новою версією glibc без необхідності змінювати PT_INTERP у виконуваному файлі.

Ще один цікавий випадок використання дійсно пов'язаний...

20 серпня 2022 р.

Зазвичай у вас є виконуваний файл або спільна бібліотека, але чи можете ви також мати виконувану спільну бібліотеку?

Заголовок файлу ELF має тег для типу об’єкта, який включає ET_EXEC або ET_DYN. Це означає, що файл ELF є виконуваним файлом або спільною бібліотекою. Однак на практиці багато виконуваних файлів насправді є об’єктами ET_DYN:

$ кіт привіт.c #зрозуміти void hello() { met("привіт"); } int main() { привіт(); привіт(); } $ gcc -o привіт привіт.c $./привіт привіт привіт $ файл привіт привіт: спільний об’єкт LSB 64-bit ELF, x86-64, версія 1 (SYSV), динамічно пов’язаний, інтерпретатор /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bec6318280e1ed6d7d271d870a7d22f589c233f2, для GNU/Linux 3.2 .0, без зачищення

Зверніть увагу, що у файлі показано спільний об’єкт, що означає, що виконуваний файл є об’єктом ET_DYN.

Причина в тому, що мій GCC налаштовано на ввімкнення -fpie за умовчанням.

Спроба посилання проти hello

Однак виконуваний файл hello не особливо корисний як спільна бібліотека, оскільки він не експортує символ hello:

$ nm --dynamic --defined-only привіт $# тут нічого.

Якщо ми скомпілюємо його за допомогою -shared, обидва символи будуть відкритими

$ gcc -o hello -shared hello.c $ nm --dynamic --defined-only привіт 0000000000001139 Привіт 0000000000001150 Головна Т

Основний символ у спільній бібліотеці виглядає дивно, але компонувальник не скаржиться:

$ cat main.c недійсний привіт(); int main(){ привіт(); } $ gcc -o main main.c -L. -l:привіт -Wl, -rpath,. $ ./основний привіт

Однак, вказавши -shared, ми значно втратили можливість запускати hello:

$./привіт Помилка сегментації (ядро скинуто) Надайте спільній бібліотеці програмний інтерпретатор

Одним із ефектів визначення -shared є те, що компонувальник не додаватиме програмний заголовок типу PT_INTERP до двійкового файлу. Цей заголовок програми вказує абсолютний шлях до інтерпретатора, яким зазвичай є динамічний компонувальник. Під час виконання файлу ELF ядро ​​аналізує файл ELF і шукає інтерпретатор. Коли він знаходить його, він фактично запускає інтерпретатор і надає йому дескриптор файлу ELF-файлу, щоб динамічний компонувальник міг продовжувати розбирати файл ELF, відображаючи відповідні його розділи.ci та його залежності в пам’яті перед виконанням точка входу.

Отже, щоб створити спільний виконуваний файл, нам потрібно буде вручну створити розділ PT_INTERP. У моєму випадку динамічний компонувальник знаходиться в /lib64/ld-linux-x86-64.so.2. Реєстрація відбувається наступним чином:

$ кіт привіт.c #зрозуміти const char interp[] __attribute__ ((section(".interp"))) = PT_INTERP; void hello() { met("привіт"); } int main() { привіт(); привіт(); } $ gcc -shared -o hello hello.c -D 'PT_INTERP="/lib64/ld-linux-x86-64.so.2"' $ файл привіт привіт: спільний об’єкт LSB 64-bit ELF, x86-64, версія 1 (SYSV), динамічно пов’язаний, інтерпретатор /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=492b8f47d786a0d3d7152e131fd2c096135f1e53, не видалено

Тепер у нас є спільна бібліотека з перекладачем. На жаль, цього недостатньо:

$./привіт Помилка сегментації (ядро скинуто) Визначення точки входу

Щоб виправити помилку segfault, нам потрібно повідомити компонувальнику, який символ використовувати як точку входу під час виконання:

$ gcc -shared -o hello -Wl,--entry,main hello.c $./привіт привіт привіт Помилка сегментації (ядро скинуто)

Тож це майже працює, за винятком segfault на виході. Причина в тому, що ми не маємо вихідного виклику:

$ кіт привіт.c #зрозуміти #зрозуміти const char interp[] __attribute__ ((section(".interp"))) = PT_INTERP; void hello() { met("привіт"); } int main() { привіт(); привіт(); вихід (EXIT_SUCCESS); } $ gcc -shared -o hello -Wl,--entry,main hello.c -D 'PT_INTERP="/lib64/ld-linux-x86-64.so.2"' $./привіт привіт привіт Знову пов’яжіть виконуваний файл із hello

Нарешті, давайте перевіримо, чи наш основний виконуваний файл можна зв’язати з виконуваним файлом hello:

$ gcc -o main main.c -L. -l:привіт -Wl, -rpath,. $ ldd головний | привіт привіт => ./привіт (0x00007fd419bb6000) $ ./основний привіт

Успіху!

Приклади в природі

Наразі я бачив два випадки, коли спільні бібліотеки використовуються як виконувані файли.

Перший і найбільш очевидний приклад — це сам динамічний компонувальник. Наприклад, динамічний компонувальник glibc дозволяє запускати виконуваний файл таким чином:

$ /lib64/ld-linux-x86-64.so.2 ./main привіт

Приклад використання полягає в тому, що ви можете запускати існуючі виконувані файли під новою версією glibc без необхідності змінювати PT_INTERP у виконуваному файлі.

Ще один цікавий випадок використання дійсно пов'язаний...

What's Your Reaction?

like

dislike

love

funny

angry

sad

wow