Почему автономная программа C hello завершает работу при использовании в качестве динамического компоновщика

#gcc #glibc #elf #dynamic-linking

#gcc #glibc #elf #динамическое связывание

Вопрос:

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

 #include <stdio.h>

int main(int argc, char *argv[])
{
  for (int j = 0; j < argc; j  )
    printf("%d: %sn", 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:

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

Сбой в:

 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 явно ненулевой. Очевидно, что-то пошло не так во TLS время установки, но что?

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

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

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

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

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

Комментарии:

1. Имя ld.so выглядит подозрительно. Почему вы используете это имя?

2. @wildplasser man7.org/linux/man-pages/man8/ld.so.8.html может помочь вам понять, почему я выбрал это имя.