静态链接的可执行文件的启动代码会发出如此多的系统调用?

时间:2011-10-03 21:33:55

标签: c linux static-linking libc system-calls

我正在通过静态编译最小程序并检查已发出的系统调用来进行实验:

$ cat hello.c
#include <stdio.h>

int main (void) {
  write(1, "Hello world!", 12);
  return 0;
}

$ gcc hello.c -static

$ objdump -f a.out
a.out:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x00000000004003c0

$ strace ./a.out
execve("./a.out", ["./a.out"], [/* 39 vars */]) = 0
uname({sys="Linux", node="ubuntu", ...}) = 0
brk(0)                                  = 0xa20000
brk(0xa211a0)                           = 0xa211a0
arch_prctl(ARCH_SET_FS, 0xa20880)       = 0
brk(0xa421a0)                           = 0xa421a0
brk(0xa43000)                           = 0xa43000
write(1, "Hello world!", 12Hello world!)            = 12
exit_group(0)                           = ?

我知道,当非静态链接时,ld会发出启动代码以将libc.sold.so映射到流程的地址空间,ld.so会继续加载任何其他共享库。

但在这种情况下,除了execvewriteexit_group之外,为什么会发出如此多的系统调用?

为什么要uname(2)?为什么这么多调用brk(2)来获取和设置程序中断,以及调用arch_prctl(2)来设置进程状态,当这看起来像应该在内核空间中完成的那样,在{ {1}}时间?

2 个答案:

答案 0 :(得分:10)

需要

uname来检查内核版本是不是太古老了。

设置线程本地存储需要两个brk。设置动态加载程序路径需要另外两个(可执行文件仍然可以调用dlopen,即使它是静态链接的)。我不确定为什么这些成对出现。

在系统arch_prctl未调用时,set_thread_area将被调用。这为当前线程设置了TLS。

这些事情可能是懒惰地进行的(即第一次使用相应设施时调用)。但也许在性能方面没有任何意义(只是猜测)。

顺便说一下gdb-7.x可以使用catch syscall命令停止系统调用。

答案 1 :(得分:7)

无耻插件:当针对musl libc构建时,该程序静态链接或动态链接的strace是:

execve("./a.out", ["./a.out"], [/* 42 vars */]) = 0
write(1, "Hello world!", 12)            = 12
exit_group(0)                           = ?

如果您使用静态链接,或者使用uClibc和静态链接,只要您使用区域设置和高级stdio内容禁用了uClibc,它应该与dietlibc同样最小。 (出于某种原因,启用了这些功能的uClibc会运行大量启动代码来初始化它们,即使在不使用它们的程序中......)。据我所知,musl是唯一一个拥有动态链接器的人,它能够避免动态链接程序中繁重的启动系统调用开销。

至于为什么静态链接与glibc进行所有brk次调用,我真的不知道;你必须阅读来源。我怀疑它为malloc,stdio,locale以及可能是主线程的线程结构的内部数据结构分配空间。作为n.m.说,arch_prctl用于将线程寄存器设置为指向主线程的线程结构。这个可能被推迟到第一次访问(musl会这样做),但这样做有点痛苦并且会轻微地伤害性能。如果您关心大型程序的运行时间而不是许多小程序的启动时间,那么在程序加载时始终初始化线程寄存器可能是有意义的。请注意,内核无法为您设置它,因为它不知道应该设置的地址。

可能会对ELF格式进行扩展,以允许主线程结构位于.data部分,ELF头告诉内核它在哪里,但是libc之间需要杂技,链接器和内核可能会如此丑陋,以至于不希望这种优化......它们还会对线程的用户空间实现施加进一步的限制。