Linux内核分析之简析加载和启动一个可执行程序
来源:互联网 发布:php获取url参数 编辑:程序博客网 时间:2024/05/17 04:35
SA16225055冯金明 原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
实验内容
实验要求:
- 理解编译链接的过程和ELF可执行文件的格式
- 编程使用exec* 库函数加载一个可执行文件,动态链接分为可执行程序装载时动态链接和运行时动态链接,编程练习动态链接库的这两种使用方式
- 使用gdb跟踪分析一个execve系统调用内核处理函数sys_execve,验证对linux系统可执行程序所需处理过程的分析的理解
- 特别关注新的可执行程序是从哪里开始执行的?为什么execve系统调用返回后新的可执行程序能顺利执行?对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时会有什么不同
- 在test.c中添加Exec()函数,并在menu命令中添加exec命令,修改Makefile,如图一所示
- 使用gdb调试,添加相应的断点,如图二所示
- 通过调试,查看加载和启动一个可执行程序的具体过程,如图三所示
预处理:
gcc -E -o hello.cpp hello.c -m32 预处理(文本文件) 预处理负责把include的文件包含进来及宏替换等工作
编译:
gcc -x cpp-output -S -o hello.s hello.cpp -m32 编译成汇编代码(文本文件)
汇编:
gcc -x assembler -c hello.s -o hello.o -m32 汇编成目标代码(ELF格式,二进制文件,有一些机器指令,只是还不能运行)
链接:
gcc -o hello hello.o -m32 链接成可执行文件(ELF格式,二进制文件) 在hello可执行文件里面使用了共享库,会调用printf,libc库里的函数
gcc -o hello.static hello.o -m32 -static 静态链接把执行所需要依赖的东西都放在程序内部
ELF三种主要的目标文件:
- 可重定位:保存代码和适当数据,用来和其他的object文件一起创建可执行/共享文件,主要是.o文件
- 可执行文件:指出了exec如何创建程序进程映像,怎么加载,从哪里开始执行
- 共享object文件:保存代码和适当数据,用来被下面的两个连接器链接
- 连接editor,连接可重定位、共享object文件。即装载时链接。
- 动态链接器,联合可执行、其他共享object文件创建进程映像。即运行时链接。
- 在启动一个可执行程序时,会将输入的命令行参数和环境串都放在用户态堆栈中。
- 动态链接分为可执行程序装载时动态链接和运行时动态链接。
- 可执行程序装载时的动态链接过程如下。
- 输入命令gcc -shared dllibexample.c -o libdlibexample.so -m32生成链接库。
- 然后在程序中使用#include导入该头文件即可使用该链接库中的函数。
- 使用命令gcc main.c -o main -L /path/to/your/dir -l shlibexample -ldl -m32执行。
- 运行时动态链接过程如下
- 同样使用使用上述命令生成动态链接库。
- 在程序中动态调用
- 首先,我们使用函数dlopen打开动态链接库,然后定义一个函数指针,然后通过函数dlsym来找到我们想要的函数并附值给上面定义的函数指针,最后利用函数指针调用该函数。
- 调用exec系统调用装载一个可执行程序的过程。
- 首先在用户态调用exec*函数,对应的内核入口为sys_execve
- 在sys_execve中调用do_execve函数
- 在do_execve函数中调用open_execve打开可执行文件
- 调用copy_strings_kernel从系统空间拷贝
- 调用copy_strings从用户空间拷贝
- 调用exec_binprm函数,然后调用search_binary_handler函数
- 调用list_for_each_entry来搜寻formats队列中的成员来执行
- 调用copy_thread来附值进程控制快的相关信息
- 最后调用start_thread函数来设置新的程序执行的开始地址
执行过程分析:
execve系统调用和fork系统调用一样,都是特殊的系统调用。fork系统调用返回两次,一次返回父进程执行,一回返回特定的点ret_from_fork执行,然后返回到用户态。execve系统调用在内核中将当前的可执行程序覆盖掉了,当返回时不再是原来的可执行程序了。Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数:
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
而所有的库函数exec*都是execve的封装例程。
start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp){ set_user_gs(regs, 0); regs->fs = 0; regs->ds = __USER_DS; regs->es = __USER_DS; regs->ss = __USER_DS; regs->cs = __USER_CS; regs->ip = new_ip; regs->sp = new_sp; regs->flags = X86_EFLAGS_IF; /* * force it to the iret return path by making it look as if there was * some work pending. */ set_thread_flag(TIF_NOTIFY_RESUME);}
execve在返回前用新的ip和sp更新了进程的ip和sp。对于需要动态链接的程序,elf_entry就会加载动态链接器ld的入口地址。
总结
在linux环境下,可执行文件的格式为ELF,文件头部信息会标明文件在加载到内存中的相关信息,随后的是以段形式存在的代码和数据。段的划分主要依据是加载到内存中的读写属性。系统调用execve则是负责可执行文件的调度和执行,先进行相关参数的传递和调用前环境的设置,然后加载可执行文件的信息,查找相关执行文件解析模块,对ELF格式的可执行文件,按照格式要求加载到内存中相应的地址空间当中。如果是静态链接,则将文件头部标明的入口地址作为开始执行的地址,如果是依赖动态链接库的 可执行文件则需要将动态链接器ld的入口地址作为开始的地址。
参考资料
- http://swordautumn.blog.51cto.com/1485402/1633663
- http://blog.csdn.net/u010521171/article/details/51106773
- http://www.cnblogs.com/petede/p/5351696.html
- Linux内核分析之简析加载和启动一个可执行程序
- Linux内核分析之七——Linux内核如何装载和启动一个可执行程序
- 《Linux操作系统分析》之分析Linux内核如何装载和启动一个可执行程序
- Linux内核分析7:Linux内核装载和启动一个可执行程序的分析
- Linux内核分析:Linux内核如何装载和启动一个可执行程序
- 网易公开课《Linux内核分析》学习心得-Linux内核如何装载和启动一个可执行程序
- Linux内核分析 实验七:Linux内核如何装载和启动一个可执行程序
- Linux内核分析:实验七--Linux内核如何装载和启动一个可执行程序
- Linux内核分析——Linux内核如何装载和启动一个可执行程序
- Linux内核分析课程-- Linux内核如何装载和启动一个可执行程序
- 第7节 Linux内核如何装载和启动一个可执行程序【Linux内核分析】
- GCC & ELF文件格式 &linux内核如何加载和启动一个可执行程序
- Linux内核如何装载和启动一个可执行程序(Linux)
- Linux内核如何装载和启动一个可执行程序
- Linux内核如何装载和启动一个可执行程序
- Linux内核如何装载和启动一个可执行程序
- Linux内核如何装载和启动一个可执行程序
- 初学《Linux内核如何装载和启动一个可执行程序》
- 边框圆角效果的原理
- 求n的阶乘的末尾有多少个零
- 20170404_请说出IP地址的5大分类
- 收集整理的shell游戏
- 【数据结构】中平衡搜索树的旋转方式解析
- Linux内核分析之简析加载和启动一个可执行程序
- Ant1.8.2 Jdk7 Dockerfile
- 1038. 统计同成绩学生(20)
- POJ 1590 -- 回文词
- new、引用
- sealed(C# 参考)
- 工作思想总结
- bzoj2190 [SDOI2008]仪仗队
- python3和2共存