Создание .so, которое также является исполняемым

Итак, все, вероятно, знают, что glibc /lib/libc.so.6 может быть запущен в оболочке, как обычный исполняемый файл, в случае которого он печатает информацию о своей версии и выходит. Это делается путем определения точки входа в .so. В некоторых случаях было бы интересно использовать это и для других проектов. К сожалению, низкоуровневая точка входа, которую вы можете установить с помощью параметра ld -e, немного слишком низка: динамический загрузчик недоступен, поэтому вы не можете вызвать какие-либо правильные функции библиотеки. glibc по этой причине реализует системный вызов write() через голый системный вызов в этой точке входа.

Теперь мой вопрос: может ли кто-нибудь подумать о том, как можно было бы загружать полный динамический компоновщик из этой точки входа, чтобы можно было получить доступ к функциям из других .so?

Ответ 1

Построение общей библиотеки с параметром -pie, как представляется, даст вам все, что вы хотите:

/* pie.c */
#include <stdio.h>
int foo()
{
  printf("in %s %s:%d\n", __func__, __FILE__, __LINE__);
  return 42; 
}
int main() 
{ 
  printf("in %s %s:%d\n", __func__, __FILE__, __LINE__);
  return foo(); 
}


/* main.c */
#include <stdio.h>

extern int foo(void);
int main() 
{ 
  printf("in %s %s:%d\n", __func__, __FILE__, __LINE__);
  return foo(); 
}


$ gcc -fPIC -pie -o pie.so pie.c -Wl,-E
$ gcc main.c ./pie.so


$ ./pie.so
in main pie.c:9
in foo pie.c:4
$ ./a.out
in main main.c:6
in foo pie.c:4
$

P.S. glibc реализует write(3) через системный вызов, потому что ему больше не нужно звонить (это уже самый низкий уровень). Это не имеет никакого отношения к возможности выполнить libc.so.6.

Ответ 2

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

Ответ 3

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

Ничто из этого не кажется мне "приятным". На самом деле просто мысль о чтении источников glibc (и исходный код ld-linux, как один пример) достаточно, чтобы оценить размер задания, который звучит довольно седой для меня. Это может быть и переносимость кошмара. Могут быть существенные различия между тем, как Linux реализует ld-linux и как выполняются связи в OpenSolaris, FreeBSD и т.д. (Я не знаю).