Почему при использовании в качестве динамического компоновщика происходит аварийное завершение автономной программы на C - PullRequest
3 голосов
/ 14 апреля 2019

Следующая программа:

#include <stdio.h>

int main(int argc, char *argv[])
{
  for (int j = 0; j < argc; j++)
    printf("%d: %s\n", j, argv[j]);
  return 0;
}

встроенный в статически связанный пирог:

gcc -g -fpie main.c -static-pie -o ld.so

отлично работает:

$ ./ld.so foo bar
0: ./ld.so
1: foo
2: bar

Но когда я использую эту программу в качестве переводчика ELF для другой программы:

$ gcc -g main.c -Wl,-I./ld.so -o a.out

вылетает вот так:

gdb -q ./a.out
(gdb) run
Starting program: /tmp/a.out 

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7da84e2 in __ctype_init () at ctype-info.c:31
31    *bp = (const uint16_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_CLASS) + 128;
(gdb) bt
#0  0x00007ffff7da84e2 in __ctype_init () at ctype-info.c:31
#1  0x00007ffff7d9e3bf in __libc_init_first (argc=argc@entry=1, argv=argv@entry=0x7fffffffd728, envp=0x7fffffffd738) at ../csu/init-first.c:84
#2  0x00007ffff7d575cd in __libc_start_main (main=0x7ffff7d56e29 <main>, argc=1, argv=0x7fffffffd728, init=0x7ffff7d57ce0 <__libc_csu_init>, fini=0x7ffff7d57d70 <__libc_csu_fini>, rtld_fini=0x0, 
    stack_end=0x7fffffffd718) at ../csu/libc-start.c:244
#3  0x00007ffff7d56d6a in _start () at ../sysdeps/x86_64/start.S:120

Почему это?

Все вышеперечисленные адреса находятся внутри самого ./ld.so, поэтому он аварийно завершает свою работу во время собственной инициализации. Действительно, управление никогда не достигнет a.out, так как ld.so выходит.

1 Ответ

4 голосов
/ 14 апреля 2019

Отладка заняла немного больше времени, чем я ожидал.

Авария в:

Dump of assembler code for function __ctype_init:
   0x00007ffff7da84d0 <+0>:     mov    $0xffffffffffffffa0,%rax
   0x00007ffff7da84d7 <+7>:     mov    $0xfffffffffffffff0,%rcx
   0x00007ffff7da84de <+14>:    mov    %fs:(%rax),%rax
=> 0x00007ffff7da84e2 <+18>:    mov    (%rax),%rax
   0x00007ffff7da84e5 <+21>:    mov    0x40(%rax),%rsi

с $rax == 0. Когда ld.so сам проходит через этот код, $rax явно не NULL. Очевидно, что во время установки TLS что-то пошло не так, но что?

Оказывается, GLIBC инициализирует свой _dl_phdr из AT_PHDR во вспомогательном векторе, затем выполняет итерацию по всем Phdr s, чтобы найти один с типом PT_TLS.

Если его нет, GLIBC предполагает, что настройка TLS не требуется.

Когда ld.so запускается напрямую, предоставленный ядром вспомогательный вектор указывает на Phdr с для ld.so, PT_TLS присутствует, и все работает.

Но когда ld.so запускает косвенно в качестве интерпретатора для a.out, вспомогательный вектор указывает на Phdr с для a.out (а не для ld.so - это как задумано ). Поскольку a.out не имеет локальных переменных потока, он также не имеет сегмента PT_TLS.

Вывод: в настоящее время невозможно создать интерпретатор ELF с -static-pie и GLIBC, если только один из них не очень осторожен, чтобы избежать локального хранилища потоков. И избегать локального хранилища потоков в настоящее время тоже не представляется возможным: тривиальный int main() { return 0; } все еще имеет сегмент TLS, несмотря на то, что он вообще не использует что-либо из GLIBC.

...