请注意:>原作者:张澍> 原创作品转载请注明出处> 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
1.知识点分析
可执行程序的装载过程
过程可概括为:
预处理->汇编->链接->加载到内存
ELF格式三种目标文件
可重定文件:’.o’
保存着代码和适当的数据,用来和其它的object文件一起来创建一个可执行文件或者是一个共享文件
可执行文件:指出exec
保存着一个用来执行的程序,该文件指出了exec(BA_OS)如何来创建程序进程映象
共享文件:’.object’ (可连接其他文件 )
保存着代码和合适的数据,用来被下面两个链接器链接:(主要是.so文件)第一个是链接编辑器(静态链接)【请参看ld(SD_CMD)】,可以和其它的可重定位和共享object文件来创建其它的object第二个是动态链接器,联合一个可执行文件和其它的共享object文件来创建一个进程映象
ELF文件头部内容
可用readelf命令来详细查看ELF文件头
一个ELF头在文件的开始,保存了路线图(road map),描述了该文件的组织情况程序头表(Program header table)告诉系统如何来创建一个进程的内存映像Section头表(Section header table)包含了描述文件Sections的信息。每个Section在这个表中有一个入口,每个入口给出了该Section的名字,大小等信息
ELF文件加载内存的过程
代码段初始位置:0x8048000
程序实际执行位置:0x8048300
一般静态链接会将所有代码放在一个代码段内
动态链接的进程会有多个代码段
可执行程序的执行环境(shell环境)
int main(int argc, char* argv[], char *envp[])详解:
argc:是命令行总的参数个数
argv[]:是argc个参数,其中第0个参数是程序的全名,之后跟用户输入的参数
envp[]:环境变量
Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
命令行参数和环境串都放在用户态堆栈中 :
堆栈执行流程:
shell -> execve -> sys_execve
创建一个新的用户态堆栈的时候,实际上是把命令行参数的内容和环境变量的内容通过指针的方式传递到execve系统调用的内核处理函数,然后内核处理函数在创建可执行程序新的用户态堆栈的时候,会把参数拷贝到用户态堆栈里,初始化新的可执行程序的上下文环境。所以,新的程序能从main函数开始,把对应的参数接收过来,然后执行。但原先在调用execve时,参数只是压在了shell程序当前进程的堆栈上,而这个堆栈在加载完新的可执行程序之后,已经被清空了,内核又创建了一个新进程的用户态堆栈
sys_execve内核处理过程
当execve系统调用陷入到内核里的时候,system_call,调用了sys_execve(),sys_execve内部会解析可执行文件格式,后面的调用顺序:do_execve -> do_execve_common -> exec_binprm
search_binary_handler根据文件头部信息寻找对应的文件格式处理模块,如下:
根据文件名加载了文件的头部,判断文件是什么格式,在列表中寻找能够解释ELF格式的内核模块
当ELF文件格式出现的时候,观察者就能自动执行load_elf_binary,但实际上是在retval = fmt->load_binary(bprm)执行,这个地方实际上是一种多态的机制,本质上是一种观察者模式
在start_thread函数中,将中断返回后的 ip 和 sp 设置成了特定的新值,以便执行新的程序逻辑。
start_thread这个函数有一个pt_regs,一个new_ip,一个new_sp,pt_regs实际上就是内核堆栈的栈底的那部分,发生系统调用int 0x80的时候,把eflags、sp、ip都压入到栈。那么新进程执行的时候,需要把它的起点位置给它替换掉
如果该程序需要动态链接,则elf_interpreter指针不为空,并指向对应的 ld 文件.内核则加载此文件,由该文件进行动态链接,并最终跳入程序头文件中制定的入口点.如下图所示:
2.实验过程
运行:
运行内核:
启动GDB、加载符号表:
设置断点
运行到断点停止:
运行完毕:
总结:
新的可执行程序通过修改内核堆栈EIP作为新程序的起点,从new_ip开始执行后start_thread把返回到用户态的位置从Int 0x80的下一条指令变成新加载的可执行文件的入口位置。当执行到execve系统调用时,陷入内核态,用execve加载的可执行文件覆盖当前进程的可执行程序,当execve系统调用返回时,返回新的可执行程序的执行起点(main函数位置),所以execve系统调用返回后新的可执行程序能顺利执行。execve系统调用返回时,如果是静态链接,elf_entry指向可执行文件规定的头部(main函数对应的位置0x8048***);如果需要依赖动态链接库,elf_entry指向动态链接器的起点。动态链接主要是由动态链接器ld来完成的。