Ответ 1
Лучшие моменты системного вызова exec
в Linux 4.0
Лучший способ выяснить это - пошаговая отладка ядра GDB с помощью QEMU: Как отладить ядро Linux с помощью GDB и QEMU?
-
fs/exec.c
определяет системный вызов вSYSCALL_DEFINE3(execve
Просто пересылается в
do_execve
. -
do_execve
do_execveat_common
кdo_execveat_common
. -
do_execveat_common
Для того, чтобы найти следующую важную функцию, отслеживать, когда возвращаемое значение
retval
является последней модификацией.Начинает
struct linux_binprm *bprm
для описания программы и передает ее вexec_binprm
для выполнения. -
exec_binprm
Еще раз, следуйте возвращаемому значению, чтобы найти следующий основной вызов.
-
search_binary_handler
-
Обработчики определяются первыми магическими байтами исполняемого файла.
Двумя наиболее распространенными обработчиками являются обработчики для интерпретируемых файлов (
#!
Magic) и для ELF (\x7fELF
magic), но есть и другие встроенные в ядро, например,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 в памяти.
- Теперь в пространстве пользователя, загрузчик как - то разбирает эльф заголовки, и не
dlopen
на них -
dlopen
использует настраиваемый путь поиска, чтобы найти эти библиотеки (ldd
и friends), отобразить их в памяти и каким-то образом сообщить ELF, где найти пропущенные символы - загрузчик вызывает
_start
-
в противном случае ядро загружает исполняемый файл в память напрямую, без динамического загрузчика.
Поэтому он должен, в частности, проверить, является ли исполняемый файл PIE или нет, если он помещает его в память в произвольном месте: какова опция -fPIE для независимых от позиции исполняемых файлов в gcc и ld?