Является ли реальная точка входа программой Linux в динамическом загрузчике и как показать ее в стеке вызовов?

Я думаю, что процесс запуска программы Linux, использующей разделяемую библиотеку (не скомпилированную с помощью -static), выглядит следующим образом:

(1) Введите имя исполняемого файла в bash

(2) Bash прочитал исполняемый файл и обнаружил, что он содержит секцию .interp, которая содержит имя динамического загрузчика.

(3) Bash разветвляет новый процесс, и в подпроцессе запускает динамический загрузчик и передает имя / путь исполняемого файла в качестве параметра динамическому загрузчику. Но я не уверен, как называется точка входа, в динамическом загрузчике нет _start, который /lib64/ld-linux-x86-64.so.2

(4) После возврата exec из ядра Linux в пользовательский режим первая инструкция, выполняемая в пользовательском режиме, должна быть точкой входа в динамический загрузчик.

(5) Динамический загрузчик загружает исполняемый файл (имя / путь которого передается в качестве параметра) и все его зависимости, а динамический загрузчик будет вызывать методы в разделе .init_array каждого загруженного модуля. В частности, методы в разделе .init_array в основном исполняемом файле также должны выполняться динамическим загрузчиком. Действительно, с точки зрения динамического загрузчика нет большой разницы между основным исполняемым файлом и разделяемыми библиотеками.

(6) Динамический загрузчик вызывает точку входа основного исполняемого файла _start. Это означает, что если точка входа динамического загрузчика также называется _start, с этого момента в стеке вызовов должно быть два _start.

Есть ли какие-либо проблемы в понимании вышеизложенного?

Но проблема в том, что при отладке программы с помощью gdb и использовании

set backtrace past-main
start
bt

Он показывает только обратную трассировку до _start, но как насчет стеков вызовов в динамическом загрузчике? Также пробовал:

set backtrace past-entry
start
bt

Теперь он ничего не показывает перед main ...


person jw_    schedule 08.04.2021    source источник


Ответы (1)


Есть ли проблема в вышеизложенном понимании?

Да, в основном это неправильно.

bash не заглядывает внутрь двоичного файла, он просто fork()s и exec()s его. ядро ​​ сопоставляет двоичный файл, обнаруживает, что в нем есть PT_INTERP сегмент (во время выполнения ничего не заботит разделы), сопоставляет интерпретатор и передает контроль в нем.

После возврата exec из ядра Linux в пользовательский режим первая инструкция, выполняемая в пользовательском режиме, должна быть точкой входа в динамический загрузчик.

Верный.

Динамический загрузчик загружает исполняемый файл

Это снова отчасти неверно: ядро ​​уже отобразило основной исполняемый файл.

Динамический загрузчик вызывает точку входа основного исполняемого файла _start. Это означает, что если точка входа динамического загрузчика также называется _start, с этого момента в стеке вызовов должно быть два _start.

Динамический загрузчик передает управление главной исполняемой точке входа. Передача управления не обязательно должна быть CALL, это может быть JMP, и в этом случае вам не следует ожидать, что вы найдете вызывающих a.out:_entry.

На x86_64 это происходит в _dl_start_user подпрограмме сборки (в sysdeps/x86_64/dl-machine.h файле), которая выглядит примерно так:

_dl_start_user:
    # Save the user entry point address in %r12.
    movq %rax, %r12
...
    # Jump to the user's entry point.
    jmp *%r12
person Employed Russian    schedule 08.04.2021