Русские Блоги
Процесс загрузки файлов 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 начинает настоящую загрузку.
- В ядре — 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_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)
Эта функция выполняет примерно три вещи:
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 точкой входа в программу является динамический компоновщик.
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 (); этот тип функции, включающий системный вызов базового исходного кода, может быть проанализирован только после того, как исходный код станет понятен, но исходный код нелегко контролировать. Вероятно, его можно увидеть в облаке, например мне. Так что мой анализ очень поверхностный. Это просто обзор всего процесса.