Ответ 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
.