第七周 可执行程序的装载
一、预处理、编译、链接和目标文件格式
1.可执行文件的由来
.c文件;
.s文件(汇编文件);
.o文件(目标文件);
多个.o文件链接为一个可执行文件,然后加载到内存执行;
2.目标文件的格式ELF
(1)类型:
- 可重定位问价(.o文件)
- 可执行文件(操作系统从哪里执行)
- 共享object文件
(2)ELF文件已经是适应到某一种CPU体系结构的二进制兼容文件了
(3)格式:
- 头部含有大量原信息
- 默认的ELF头加载地址是0x8048000,头部大概要到0x48100处或者0x483000处,也就是可执行文件加载到内存之后执行的第一条代码地址
- 一般静态链接会将所有代码放在一个代码段;动态链接的进程会有多个代码段
二、可执行程序、共享库和动态链接
1.装载可执行文件之前的工作
(1)可执行程序的执行环境:
- ls本事也是个命令,加上参数,列出/usr/bin下的目录信息
- Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身
- Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数
(2)示例:
- 要先fork一个进程,不然会覆盖shell
- execlp加载一个程序
(3)命令行参数和环境变量是如何进入新程序的堆栈的?
- shell程序-->execve-->sys_execve,然后在初始化新程序堆栈的时候拷贝进去
- 先传递函数调用参数,再传递系统调用参数
2.装载时动态链接和运行时动态链接举例
(1)动态链接分为可执行程序装载时动态链接和运行时动态链接
(2)准备.so文件(链接文件),编译指令:
$ gcc -shared shlibexample.c -o libshlibexample.so -m32
(3)动态加载库指令:(直接include共享库或下面方式)
#include <dlfcn.h>
void *handl=dlopen("文件名",路径);
func=dlsym(handle,"调用使用的名字");
(4)编译:
- $ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32 #这里只提供shlibexample的-L(库对应的接口头文件所在目录,也就是path to your dir)和-l(库名,如libshlibexample.so去掉lib和.so的部分),并没有提供dllibexample的相关信息,只是指明了-ldl
- $ export LD_LIBRARY_PATH=$PWD #将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件copy到默认路径下。
- $ ./main
三、可执行程序的装载
1.装载中的关键问题分析
(1)execve与fork是比较特殊的系统调用:
- execve用它加载的可执行文件把当前的进程覆盖掉,返回之后就不是原来的程序而是新的可执行程序起点;
- fork函数的返回点ret_from_fork是用户态起点
(2)sys_execve内部会解析可执行文件格式
- 顺序:do_execve -> do_execve_common -> exec_binprm
- search_binary_handle(寻找能解析文件格式的内核模块)
- 对于ELF格式的可执行文件fmt->load_binary(bprm);执行的应该是load_elf_binary其内部是和ELF文件格式解析的部分需要和ELF文件格式标准结合起来阅读
(3)Linux内核是如何支持多种不同的可执行文件格式的?
- elf_format和init_elf_binfmt,是观察者模式中的观察者,search_binary_handle相当于被观察者。(多态机制)
2.sys_execve的内部处理过程
- do_execve
- do_open_exec(filename)打开要加载的文件
- 命令行参数,结构体变量copy到bprm结构体中
- exce_binprm(bprm),关键代码是寻找能解析当前文件的处理模块
- register_binfmt($elf_format)注册这个格式到链表里,然后寻找能处理的模块
- ELF可执行文件默认映射到0x8048000这个地址
- 需要动态链接的可执行文件先加载连接器ld;否则直接把elf文件entry地址赋值给entry即可。
- start_thread(regs, elf_entry, bprm->p)会将CPU控制权交给ld来加载依赖库并完成动态链接;对于静态链接的文件elf_entry是新程序执行的起点
3.实验:gdb跟踪sys_execve内核
- (更新menu内核之后)查看test.c文件,可以看到新增加了exec系统调用
- 直接e hello.c切换到hello.c
- 查看Makefile,发现增加了gcc -o hello hello.c -m32 -static一句
- 启动内核并验证execv函数
- 冻结内核,启动GDB调试
- 进行调试
- 先停在sys_execve处,再设置其它断点;按c一路运行下去直到断点sys_execve
- 按s跳入函数内单步执行
- new_ip是返回到用户态的第一条指令
- 退出调试状态,输入redelf -h hello可以查看hello的EIF头部
4.动态链接的可执行程序的装载
- 动态链接库的依赖关系会形成一个图
- load_elf_interp实际加载动态链接器,entry返回的是动态链接器的入口,根据需求加载动态链接库,根据库的需要再加载更多的库(遍历)