Почему и как работают некоторые общие библиотеки, как если бы они были исполняемыми файлами?

56

В 32-битных системах Linux, вызывая этот

$ /lib/libc.so.6

и на 64-битных системах это

$ /lib/x86_64-linux-gnu/libc.so.6

в оболочке, обеспечивает вывод как это:

GNU C Library stable release version 2.10.1, by Roland McGrath et al.
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 4.4.0 20090506 (Red Hat 4.4.0-4).
Compiled on a Linux >>2.6.18-128.4.1.el5<< system on 2009-08-19.
Available extensions:
    The C stubs add-on version 2.1.2.
    crypt add-on version 2.1 by Michael Glad and others
    GNU Libidn by Simon Josefsson
    Native POSIX Threads Library by Ulrich Drepper et al
    BIND-8.2.3-T5B
    RT using linux kernel aio
For bug reporting instructions, please see:
<http://www.gnu.org/software/libc/bugs.html>.

Почему и как это происходит, и как это можно сделать в других общих библиотеках?

Я посмотрел, /usr/libчтобы найти исполняемые файлы, и я нашел /usr/lib/libvlc.so.5.5.0. Запуск его привел к ошибке сегментации . : - /

HO1
источник
В дополнение ко всем следующим ответам, я помню, если вы установили бит x в разделяемой библиотеке, было (возможно, все еще) возможно загрузить его из исполняемого файла, даже если бит r очищен. Когда-то считалось хорошей практикой безопасности исключать из мира системные исполняемые файлы и библиотеки. Из-за широко распространенного открытого исходного кода это больше относится только к анонимному каталогу ftp / bin / ls. Для меня оставление набора битов х выглядит как образ старой практики.
Джошуа

Ответы:

53

Эта библиотека имеет main()функцию или эквивалентную точку входа и была скомпилирована таким образом, что она полезна как в качестве исполняемого файла, так и в качестве общего объекта.

Вот одно предложение о том, как это сделать, хотя у меня это не работает.

Вот еще один ответ на аналогичный вопрос о SO , который я бесстыдно плагиат, подправлю и добавлю немного объяснений.

Во-первых, источник для нашей библиотеки примеров test.c:

#include <stdio.h>                  

void sayHello (char *tag) {         
    printf("%s: Hello!\n", tag);    
}                                   

int main (int argc, char *argv[]) { 
    sayHello(argv[0]);              
    return 0;                       
}                   

Скомпилируйте это:

gcc -fPIC -pie -o libtest.so test.c -Wl,-E

Здесь мы компилируем разделяемую библиотеку ( -fPIC), но сообщаем компоновщику, что это обычный исполняемый файл ( -pie), и делаем свою таблицу символов экспортируемой ( -Wl,-E), чтобы с ней можно было с пользой связываться.

И, хотя fileон скажет, что это общий объект, он работает как исполняемый файл:

> ./libtest.so 
./libtest.so: Hello!

Теперь нам нужно посмотреть, действительно ли это может быть динамически связано. Пример программы program.c:

#include <stdio.h>

extern void sayHello (char*);

int main (int argc, char *argv[]) {
    puts("Test program.");
    sayHello(argv[0]);
    return 0;
}

Использование externизбавляет нас от необходимости создавать заголовок. Теперь скомпилируйте это:

gcc program.c -L. -ltest

Прежде чем мы сможем выполнить его, нам нужно добавить путь libtest.soдля динамического загрузчика:

export LD_LIBRARY_PATH=./

В настоящее время:

> ./a.out
Test program.
./a.out: Hello!

И ldd a.outпокажет связь с libtest.so.

Обратите внимание , что я сомневаюсь , что это как Glibc на самом деле составлена, так как это, вероятно , не так , как портативные сам Glibc (см man gccотношении к -fPICи -pieпереключателям), но она демонстрирует базовый механизм. Чтобы узнать подробности, вам нужно взглянуть на исходный make-файл.

лютик золотистый
источник
1
Отличный ответ, спасибо! :-) Я пытался использовать nmв общей библиотеке, но это была не отладочная версия. Итак, почему libvlcи другие терпят крах?
Ho1
1
Поскольку большинство разделяемых библиотек не предназначены для выполнения, GNU libcявляется исключением.
Златовласка
Я нашел два других: ldи libpthread.
Ho1
@ Ho1 ld.soособенный в других отношениях. В некоторой степени это более настоящий исполняемый файл, чем обычный динамически связанный исполняемый файл.
Random832
1
Вышеуказанные параметры, хотя и создают исполняемую-разделяемую библиотеку, но являются неполными в том смысле, что они отмечают ошибку, когда какой-то исполняемый файл пытается связаться с этим. Подробный пример ссылки добавлен здесь: unix.stackexchange.com/a/479334/152034
parasrish
21

Давайте погрузимся за ответ в случайном репозитории glibc в github. Эта версия предоставляет «баннер» в файле version.c.

В том же файле есть несколько интересных моментов: __libc_print_versionфункция, обеспечивающая печать на стандартный текст и символ, __libc_main (void)который задокументирован как точка входа. Так что этот символ вызывается при запуске библиотеки.

Так как же компоновщик / компилятор знает, что это именно функция точки входа?

Давайте погрузимся в make-файл . В флаге компоновщика есть интересный флаг:

# Give libc.so an entry point and make it directly runnable itself.
LDFLAGS-c.so += -e __libc_main

Так что это флаг компоновщика для установки точки входа в библиотеку. При сборке библиотеки вы можете предусмотреть -e function_nameдля компоновщика создание исполняемого поведения. Что это действительно делает? Давайте посмотрим на руководство (несколько устаревшее, но все еще действующее) :

Командный язык компоновщика включает в себя команду, специально предназначенную для определения первой исполняемой инструкции в выходном файле (ее точка входа). Его аргумент является именем символа:

ЗАПИСЬ (символ)

Как и при назначении символов, команда ENTRY может быть размещена либо как независимая команда в командном файле, либо среди определений разделов в команде SECTIONS - что бы ни имело никакого смысла для вашего макета.

ENTRY - это только один из нескольких способов выбора точки входа. Вы можете указать это любым из следующих способов (показано в порядке убывания приоритета: методы выше в списке переопределяют методы ниже вниз).

the `-e' entry command-line option;
the ENTRY(symbol) command in a linker control script;
the value of the symbol start, if present;
the address of the first byte of the .text section, if present;
The address 0. 

Например, вы можете использовать эти правила для генерации точки входа с оператором присваивания: если в ваших входных файлах не определено начало символа, вы можете просто определить его, присвоив ему соответствующее значение ---

начало = 0x2020;

В примере показан абсолютный адрес, но вы можете использовать любое выражение. Например, если ваши входные объектные файлы используют другое соглашение об именах символов для точки входа, вы можете просто присвоить значение любому символу, содержащему начальный адрес для начала:

start = other_symbol;

(текущую документацию можно найти здесь )

Реально ldкомпоновщик создает исполняемый файл с функцией точки входа, если вы предоставляете ему опцию командной строки -e(наиболее практичное решение), предоставляете символ функции startили вводите адрес символа в ассемблере.

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

IBr
источник
1
Если бы это был python, он бы предоставил модульные тесты.
Эрик Аронести