Как ядро получает исполняемый двоичный файл, работающий под Linux?
Как ядро получает исполняемый двоичный файл, работающий под Linux?
Это кажется простым вопросом, но кто-нибудь может помочь мне копать глубоко? Как файл загружается в память и как начинается выполнение кода?
Может кто-нибудь помочь мне и рассказать, что происходит, шаг за шагом?
4 ответа
Лучшие моменты exec
системный вызов в Linux 4.0
fs/exec.c
определяет системный вызов вSYSCALL_DEFINE3(execve
Просто вперед
do_execve
,do_execve
Вперед к
do_execveat_common
,do_execveat_common
Чтобы найти следующую основную функцию, проследите, когда возвращаемое значение
retval
последнее изменение.Начинает строить
struct linux_binprm *bprm
описать программу и передать ееexec_binprm
выполнить.exec_binprm
Еще раз, следуйте возвращаемому значению, чтобы найти следующий главный вызов.
search_binary_handler
Обработчики определяются первыми магическими байтами исполняемого файла.
Два наиболее распространенных обработчика - это те, что для интерпретируемых файлов (
#!
магия) и для эльфов (\x7fELF
волшебство), но есть и другие встроенные в ядро, напримерa.out
, И пользователи могут также зарегистрировать свои собственные, хотя / proc / sys / fs / binfmt_miscОбработчик ELF определяется в
fs/binfmt_elf.c
,См. Также: Почему люди пишут #! / Usr / bin / env pyb shebang в первой строке скрипта Python?
formats
список содержит все обработчики.Каждый файл обработчика содержит что-то вроде:
static int __init init_elf_binfmt(void) { register_binfmt(&elf_format); return 0; }
а также
elf_format
этоstruct linux_binfmt
определяется в этом файле.__init
является магией и помещает этот код в магическую секцию, которая вызывается при запуске ядра: что означает __init в коде ядра Linux?Инъекция зависимостей на уровне линкера!
Существует также счетчик рекурсии, если интерпретатор выполняет себя бесконечно.
Попробуй это:
echo '#!/tmp/a' > /tmp/a chmod +x /tmp/a /tmp/a
Еще раз мы следуем возвращаемому значению, чтобы увидеть, что будет дальше, и увидеть, что это происходит из:
retval = fmt->load_binary(bprm);
где
load_binary
определяется для каждого обработчика в структуре: полиморфизм C-стиля.
fs/binfmt_elf.c:load_binary
Фактическая работа:
- анализирует файл ELF согласно спецификации
- устанавливает начальное состояние программы процесса на основе проанализированного ELF (память в
struct linux_binprm
регистрируется вstruct pt_regs
) - вызов
start_thread
, где это действительно может начать планироваться
ТОДО: продолжить исходный анализ. Что я ожидаю, что произойдет дальше:
- ядро анализирует заголовок INTERP в ELF, чтобы найти динамический загрузчик (обычно устанавливается
/lib64/ld-linux-x86-64.so.2
). - ядро отображает динамический загрузчик и ELF, которые будут выполнены в память
- динамический загрузчик запускается, принимая указатель на ELF в памяти.
- теперь в userland загрузчик как-то разбирает заголовки эльфов и делает
dlopen
на них dlopen
использует настраиваемый путь поиска, чтобы найти эти библиотеки (ldd
и друзья), отобразить их в памяти и как-то сообщить эльфу, где найти пропавшие символы- загрузчик вызывает
_start
эльфа
Два системных вызова из ядра Linux являются актуальными. Системный вызов fork (или, возможно, vfork
или же clone
) используется для создания нового процесса, похожего на вызывающий (каждый пользовательский процесс Linux, кроме init
создан fork
или друзья). Системный вызов execve заменяет адресное пространство процесса новым (по существу, путем сортировки сегментов mmap из исполняемого ELF-сегмента и анонимных сегментов с последующей инициализацией регистров, включая указатель стека). Дополнение ABI x86-64 и сборка Linux дают подробности.
Динамическое связывание происходит после execve
и включает в себя /lib/x86_64-linux-gnu/ld-2.13.so
файл, который для ELF рассматривается как "переводчик".
После прочтения уже упомянутых документов ELF, вы должны просто прочитать код ядра, который на самом деле это делает.
Если у вас возникли проблемы с пониманием этого кода, создайте UML Linux, и вы можете просмотреть этот код в отладчике.
Вы можете начать с понимания форматов исполняемых файлов, таких как ELF. http://en.wikipedia.org/wiki/Executable_and_Linkable_Format
Файл ELF содержит несколько разделов с заголовками, которые описывают, как и где части двоичного файла должны быть загружены в память.
Затем я предлагаю прочитать информацию о части linux, которая загружает двоичные файлы и обрабатывает динамические ссылки, ld-linux. Это также хорошее описание ld-linux: http://www.cs.virginia.edu/~dww4s/articles/ld_linux.html