第一部分
理解编译链接的过程和ELF可执行文件格式
图1 预处理、编译、汇编、链接、静态链接
目标文件中的内容至少有编译后的机器指令代码、数据。没错,除了这些内容以外,目标文件中还包括了链接时所须要的一些信息,比如符号表、调试信息、字符串等。
常见目标文件的格式:A.out–>COFF–>PE/ELF
ELF格式的文件中有三种主要的目标文件
1、可重定位(relocatable)文件保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或是一个共享文件(主要是 .o 文件)
2、一个可执行(executable)文件保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建序进程映像。
3、一个共享object文件保存这代码和合适的数据,用来被下面的两个链接器链接。第一个连接编译器(请查看ld(SD_CMD)),也可以和其他可重定位和共享object文件来创建他的object。第二是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映像。(主要是 .so 文件)。
通过 readelf -h 命令可以查看到目标文件elf头部信息
第二部分
可执行程序的装载
静态链接:可执行文件加载到内存中开始执行的第一行代码,一般静态链接会将所有代码放在一个代码段。
动态链接:可执行程序装载时的动态链接和运行时的动态链接。
装载时动态链接(Load-time Dynamic Linking):这种方法的前提是在编译之前已经明确知道要调用的动态库的哪些函数,编译时在目标文件中只保留必要的链接信息,而不含动态库函数代码;当程序执行时,调用函数的时候利用链接信息加载动态库函数代码并在内存中将其链接入调用程序的执行空间中(全部函数加载进内存),其主要目的是便于代码共享。(动态加载程序,处在加载阶段,主要为了共享代码,共享代码内存)
运行时动态链接(Run-time Dynamic Linking):这种方式是指在编译之前并不知道将会调用哪些动态库函数,完全是在运行过程中根据需要决定应调用哪个函数,将其加载到内存中(只加载调用的函数进内存);并标识内存地址,其他程序也可以使用该程序,并获得动态库函数的入口地址。(动态库在内存中只存在一份,处在运行阶段)
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的封装例程
sys_execve内部会解析可执行文件格式,具体执行过程为do_execve -> do_execve_common -> exec_binprm
search_binary_handler符合寻找文件格式对应的解析模块
对于ELF格式的可执行文件fmt->load_binary(bprm);执行的应该是load_elf_binary
庄生梦蝶:load_elf_binary -> start_thread,通过修改int 0x80压入内核堆栈中的EIP的值作,为新程序的起点(elf_entry)。
静态链接的文件直接将变量赋给elf_entry。
需要依赖动态链接的可执行文件先加载链接器ld作为elf_entry。
需要依赖其他链接库的话,将CPU控制权变给ld来加载依赖库并完成动态链接。
图2 execlp执行过程的流程图
第三部分
使用gdb跟踪分析一个execve系统调用内核处理函数sys_execve
图3 内核准备
图4 在sys_execve设置断点
图5 在exec_binprm设置断点,可执行文件的处理
图6 load_elf_binary()寻找能够解析当前可执行文件代码模块
图7 在start_thread()设置断点,并观察其new_ip
图8 通过查看hello的elf头文件发现与new_ip吻合
小市民 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000