linux ELF文件启动流程
一、背景
最近看了《linkers and loader》和以前学习《程序员的自我修养》,但是看了理解不是特别透,所以就想通过一个实际的案例来把了解到知识串起来,因此就想到把linux 识别和启动elf可执行文件流程梳理下,巩固自己所学。
二、内容
2.1 基础知识
无论是通过shell还是通过system函数的方式去调用新的命令,他底层的调用接口都是通过linux函数族实现的,linux主要的函数如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
整个代码执行伪代码如下:
int pid = fork();
if(pid == 0) {//children processexecv();
} else {//parent processwaitpid(pid);
}
而exec这一系列函数的主要功能就是识别不同文件类型,并用使用对应的文件解析器解析和加载文件到当前进程的虚拟地址空间,然后将程序控制权转移给对应文件给定的入口地址,有入口地址内的程序完成自我的初始化后,执行程序对应功能。
2.2 elf 执行文件详细启动流程讲解
下面将通过一个简单的测试demo,对elf 文件的启动进行详细分析:
测试代码如下:
#include <stdio.h>int main() {printf("just for the test\n");return 0;
}
# test elf header
ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: EXEC (Executable file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x400430Start of program headers: 64 (bytes into file)Start of section headers: 6528 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 9Size of section headers: 64 (bytes)Number of section headers: 31Section header string table index: 30
1. 内核执行部分
应用层execl函数族的调用最终会调用到内核的sys_execve函数,对应的执行代码如下:
//fs/exec.c 文件
SYSCALL_DEFINE3(execve,const char __user *, filename,const char __user *const __user *, argv,const char __user *const __user *, envp)
{return do_execve(getname(filename), argv, envp);
}
在do_execve中将进程的参数准备好后,通过查询的方式调用load_binary 加载对应进程,函数大体流程如下:
static int do_execve(struct filename *filename,const char __user *const __user *__argv,const char __user *const __user *__envp) {/* 1.prepare function environment, argc, argv2. call search_binary_handler() load binary*/static int search_binary_handler(struct linux_binprm *bprm){list_for_each_entry(fmt, &formats, lh) {if (!try_module_get(fmt->module))continue;read_unlock(&binfmt_lock);retval = fmt->load_binary(bprm);read_lock(&binfmt_lock);put_binfmt(fmt);if (bprm->point_of_no_return || (retval != -ENOEXEC)) {read_unlock(&binfmt_lock);return retval;}}}
}
// elf 文件的load_binary函数如下:
//fs/binfmt_elf.c
static int load_elf_binary(struct linux_binprm *bprm)
{/*1. check is elf format file2. load elf file program header3. load interpreter into memory4. start_thread begin address is interpreter entry address*/START_THREAD(elf_ex, regs, elf_entry, bprm->p);
}
2. 应用层执行部分
通过内核解析和加载后,并启动新的进程相应的入口函数时interpreter(链接器)的入口函数,相应的执行代码流程如下:
//sysdeps/x86_64/dl-machine.h
//入口函数
_start:movq %rsp, %rdicall _dl_start//elf/rtld.c
static ElfW(Addr) __attribute_used__
_dl_start (void *arg)
{/*1. relocation ld library self(ld library always static link don't have dynamic link),this alway do nothing2. set up runtime setup,(set global off set table dl_runtime value)3. load library dependencies, and return elf execute entry*/elf_machine_runtime_setup();_dl_start_final()
}//after dl_start return back,next call is dl_start_user
/*
1. backtrace stack to the argc argc env
2. call preinitial funcitons
3. jump to the execute file entry start execute
*/
_dl_start_user:\n\# Save the user entry point address in %r12.\n\movq %rax, %r12\n\# See if we were run as a command with the executable file\n\# name as an extra leading argument.\n\movl _dl_skip_args(%rip), %eax\n\# Pop the original argument count.\n\popq %rdx\n# Adjust the stack pointer to skip _dl_skip_args words.\n\leaq (%rsp,%rax,8), %rsp\n\# Subtract _dl_skip_args from argc.\n\subl %eax, %edx\n\# Push argc back on the stack.\n\pushq %rdx\n\# Call _dl_init (struct link_map *main_map, int argc, char **argv, char **env)\n\# argc -> rsi\n\movq %rdx, %rsi\n\# Save %rsp value in %r13.\n\movq %rsp, %r13\n\# And align stack for the _dl_init call. \n\andq $-16, %rsp\n\# _dl_loaded -> rdi\n\movq _rtld_local(%rip), %rdi\n\# env -> rcx\n\leaq 16(%r13,%rdx,8), %rcx\n\# argv -> rdx\n\leaq 8(%r13), %rdx\n\# Clear %rbp to mark outermost frame obviously even for constructors.\n\xorl %ebp, %ebp\n\# Call the function to run the initializers.\n\call _dl_init\n\# Pass our finalizer function to the user in %rdx, as per ELF ABI.\n\leaq _dl_fini(%rip), %rdx\n\# And make sure %rsp points to argc stored on the stack.\n\movq %r13, %rsp\n\# Jump to the user's entry point.\n\jmp *%r12\n\
3. execute file 执行流程
execute file函数执行流程网上比较多,所以不在本次分析的重点中,如果有需要查看请跳转链接
三、 附录
- linux elf 启动流程