The Proccess of Loading and Executing a Specific Program in Linux Kernel

来源:互联网 发布:淘宝客日赚 编辑:程序博客网 时间:2024/06/06 14:00

Chapter 1 Environment

Ubuntu 14.10

Linux Kernel 3.18.6

第二章 调试

Chapter 2 Debug

What we need to pay attention is that we shouldn't set breakpoint when the kernel start. Otherwise, it will be extremely hard to start.

Breakpoints:

sys_execv

load_elf_binary




第三章 分析

主要是以下的代码:

binfmt_elf.c和exec.c

先分析一个原始的可执行文件:a.out。

其数据结构为:参考文献:http://blog.chinaunix.net/uid-11469366-id-1747286.html

struct exec {        unsigned long   a_midmag;    /* 魔数和其它信息 */        unsigned long   a_text;      /* 文本段的长度 */        unsigned long   a_data;      /* 数据段的长度 */        unsigned long   a_bss;       /* BSS段的长度 */        unsigned long   a_syms;      /* 符号表的长度 */        unsigned long   a_entry;     /* 程序进入点 */        unsigned long   a_trsize;    /* 文本重定位表的长度 */        unsigned long   a_drsize;    /* 数据重定位表的长度 */};


魔数:通过文件头的前几个数来判断这个文件的类型。

在do_execve中真正地实现了对程序的执行。这个函数定义了一个名为linux_binprm的结构体来存储所要运行进程的信息。

struct linux_binprm{    char buf[BINPRM_BUF_SIZE]; //保存可执行文件的头128字节    struct page *page[MAX_ARG_PAGES];    struct mm_struct *mm;    unsigned long p;    //当前内存页最高地址    int sh_bang;    struct file * file;     //要执行的文件    int e_uid, e_gid;    //要执行的进程的有效用户ID和有效组ID    kernel_cap_t cap_inheritable, cap_permitted, cap_effective;    void *security;    int argc, envc;     //命令行参数和环境变量数目    char * filename;    //要执行的文件的名称    char * interp;        //要执行的文件的真实名称,通常和filename相同    unsigned interp_flags;    unsigned interp_data;    unsigned long loader, exec;};


然后用do_open_exec()来打开文件,加载文件头部,把环境变量和参数调入至调用函数中。list_for_each_entry()尝试寻找可以解析当前可执行文件的代码。实际上调用的是load_elf_binary。

load_elf_binary:

加载elf类型文件的handler是load_elf_binary(),它先读入ELF文件的头部,根据ELF文件的头部信息读入各种数据(header information)。再次扫描程序段描述表,找到类型为PT_LOAD的段,将其映射(elf_map())到内存的固定地址上。如果没有动态链接器的描述段,把返回的入口地址设置成应用程序入口。完成这个功能的是start_thread(),start_thread()并不启动一个线程,而只是用来修改了pt_regs中保存的PC等寄存器的值,使其指向加载的应用程序的入口。这样当内核操作结束,返回用户态的时候,接下来执行的就是应用程序了。

参考资料:http://www.cnblogs.com/li-hao/archive/2011/09/24/2189504.html

如果是要使用动态链接的话,则要将连接器ld先加载进来,就是调用load_elf_interp()函数。

load_elf_interp()

如果应用程序中使用了动态链接库,就没有那么简单了,内核除了加载指定的可执行文件,还要把控制权交给动态连接器(program interpreter,ld.so in linux)以处理动态链接的程序。内核搜寻段表,找到标记为PT_INTERP的段中所对应的动态连接器的名称,并使用load_elf_interp()加载其映像,并把返回的入口地址设置成load_elf_interp()的返回值,即动态链接器入口。当execve退出的时候动态链接器接着运行。动态连接器检查应用程序对共享连接库的依赖性,并在需要时对其进行加载,对程序的外部引用进行重定位。然后动态连接器把控制权交给应用程序,从ELF文件头部中定义的程序进入点开始执行。(比如test.c中使用了userlib.so中函数foo(),在编译的时候这个信息被放进了test这个ELF文件中,相应的语句也变成了call fakefoo()。当加载test的时候,知道foo()是一个外部调用,于是求助于动态链接器,加载userlib.so,解析foo()函数地址,然后让fakefoo()重定向到foo(),这样call foo()就成功了。)

参考资料:http://www.cnblogs.com/li-hao/archive/2011/09/24/2189504.html

CPU将控制权交给ld来对环境变量中的依赖库进行广度优先或深度优先的检索,来进行动态链接。最后加载程序。

Chapter 4 Conclusion

sys_execve可以让程序链接的时候选择动态编译还是静态编译。在静态编译下,申请的地址空间往往是静态的,而且是连续的。而动态编译的情况下,是一块一块不连续的内存段。这也就印证了操作系统原理的内容,就是段式管理和页式管理。

从do_execve中开始,系统先对程序文件和路径进行记录,然后通过通过load_elf_binary对文件进行连接,加载一些依赖的库和检测系统参数。最终记录并返回eip的内存地址,产生新的线程让程序运行。

在学数据结构时,一直质疑这些内容对以后的编程是否能帮助。Linux内核分析使我明白:广度优先算法在分析依赖库上十分有帮助,可以快速检索所需要的库,而且不会出错。并且也深刻理解了离散数学对图的分析。

记得在寒假,我在使用《Unix网络环境编程》时,曾经十分深刻地接触了.so文件的生成。当时在调试有关环境时,生成了.so文件却不知道它的作用。唯一感觉得到的便是,很多自定义命令不再提示未定义。却没想到后面还有很多内容。


Appendix


Luxuan + Writen by the author, Please let me know if you want to take it as a reference + Linux Kernel Analyzation a course of MOOC website:http://mooc.study.163.com/course/USTC-1000029000

0 0
原创粉丝点击