第七周 linux加载和启动一个可执行程序
1. 前言
这里主要分析linux系统如何加载和启动一个可执行程序。一个源文件要想形成可执行文件,需要经过编译和链接才可以。也就是说源文件先要通过编译器编译成目标文件,然后利用链接器链接成可运行文件。这样做有助于程序的模块化管理,其示意图如下程序经过编译后的目标文件由代码段以及数据段等段组成,然后链接器将目标文件相同段合并,从而形成了可执行文件,其示意图如下所示:
这里需要指出,目标文件所有ELF格式在链接后也在最终的可执行文件中保存下来。可以利用readelf命令进行查看。
2 系统加载程序过程分析
源文件被编译成可执行文件后,我们需要加载这个可执行文件进行执行。对于Linux下,系统是调用execve用来加载程序的运行。其具体的流程图如下
相关代码如下search_binary_handler寻找ELF文件解析器的关键代码如下
1 list_for_each_entry(fmt, &formats, lh) { 2 if (!try_module_get(fmt->module)) 3 continue; 4 read_unlock(&binfmt_lock); 5 prm->recursion_depth++; 6 retval = fmt->load_binary(bprm); 7 read_lock(&binfmt_lock);
其中注意load_binary这个调用函数,它启动了了load_elf_binary这个程序,具体是通过修改elf_fomat这个结构体实现的。相关代码如下所示
1 static struct linux_binfmt elf_format = { 2 .module = THIS_MODULE, 3 .load_binary = load_elf_binary, 4 .load_shlib = load_elf_library, 5 .core_dump = elf_core_dump, 6 .min_coredump = ELF_EXEC_PAGESIZE, 7 }; 8 9 static int __init init_elf_binfmt(void) 10 { 11 register_binfmt(&elf_format); //注册elf_format,这样解析器就可以调用合适的程序来解析elf格式 12 return 0; 13 }
3 实验
开始启动时出发sys_execve中断
最后修改IP(指令计数器指向可执行文件),通过readelf命令读取执行文件可以验证。这里映射地址是进程中的地址,并非实际内存中地址。
进程地址空间为
0x08048000 —— 0x0804c000
4 总结
通过这章学习了一个源文件如何编译成可执行文件,并且Linux如何加载这个可执行文件到进程的地址空间当中的。实验也验证了之前的分析,而这里execve最终实现方式是通过修改要指令寄存器实现的,即修改IP寄存器。虽然这里分析的只是elf文件的执行,但是这里看到程序中引入不同解释器解释不同文件的环节。通过这一机制可以有效增加支持的文件格式。这让我想起了那句计算机名言
Any problem in computing can be solved by another level of indirection. ------David Wheel