刘文 + 原创作品转载请注明出处 +《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
本次的实验我们要分析一下Linux系统如何装载和启动一个可执行程序。首先我们分析一下其装载过程,然后用gdb对其进行跟踪。
首先我们要从C代码到可执行程序的几个步骤,如下:
预处理:将C源程序翻译成.i文件,把include的文件包含进来及进行宏替换;
编译:将.c转换为.s汇编文件;
汇编:将.s汇编文件转换为目标文件.o;
链接:将目标文件转换为可执行目标文件;
接下来我们需要了解execve这个函数:
可执行程序的装载
命令行参数和shell环境,一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。
Shell本身不限制命令行参数的个数, 命令行参数的个数受限于命令自身,
例如,int main(int argc, char *argv[])
又如, int main(int argc, char *argv[], char *envp[])
Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数:
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
库函数exec*都是execve的封装例程,
sys_execve内部会解析可执行文件格式,
do_execve -> do_execve_common -> exec_binprm
search_binary_handler符合寻找文件格式对应的解析模块,如下:
1369 list_for_each_entry(fmt, &formats, lh) {
1370 if (!try_module_get(fmt->module))
1371 continue;
1372 read_unlock(&binfmt_lock);
1373 bprm->recursion_depth++;
1374 retval = fmt->load_binary(bprm);
1375 read_lock(&binfmt_lock);
由以上分析我们可以得到execve系统调用的处理过程:
sys_execve——do_execve——do_execve_common(do_open_exec、exec_binprm)
exece_binprm——search_binary_handler——list_for_each_entry——load_binary
接下来我们通过实验来了解Linux内核是如何装载和启动一个可执行程序的;创建新的test.c文件如下,其中增加了一个选项exec:
首先我们先设三个断点sys_execve,load_elf_binary和start_threaad用于跟踪:
接下来开始跟踪整个流程:
我们可以看到在sys_execve处主要是将命令行参数和环境变量传递到内核堆栈中去。
接下来跟踪到load_elf_binary处的程序中去:
这部分的代码主要是将elf二进制文件加载到内核。最后是start_thread,继续进行跟踪:
可以看到,这部分代码的主要功能是修改内核堆栈,创建新堆栈,然后将命令行参数和环境变量拷贝到新程序堆栈中,再进行任务调度,运行新程序。而新程序的起始入口为elf_entry。
总结:
对于Linux内核如何装载和启动一个可执行程序的,首先是通过sys_execve将函数的命令行参数和环境变量传递到内核堆栈中去,再由load_elf_binary将二进制文件加载到内核中去,再通过start_thread修改内核堆栈,创建新堆栈,然后将命令行参数和环境变量拷贝到新程序堆栈中,再进行任务调度,运行新程序。而新程序的起始入口为elf_entry。