20135132陈雨鑫 + 原创做品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”linux
1、预处理、编译和连接和目标文件的格式shell
一、可执行程序是怎么来的?函数
理解编译连接的过程和ELF可执行文件格式spa
过程:命令行
.c文件汇编成汇编代码.asm,3d
再汇编成目标码.o,调试
连接成可执行文件a.out,code
最后可执行文件就能够加载到内存中执行。blog
二、目标文件的格式ELF接口
1)主要有三种目标文件
可重定位文件、可执行文件、共享文件
2)ELF文件加载到内存是如何加载的呢?
代码、数据都加载到内存中,默认ELF文件从0x开始加载,开始加载的是头部,会有一些信息,因此入口地址0x(这个地方就是程序的实际入口,可执行文件加载到内存中开始执行的第一行代码从这里开始)
三、静态连接的ELF可执行文件和进程的地址空间
入口地址为0x8048*00
2、可执行程序、共享库和动态连接
一、使用exec*库函数加载一个可执行文件
(1)动态连接分为可执行程序转载时动态连接和运行时动态连接
1)在linux下动态连接的文件是.so
2)libshlibexample.so文件(生成一个共享库文件)
libdllibexample.so(生成可动态加载文件)
(2)如果静态连接的,elf_entry就是指向可执行文件里边规定的那个头部,即main函数对应的位置,若这个可执行文件是须要依赖其它动态连接库的话,则elf_entry就是指向动态连接器的起点
二、
(1)execve:当前的可执行程序在执行execve这个系统调用的时候,它陷入到内核态,在内核态里它用这个execve加载的这个可执行文件把当前进程的可执行程序给覆盖掉,当execve这个系统调用返回的时候,返回的不是原来的那个可执行程序了,而是新的可执行程序了,它返回的是新的可执行程序的起点,即main函数大体的位置
(2)ELF文件加载到内存是如何加载的呢?
代码、数据都加载到内存中,默认ELF文件从0x开始加载,开始加载的是头部,会有一些信息,因此入口地址0x(这个地方就是程序的实际入口,可执行文件加载到内存中开始执行的第一行代码从这里开始)
•命令行参数和shell环境,通常咱们执行一个程序的Shell环境,咱们的实验直接使用execve系统调用。
•$ ls -l /usr/bin 列出/usr/bin下的目录信息
•Shell自己不限制命令行参数的个数,命令行参数的个数受限于命令自身
•例如,int main(int argc, char *argv[])
•又如, int main(int argc, char *argv[], char *envp[])
•Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数
•int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
•库函数exec*都是execve的封装例程
命令行参数和环境串都放在用户态堆栈中
装载时动态连接和运行时动态连接应用举例
动态连接分为可执行程序装载时动态连接和运行时动态连接,以下代码演示了这两种动态连接。
•准备.so文件
shlibexample.h (1.3 KB) - Interface of Shared Lib Example
shlibexample.c (1.2 KB) - Implement of Shared Lib Example
编译成libshlibexample.so文件
1.$ gcc -shared shlibexample.c -o libshlibexample.so -m32
dllibexample.h (1.3 KB) - Interface of Dynamical Loading Lib Example
dllibexample.c (1.3 KB) - Implement of Dynamical Loading Lib Example
编译成libdllibexample.so文件
1.$ gcc -shared dllibexample.c -o libdllibexample.so -m32
•分别以共享库和动态加载共享库的方式使用libshlibexample.so文件和libdllibexample.so文件
main.c (1.9 KB) - Main program
编译main,注意这里只提供shlibexample的-L(库对应的接口头文件所在目录)和-l(库名,如libshlibexample.so去掉lib和.so的部分),并无提供dllibexample的相关信息,只是指明了-ldl
1.$ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
2.$ export LD_LIBRARY_PATH=$PWD #将当前目录加入默认路径,不然main找不到依赖的库文件,固然也能够将库文件copy到默认路径下。
3.$ ./main
4.This is a Main program!
5.Calling SharedLibApi() function of libshlibexample.so!
6.This is a shared libary!
7.Calling DynamicalLoadingLibApi() function of libdllibexample.so!
8.This is a Dynamical Loading libary!
3、可执行程序的装载
1.可执行程序的装载相关关键问题分析
(1)可执行程序的装载实际上至关于系统调用。execve系统调用比较特殊。
(2)sys_execve内核处理过程:
(3)fmt->load_binary(bprm):用来解析ELF格式文件的执行的位置,这个位置是load_elf_binary。
(4)内核是如何支持多种不一样可执行文件格式的?
本质上是观察者模式,经过修改内核堆栈中EIP的值做为新程序的起点。
(5)庄周(调用execve的可执行程序)入睡(调用execve陷入内核),醒来(系统调用execve返回用户态)发现本身是蝴蝶(被execve加载的可执行程序)。
2. sys_execve的内部处理过程
do_ execve调用do_ execve_ common,do_ execve_ common主要依靠exec_ binprm,其中重要的函数:search_binary_handler(bprm)。
retval=copy_strings(bprm->envc, envp, bprm);
ret= search_binary_handler(bprm);
retval=fmt->load_binary(bprm);
经过实验用gdb跟踪分析一个execve系统调用内核处理函数sys_execve ,验证对Linux系统加载可执行程序所需处理过程的理解
实验过程:
把menu删掉,从新克隆一份
进入test.c中查看:
进入makefile中查看:
开始使用gdb跟踪:
new_ip是返回到用户态的第一条指令的地址 看该可执行程序的入口点地址,发现和new_ip的位置是同样的。
退出调试状态,输入redelf -h hello能够查看hello的EIF头部:
4、浅谈Linux内核装载和启动一个可执行程序
检查ELF可执行文件的有效性,寻找动态连接的“.interp”段,设置动态连接器路径(与动态连接有关),根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,好比代码,数据,只读数据,代码、数据都加载到内存中,默认ELF文件从0x开始加载,开始加载的是头部,会有一些信息,因此入口地址0x(这个地方就是程序的实际入口,可执行文件加载到内存中开始执行的第一行代码从这里开始),初始化ELF进程环境,好比进程启动时EDX寄存器的地址应该是DT_FINI的地址(和动态连接有关),将系统调用的返回地址修改成ELF可执行文件的入口,这个入口点取决于程序的连接方式,对于静态连接的可执行文件,如果静态连接的,elf_entry就是指向可执行文件里边规定的那个头部,即main函数对应的位置,若这个可执行文件是须要依赖其它动态连接库的话,则elf_entry就是指向动态连接器的起点。