为什么独立的 C hello 程序在用作动态链接器时会崩溃
Why does a standalone C hello program crash when used as a dynamic linker
以下程序:
#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;
}
内置于静态链接的 PIE 中:
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
本身,所以在自己初始化的时候崩溃了。事实上,由于 ld.so
退出,控制永远不会达到 a.out
。
调试花费的时间比我预期的要长。
崩溃发生在:
Dump of assembler code for function __ctype_init:
0x00007ffff7da84d0 <+0>: mov [=10=]xffffffffffffffa0,%rax
0x00007ffff7da84d7 <+7>: mov [=10=]xfffffffffffffff0,%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 从辅助向量中的 AT_PHDR
初始化其 _dl_phdr
,然后遍历所有 Phdr
以寻找具有 PT_TLS
类型的.
如果没有,则 GLIBC 假定不需要 TLS
设置。
当ld.so
直接运行时,内核提供的辅助向量指向Phdr
s for ld.so
,PT_TLS
存在,一切正常。
但是当ld.so
作为a.out
的解释器间接运行时,辅助向量指向[=25=的Phdr
s ](而不是 ld.so
——这是设计的)。由于 a.out
没有任何线程局部变量,因此它也没有 PT_TLS
段。
结论:目前无法使用 -static-pie
和 GLIBC 构建 ELF
解释器,除非非常小心地避免线程本地存储。并且避免线程本地存储目前似乎也不是一个选项:一个微不足道的 int main() { return 0; }
仍然有一个 TLS
段,尽管根本没有使用 GLIBC 中的 anything。
以下程序:
#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;
}
内置于静态链接的 PIE 中:
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
本身,所以在自己初始化的时候崩溃了。事实上,由于 ld.so
退出,控制永远不会达到 a.out
。
调试花费的时间比我预期的要长。
崩溃发生在:
Dump of assembler code for function __ctype_init:
0x00007ffff7da84d0 <+0>: mov [=10=]xffffffffffffffa0,%rax
0x00007ffff7da84d7 <+7>: mov [=10=]xfffffffffffffff0,%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 从辅助向量中的 AT_PHDR
初始化其 _dl_phdr
,然后遍历所有 Phdr
以寻找具有 PT_TLS
类型的.
如果没有,则 GLIBC 假定不需要 TLS
设置。
当ld.so
直接运行时,内核提供的辅助向量指向Phdr
s for ld.so
,PT_TLS
存在,一切正常。
但是当ld.so
作为a.out
的解释器间接运行时,辅助向量指向[=25=的Phdr
s ](而不是 ld.so
——这是设计的)。由于 a.out
没有任何线程局部变量,因此它也没有 PT_TLS
段。
结论:目前无法使用 -static-pie
和 GLIBC 构建 ELF
解释器,除非非常小心地避免线程本地存储。并且避免线程本地存储目前似乎也不是一个选项:一个微不足道的 int main() { return 0; }
仍然有一个 TLS
段,尽管根本没有使用 GLIBC 中的 anything。