Как ядро ​​получает исполняемый двоичный файл, работающий под 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

Другие вопросы по тегам