Код запуска статически связанного исполняемого файла выдает так много системных вызовов? - PullRequest
7 голосов
/ 04 октября 2011

Я экспериментирую, статически компилируя минимальную программу и изучая системные вызовы:

$ 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.so и ld.so в адресное пространство процесса, а ld.so продолжит загрузку любых других общих библиотек.

Но в этом случае почему так много системных вызовов, кроме execve, write и exit_group?

Почему, черт возьми uname(2)? Почему так много вызовов brk(2), чтобы получить и установить разрыв программы, и вызова arch_prctl(2), чтобы установить состояние процесса, когда это кажется чем-то, что должно было быть сделано в пространстве ядра, во время execve?

Ответы [ 2 ]

10 голосов
/ 04 октября 2011

uname необходим для проверки того, что версия ядра не слишком древняя.

Два brk s необходимы для настройки локального хранилища потока.Два других необходимы для установки динамического пути загрузчика (исполняемый файл может по-прежнему вызывать dlopen, даже если он статически связан).Я не уверен, почему они идут парами.

В системе arch_prctl не вызывается, set_thread_area вызывается вместо него.Это устанавливает TLS для текущего потока.

Эти вещи, вероятно, могут быть выполнены лениво (то есть вызваны, когда соответствующие средства используются в первый раз).Но, возможно, это не имеет смысла с точки зрения производительности (просто предположение).

Кстати, gdb-7.x может останавливаться на системных вызовах с помощью команды catch syscall.

7 голосов
/ 04 октября 2011

Бесстыдный плагин: при сборке с musl libc, для этой программы статическая связь или динамическая связь равна:

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

Она должна быть аналогично минимальной с dietlibc, если вы статическая ссылкаили с uClibc и статическим связыванием, если вы создали uClibc с отключенными локалями и расширенными возможностями stdio.(По какой-то причине uClibc с этими функциями запускает много кода запуска, чтобы инициализировать их даже в программах, которые их не используют ...)Насколько я знаю, однако, musl - единственный, кто имеет динамический компоновщик, способный избежать чрезмерных издержек системного вызова при запуске в программах с динамической связью.

Что касается , почему статическая связь с glibcделает все эти brk звонки, я действительно понятия не имею;Вы должны прочитать источник.Я подозреваю, что это выделяет пространство для внутренних структур данных для malloc, stdio, locale и, возможно, структуры потока для основного потока.Как сказал nm, arch_prctl предназначен для установки регистра потока, чтобы он указывал на структуру потока основного потока.Этот может быть отложен до первого доступа (что делает musl), но это немного болезненно и слегка ухудшает производительность.Если вы заботитесь о времени выполнения больших программ больше, чем время запуска многих и многих маленьких программ, возможно, имеет смысл всегда инициализировать регистр потока во время загрузки программы.Обратите внимание, что ядро ​​не может установить его для вас, потому что оно не знает адрес, который должен быть установлен.

Возможно, что расширение формата ELF может быть сделано для того, чтобы структура основного потока находилась в.data раздел с заголовком ELF, указывающим ядру, где оно находится, но акробатика, необходимая между libc, компоновщиком и ядром, вероятно, будет настолько уродливой, что сделает эту оптимизацию нежелательной ... Они также наложат дополнительные ограничения нареализация потоков в пользовательском пространстве.

...