Как ядро ​​получает исполняемый двоичный файл, работающий под Linux? - PullRequest
25 голосов
/ 02 декабря 2011

Как ядро ​​получает исполняемый двоичный файл, работающий под Linux?

Кажется, простой вопрос, но кто-нибудь может помочь мне углубиться?Как файл загружается в память и как запускается исполняемый код?

Может кто-нибудь помочь мне и рассказать, что происходит, шаг за шагом?

Ответы [ 4 ]

25 голосов

Лучшие моменты системного вызова 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

    Чтобы найти следующую основную функцию, проследите, когда возвращаемое значение 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.

      См. Также: Почему люди пишут шебанг Python #! / Usr / bin / env в первой строке скрипта 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, вот где действительно можно начать планировать

TODO: продолжить исходный анализ. Что я ожидаю, что произойдет дальше:

  • ядро ​​анализирует заголовок INTERP в ELF, чтобы найти динамический загрузчик (обычно устанавливается /lib64/ld-linux-x86-64.so.2).
  • если он присутствует:
    • ядро ​​отображает динамический загрузчик и исполняемый файл ELF в память
    • динамический загрузчик запускается, получая указатель на ELF в памяти.
    • теперь в userland, загрузчик каким-то образом анализирует заголовки эльфов и делает dlopen на них
    • dlopen использует настраиваемый путь поиска для поиска этих библиотек (ldd и друзей), отображает их в память и каким-то образом сообщает ELF, где найти пропущенные символы
    • загрузчик вызывает _start эльфа
  • в противном случае ядро ​​загружает исполняемый файл в память напрямую без динамического загрузчика.

    Поэтому он должен, в частности, проверить, является ли исполняемый файл PIE или нет, если он помещает его в память в произвольном месте: Что такое опция -fPIE для независимых от позиции исполняемых файлов в gcc и ld?

8 голосов
/ 02 декабря 2011

Два системных вызова из ядра Linux актуальны. Системный вызов fork (или, возможно, vfork или clone) используется для создания нового процесса, аналогичного вызывающему (каждый процесс пользовательской среды Linux, за исключением init, создается fork или друзья). Системный вызов execve заменяет адресное пространство процесса новым (по существу, путем сортировки сегментов mmap из исполняемого и анонимного сегментов ELF, а затем инициализирует регистры, включая регистры указатель стека). x86-64 ABI дополнение и Linux сборка howto дают подробности.

Динамическое связывание происходит после execve и включает файл /lib/x86_64-linux-gnu/ld-2.13.so, который для ELF рассматривается как «интерпретатор».

7 голосов
/ 02 декабря 2011

После прочтения уже упомянутых документов ELF вы должны просто прочитать код ядра , который фактически это делает.

Если у вас возникли проблемы с пониманием этого кода, создайте UML Linux , и вы можете просмотреть этот код в отладчике.

2 голосов
/ 02 декабря 2011

Вы можете начать с понимания форматов исполняемых файлов, таких как 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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...