Linux load elf file

Русские Блоги

Процесс загрузки файлов ELF в Linux _linux основы 18/3/1

Резюме

  • Этот блог является чисто примечанием для меня, и все еще есть много мест, где объяснение отсутствует, особенно анализ исходного исходного кода позже, могут быть места, где объяснение неуместно или даже необоснованно. Но я изменю анализ исходного кода в этом блоге после более глубокого исследования исходного кода. Тем не менее, этот блог содержит много знаний, частично из «Самосовершенствования программиста».

Подробное объяснение процесса загрузки файла ELF в ядре Linux execve

——— Частично взято из «Самосовершенствования программиста»

Один: заповедник знаний

  • Переключение процесса из пользовательского режима в режим ядра
    С точки зрения макросов, архитектура операционной системы Linux делится на пользовательский режим и режим ядра (или пользовательское пространство и ядро). Ядро — это, по сути, своего рода программное обеспечение, которое контролирует аппаратные ресурсы компьютера и обеспечивает среду, в которой работают приложения верхнего уровня. Пользовательское состояние — это пространство активности приложения верхнего уровня. Выполнение приложения должно зависеть от ресурсов, предоставляемых ядром, включая ресурсы ЦП, ресурсы хранения и ресурсы ввода-вывода. Чтобы позволить приложениям верхнего уровня получить доступ к этим ресурсам, ядро ​​должно предоставить интерфейс доступа для приложений верхнего уровня: системные вызовы.
    Это взято из блога:
    https://www.cnblogs.com/bakari/p/5520860.html

Зная концепции пользовательского режима и режима ядра, давайте посмотрим на переход между этими двумя состояниями:

  • Зачем делать это преобразование:
    Причина в том, что операции, которые может выполнять процесс, запущенный в пользовательском режиме, и ресурсы, к которым можно получить доступ, сильно ограничены, в то время как процесс, работающий в режиме ядра, может выполнять любую операцию и находится ограничений по использованию нет. Следовательно, это преобразование используется при выполнении определенных операций с более высокими уровнями привилегий.
  • Сценарии конвертации двух режимов
    Системный вызов, ненормальное событие, прерывание периферийного устройства. Вот объяснение системного вызова.
  • Процесс системного вызова:
    Когда системе необходимо вызвать функцию системного вызова, например, когда выполняется функция execve, первым шагом является вызов функции sys_exece. Этот процесс можно пояснить с помощью рисунка: (вот схема процесса с точки зрения макроса

Два: процесс загрузки ELF

  • Мы используем bash в оболочке для объединения этого процесса.
    Во-первых, на уровне пользователя, после того как пользователь вводит команду, процесс bash сначала вызывает системный вызов fork (); для создания нового процесса. Затем новый процесс вызовет execve (); система вызывает выполнение указанного ELF-файла, исходный процесс bash продолжает возвращаться и ожидает окончания нового процесса, а затем продолжает ждать, пока пользователь введет команду.

Первый: execve (); основной процесс выполнения
execve (); определен в unistd.h Прототип функции:

int execve(cosnt char *filename,char *const argv[ ],char *const envp[ ]);

Его три параметра — это имя файла исполняемой программы, параметры выполнения и переменные среды. После входа в системный вызов execve (); ядро ​​Linux начинает настоящую загрузку.

  1. В ядре — execve (); соответствующая запись системного вызова — sys_execve (); он определен в arch \ i386 \ kerne \ Process.c, а его прототип функции:
 int sys_execve(char *filenamei, char **argv, char **envp, struct pt_regs *regs);

Его функция — вызвать do_execve () после проверки и копирования параметра;

int do_execve(char * filename, char __user *__user *argv, char __user *__user *envp, struct pt_regs * regs) ;

И do_execve (); будет выполняться за 8 шагов.
1) Динамически выделяйте структуру linux_binprm и заполняйте эту структуру данными нового исполняемого файла:

bprm = kmalloc(sizeof(*bprm), GFP_KERNEL); if (!bprm) goto out_ret; // После проверки bprm используйте функцию memset, чтобы установить bprm на 0. memset(bprm, 0, sizeof(*bprm));

Для bprm эта структура имеет следующее определение перед функцией:

Читайте также:  Топ linux на debian

Затем давайте подробнее рассмотрим структуру linux_binprm:

 struct linux_binprm< char buf[BINPRM_BUF_SIZE]; struct page *page[MAX_ARG_PAGES]; struct mm_struct *mm; unsigned long p; /* current top of mem */ int sh_bang; struct file * file; int e_uid, e_gid; kernel_cap_t cap_inheritable, cap_permitted, cap_effective; void *security; int argc, envc; char * filename; char * interp; unsigned interp_flags; unsigned interp_data; unsigned long loader, exec; >;

Хотя эта структура имеет много членов, нам нужно знать только процесс загрузки, поэтому нам нужно знать только, что первый член:

Здесь определяется массив символов, который является макросом:

#define BINPRM_BUF_SIZE 128

Размер 128. Это будет использоваться позже, когда будет передано магическое число.

2) Теперь вернемся к динамическому размещению структуры bprm. После инициализации структуры bprm значением 0 do_execve (); вызовет open_exec (); эта функция вызовет path_lookup (), dentry_open (), path_release () для получения объекта записи каталога, объекта файла и индекса, связанных с исполняемым файлом Объект узла:

file = open_exec(filename); retval = PTR_ERR(file);

На следующем шаге файл будет проверен.Файл также может быть кодом ошибки, поэтому для проверки вызывается IS_ERR (файл).

 if (IS_ERR(file)) goto out_kfree;

3) В мультипроцессоре вызовите sched_exec, чтобы определить минимальную загрузку ЦП для выполнения новой программы, и перенесите часть информации, полученной выше, в структуру bprm.

sched_exec();//Проверьте // Следующая часть заполнит часть полученной информации в структуре bprm bprm->p = PAGE_SIZE*MAX_ARG_PAGES - sizeof(void *); bprm->file = file; bprm->filename = filename; bprm->interp = filename; bprm->mm = mm_alloc(); retval = -ENOMEM; // Проверка параметров if (!bprm->mm) goto out_file; 

4) Вызовите prepare_binprm (); для заполнения структуры brpm, определенной linux_binprm.
Эта функция очень длинная, поэтому мы просто смотрим на прототип функции этой функции:

int prepare_binprm(struct linux_binprm *bprm)

Эта функция выполняет примерно три вещи:

Читайте также:  How to rename all file in linux

1. Проверьте, является ли исполняемый файл исполняемым.
Исходный код:

if (!(mode & 0111)) return -EACCES; if (bprm->file->f_op == NULL) return -EACCES;

2. Инициализируйте поля e_uid и e_gid. Разрешения пользователей будут проверены на основе этих двух значений позже.
Исходный код:

bprm->e_uid = current->euid; bprm->e_gid = current->egid;

3. Заполните поле buf структуры linux_binprm первыми 128 байтами исполняемого файла. Эти байты содержат магическое число и другую информацию, подходящую для идентификации исполняемых файлов.
Исходный код:

memset(bprm->buf,0,BINPRM_BUF_SIZE);// Сначала инициализируем 0 return kernel_read(bprm->file,0,bprm->buf,BINPRM_BUF_SIZE); / * Вызвать kernel_read () при возврате; использовать исполняемый файл document bprm-> file ** Первые байты BIPRM_BUF_SIZE (128) для заполнения bprm-> buf, это ** Причина, по которой мы должны дать определение buf ранее. **/

5) Скопируйте имя пути, параметры командной строки и строку среды в один или несколько вновь выделенных страничных боксов и, наконец, они будут размещены в адресном пространстве пользовательского режима.
Исходный код:

// Копируем имя пути retval = copy_strings_kernel(1, &bprm->filename, bprm); if (retval < 0) // Проверка будет выполняться после каждой копии goto out; // Копируем строку окружения bprm->exec = bprm->p; retval = copy_strings(bprm->envc, envp, bprm); if (retval < 0) goto out; // Копируем параметры командной строки retval = copy_strings(bprm->argc, argv, bprm); if (retval < 0) goto out;

6) Используйте search_binary_handler (); для сканирования связанного списка форматов и попробуйте применить метод load_binary для каждого элемента и передать структуру данных inxu_binprm в эту функцию. Пока метод load_binary успешно отвечает на исполняемый формат файла. Проверка форматов прекращена.

  • Проще говоря, это сканирование связанного списка для поиска и соответствия процессу загрузки соответствующего исполняемого файла. Для этого процесса загрузки есть три типа функций:
    1. Процесс загрузки ELF: load_elf_binary ();
    2.a.a. процесс загрузки: load_aout_binary ();
    3. Процесс загрузки программы исполняемого скрипта: load_script ();

Функция search_binary_handler (); также определит формат файла, оценив магическое число в buf в заголовке файла.

7) Для процесса загрузки вот описание ELF:
Процесс загрузки ELF называется load_elf_binary (); он определен в fs / Binfmt_elf.c, поскольку код функции длинный, здесь приведен только прототип:

static int load_elf_binary ( struct linux_binprm * bprm, struct pt_regs * regs );

Его выполнение в основном делится на 5 шагов:
1. Проверьте правильность исполняемого файла ELF, например, магическое число, количество прерываний таблицы заголовков программы (Segmemt).
2. Найдите раздел «.interp» динамической ссылки и задайте путь динамического компоновщика.
3. В соответствии с описанием таблицы заголовков программы исполняемого файла ELF сопоставьте файл ELF, например код, данные и данные только для чтения.
4. Инициализируйте среду процесса ELF, например, адрес регистра EDX при запуске процесса должен быть адресом DT_FINI.
5. Измените адрес возврата системного вызова на точку входа исполняемого файла ELF. Эта точка входа зависит от метода связывания программы. Для статически связанных исполняемых файлов ELF значение запись программы Точка — это адрес, на который указывает e_entry в заголовке файла. Для динамически связанных исполняемых файлов ELF точкой входа в программу является динамический компоновщик.

Читайте также:  Настройка dns сервера linux bind

8) После выполнения load_elf_binary (); вернитесь к do_execve (); а затем вернитесь к sys_execve (); когда адрес возврата системного вызова был изменен на адрес входа загруженной программы ELF на шаге 5 выше, поэтому when sys_execve (); Когда системный вызов возвращается из режима ядра в режим пользователя, регистр EIP переходит непосредственно к адресу входа программы ELF, поэтому запускается новая программа и загружается исполняемая программа ELF.

Три: немного понимания магических чисел
— следующее взято из «Самосовершенствования программиста»

  • Концепция
    Используйте команду readelf -h для просмотра заголовка файла ELF. В заголовке файла есть строка из 16 байтов, которая называется Magic data. Это так называемое магическое число. Эти 16 байты — стандарт ELF определяет атрибуты платформы, используемые для представления файла ELF.
  • Подробно
    Основной формат магического числа:
  • Первые 4 байта — это идентификационные коды, которые все файлы ELF должны быть одинаковыми, соответственно: 0x7F 0x45 0x3C 0x46, первый байт соответствует управляющему символу DEL в коде ASC ||, а последние 3 байта. трехбуквенный ASC || код ELF. Фактически, эти 4 байта являются настоящим магическим числом, а остальные просто идентифицируют другую информацию.
    Пятый байт используется для определения типа файла ELF, 0x01 — 32 бита, а 0x02 — 64 бита. Шестой байт — это порядок байтов, который определяет, является ли файл ELF прямым или прямым порядком байтов (для заинтересованных, пожалуйста, обратитесь к Приложению A «Самосовершенствование программиста»). Седьмой — это основной номер версии файла ELF. . Стандарт ELF для следующих байтов не определен.
  • Как посмотреть магическое число
    1) Используйте метод readelf -h для прямого просмотра заголовка файла, магическая строка — это магическое число
    2) Чтобы проверить, является ли магическое число первыми байтами файла ELF, вы также можете написать код для чтения символов для проверки.
    Приведите небольшой код: (напишите от руки, не возражайте)
#include #include #include int main() < FILE *fp = NULL; if ((fp = fopen("./a", "r")) == NULL) < perror("fopen error"); exit(0); > char buff[128] = < 0 >; if (fread(buff, 1, 127, fp) == 0) < perror("fread error"); exit(0); > int i; for (i = 0; i < 16; i++) < printf("%x ", buff[i]); > printf("\n"); exit(0); > 

подводить итоги

Для execve (); этот тип функции, включающий системный вызов базового исходного кода, может быть проанализирован только после того, как исходный код станет понятен, но исходный код нелегко контролировать. Вероятно, его можно увидеть в облаке, например мне. Так что мой анализ очень поверхностный. Это просто обзор всего процесса.

Источник

Оцените статью
Adblock
detector