实验七 Linux内核如何装载和启动一个可执行程序
实验要求
1.理解编译链接的过程和 ELF 可执行文件格式,详细内容参考本周第一节;
2.编程使用 exec*库函数加载一个可执行文件,动态链接分为可执行程序装载时动态链接和运行时动态链接,编程练习动态链接库的这两种使用方式,详细内容参考本周第二节;
3.使用 gdb 跟踪分析一个 execve 系统调用内核处理函数 sys_execve ,验证您对 Linux 系统加载可执行程序所需处理过程的理解,详细内容参考本周第三节;推荐在实验楼 Linux 虚拟机环境下完成实验。
4.特别关注新的可执行程序是从哪里开始执行的?为什么 execve 系统调用返回后新的可执行程序能顺利执行?对于静态链接的可执行程序和动态链接的可执行程序 execve 系统调用返回时会有什么不同?
实验内容
一、理解编译链接的过程和 ELF 可执行文件格式
1.预处理、编译、链接:
预处理,处理代码中的宏定义和 include 文件,并做语法检查
gcc -E hello_world.c -o hello_world.i
编译,生成汇编代码
gcc -S hello_world.i -o hello_world.s
汇编,生成 ELF 格式的目标代码
gcc -c hello_world.s -o hello_world.o
链接,生成可执行代码
gcc hello_world.o -o hello_world
执行程序
./hello_world hello, world!
2.ELF文件格式:
ELF 格式:可执行和可链接(Executable and Linkable Format) 是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储的标准文件格式。
可重定位文件,如:.o 文件,包含代码和数据,可以被链接成可执行文件或共享目标文件,静态链接库属于这类。
可执行文件,如:/bin/bash 文件,包含可直接执行的程序,没有扩展名。
共享目标文件,如:.so 文件,包含代码和数据,可以跟其他可重定位文件和共享目标文件链接产生新的目标文件,也可以跟可执行文件结合作为进程映像的一部分。
ELF 文件由 ELF header 和文件数据组成,文件数据包括:
Program header table, 程序头:描述段信息
.text, 代码段:保存编译后得到的指令数据
.data, 数据段:保存已经初始化的全局静态变量和局部静态变量
Section header table, 节头表:链接与重定位需要的数据
二、编程使用 exec*库函数加载一个可执行文件
首先更新linux/kernel下的menu,并将test_exec变成test.c
进入内核,可以看到exec命令已经被添加进入内核,执行exec命令
接着输入以下指令
cd ..
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -S -s
三、使用 gdb 跟踪分析一个 execve 系统调用内核处理函数 sys_execve
新建terminal进行gdb调试查看
内核中对sys_execve,load_elf_binary,start_thread进行断点调试,c执行。
此时执行exec发现执行到的地方如图:
list列出执行到的代码
四、分析exec*函数对应的系统调用处理过程:
1.我们通过跟踪以及查看分析代码,执行的流程是:(代码见上面) sys_execve -> do_execve -> do_execve_common -> exec_binprm -> search_binary_handler -> load_binary ->(对于我们这里的ELF,会跳转到)load_elf_binary(也执行了elf_format)-> start_thread
2.我们调用execve的可执行程序时,当执行到exceve时,系统调用exceve陷入内核,这时会创建一个新的用户态堆栈,实际是把命令行参数的内容和环境变量的内容通过指 针的方式传递给系统调用内核处理函数的,然后内核处理函数在创建可执行程序新的用户态堆栈的时候,会把这些拷贝到用户态堆栈初始化新的可执行程序的执行上下文环 境(先函数调用参数传递,再系统调用参数传递)。这时就加载了新的可执行程序。系统调用exceve返回用户态的时候,就变成了被exceve加载的可执行程序。