Как сделать общий объект linux (библиотека) выполненным самостоятельно?

Заметив, что gcc -shared создает исполняемый файл, я просто получил странную идею, чтобы проверить, что происходит, когда я пытаюсь запустить его... ну, результат был segfault для моей собственной библиотеки. Поэтому, будучи любопытным, я попытался "запустить" glibc (/lib/x86_64-linux-gnu/libc.so.6 в моей системе). Конечно, это не сбой, но предоставил мне некоторый результат:

GNU C Library (Debian GLIBC 2.19-18) stable release version 2.19, by Roland McGrath et al.
Copyright (C) 2014 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.8.4.
Compiled on a Linux 3.16.7 system on 2015-04-14.
Available extensions:
    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
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<http://www.debian.org/Bugs/>.

Итак, мой вопрос: что это за магия? Я не могу просто определить символ main в библиотеке - или я могу?

Ответ 1

Я написал сообщение в блоге на эту тему, в котором я углубляюсь, потому что я нахожу это интригующим. Вы можете найти мой оригинальный ответ ниже.


Вы можете указать пользовательскую точку входа для компоновщика с опцией -Wl,-e,entry_point для gcc, где entry_point - это имя "основной" функции библиотеки.

void entry_point()
{
    printf("Hello, world!\n");
}

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

Раздел .interp является частью результирующего двоичного файла, который необходим ОС для запуска приложения. Он устанавливается автоматически компоновщиком, если -shared не используется. Вы должны установить этот раздел вручную в коде C, если создаете разделяемую библиотеку, которую хотите выполнить самостоятельно. Смотрите этот вопрос.

Задача интерпретатора - найти и загрузить общие библиотеки, необходимые для программы, подготовить программу к запуску и запустить ее. Для формата ELF (вездесущий для современного * nix) в Linux используется программа ld-linux.so. Для получения дополнительной информации см. справочную страницу.

Строка ниже помещает строку в раздел .interp с использованием атрибутов GCC. Поместите это в глобальную область вашей библиотеки, чтобы явно указать компоновщику, что вы хотите включить динамический путь компоновщика в ваш двоичный файл.

const char interp_section[] __attribute__((section(".interp"))) = "/path/to/ld-linux";

Самый простой способ найти путь к ld-linux.so - запустить ldd в любом обычном приложении. Пример вывода из моей системы:

[email protected] ~ $ ldd $(which gcc)
    linux-vdso.so.1 =>  (0x00007fff259fe000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007faec5939000)
    /lib64/ld-linux-x86-64.so.2 (0x00007faec5d23000)

После того, как вы указали интерпретатор, ваша библиотека должна быть исполняемой! Есть только один небольшой недостаток: он будет зависать, когда вернется entry_point.

Когда вы компилируете программу с main, она не первая функция, которая вызывается при ее выполнении. main фактически вызывается другой функцией, которая называется _start. Эта функция отвечает за настройку argv и argc и другую инициализацию. Затем он вызывает main. Когда main возвращается, _start вызывает exit с возвращаемым значением main.

В _start нет адреса возврата в стеке, так как это первая вызываемая функция. Если он попытается вернуться, произойдет недопустимое чтение (что в конечном итоге приведет к ошибке сегментации). Это именно то, что происходит в нашей функции точки входа. Добавьте вызов exit в качестве последней строки вашей функции ввода, чтобы правильно очистить и не дать сбой.

example.c

#include <stdio.h>
#include <stdlib.h>

const char interp_section[] __attribute__((section(".interp"))) = "/path/to/ld-linux";

void entry_point()
{
    printf("Hello, world!\n");
    exit(0);
}

Скомпилируйте с gcc example.c -shared -fPIC -Wl,-e,entry_point.