2018-2019-1 20189213《Linux内核原理与分析》第八周作业
可执行程序工作原理
书本重要知识总结
1.ELF文件
ELF(Excutable and Linking Format)即可执行的和可链接的格式,是一个目标文件格式的标准。通过readelf -h hello查看可执行文件hello的头部(-a查看全部信息,-h只查看头部信息),头部里面注明了目标文件类型ELF32。Entry point address是程序入口,地址为0x400400,即可执行文件加载到内存中开始执行的第一行代码地址。头部后还有一些代码数据等等。可执行文件的格式和进程的地址空间有一个映射的关系,当程序要加载到内存中运行时,将ELF文件的代码段和数据段加载到进程的地址空间。
ELF文件里面分为三种目标文件:
可重定位文件——文件中保存着代码和适当的数据,用来和其它的目标文件一起来创建一个可执行文件、静态库文件或者是一个共享目标文件(主要是.o文件);
可执行文件——文件中保存着一个用来执行的程序,该文件指出了exec(BA_OS)如何来创建程序进程映象(操作系统怎么样把可执行文件加载起来并且从哪里开始执行);
共享文件——文件中保存着代码和合适的数据,用来被两个链接器链接。第一个是链接编辑器(静态链接),可以和其它的可重定位和共享目标文件来创建其它的object。第二个是动态链接器,联合一个可执行文件和其它的共享目标文件来创建一个进程映象。
ELF格式简介:
①ELF文件的索引表
②ELF Header结构
③Section Header结构
④Program Header结构
2.可执行程序的预处理、编译、汇编、链接
gcc –E hello.c -o hello.i //预处理
gcc -S hello.i -o hello.s -m32//编译
gcc -c hello.s -o hello.o -m32 //汇编
gcc hello.o -o hello -m32 //链接
用gcc hello.o -o hello.static -m32 -static进行静态编译,得到的hello.static把C库里需要的东西也放到可执行文件里了。用命令ls –l,可以看到hello只有5K左右,hello.static比700K还有多一点。
3.动态链接
动态链接有装载时动态链接和运行时动态链接两种方式。
下面对动态链接实例进行分析:
首先是各个对应头文件和函数:
shlibexample.h:
shlibexample.c:
dlllibexample.h:
dlllibexample.c:
分别以共享库和动态加载共享库的方式使用libshlibexample.so文件和libdllibexample.so文件:
编译main.c,注意这里只提供shlibexample的-L(库对应的接口头文件所在目录)和-l(库名,如libshlibexample.so去掉lib和.so的部分),并没有提供dllibexample的相关信息,只是指明了-ldl:
主函数main.c:
实验:使用gdb跟踪分析execve系统调用内核处理函数sys_execve。
首先还是将menu目录删除,用git命令复制一个新的menu目录,用test_exec.c将test.c覆盖,然后重新编译rootfs:
发现在MenuOS中使用help命令可以看到增加了exec命令,执行exec指令发现比fork指令增加了一行输出“helloworld!”,实际上是新加载了一个可执行程序来输出了一行语句:
查看代码我们发现,在test.c中新增了exec函数:
在Makefile中不仅编译了hello.c,还在生成根文件系统时把init和hello都放到rootfs.img中:
下面我们使用对gdb进行跟踪分析:
首先还是冻结内核,加载符号表并设置端口,准备单步调试:
然后分别在sys_execve、load_elf_binary、start_thread处设置断点:
然后三次执行后,在执行exec命令后停在如下图所示位置:
使用list列出相关代码,使用step进入sys_execve函数内部发现调用了“do_execve()”函数继续执行到“load_elf_binary”处的断点:
继续执行到“start_thread”处的断点,因为是静态链接,“elf_entry”指向了可执行文件中定义的入口地址,使用po new_ip指令打印其指向的地址,“new_ip”是返回到用户态的第一条指令的地址:
查看hello的elf头部,看定义的入口地址与new_ip所指向的地址是否一致:
发现确实是一致的。
问题
1.32位可执行文件Addr会显示类似0x8048000的地址,而我的这里程序入口地址是0x400400,可能与因为这是64位的系统有关。
2.进行预处理编译汇编链接时,使用自己的虚拟机在链接这一步时无法成功,初次感觉时gcc版本原因,但更换最新版gcc之后还是不行,不知道原因在哪。
总结
对“Linux内核装载和启动一个可执行程序”的理解:
当linux内核或程序使用fork函数创建子进程后,子进程往往要调用一种exec函数(exec家族的一种)以执行另一个程序;在调用一种exec函数时,该进程执行的程序完全被替换为新程序,而新程序则从其main函数处开始执行,因为调用exec函数并不创建新进程,所以前后的进程ID并未改变,或者说exec函数只是用了一个全新的程序替换了当前进程的正文、数据段和堆栈段。