Отладка заняла немного больше времени, чем я ожидал.
Авария в:
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.