您的位置:首页 > 运维架构 > Linux

Linux内核分析——可执行程序的装载

2016-04-10 20:18 302 查看

链接的过程

首先运行C预处理器cpp,将C的源程序(a.c)翻译成ASCII码的中间文件(a.i)

接着C编译器ccl,将a.i翻译成ASCII汇编语言文件a.s

接着运行汇编器as,将a.s翻译成可重定位的目标文件a.o

最终完全链接成可执行文件a.out

目标文件

目标文件有三种形式:

可重定位的目标文件

可执行目标文件

共享目标文件

ELF格式的可重定位目标文件的结构如下:



.text:已编译程序的机器代码

.rodata:只读数据

.data:已初始化的全局C变量

.bss:未初始化的全局C变量.在目标文件中这个节不占实际空间,仅是一个占位符.

.sysmtab:一个符号表,存放在程序中被定义和引用的函数和全局变量的信息.

.rel.text:当链接器把这个目标文件和其他文件结合时,.text节中的许多位置都需要修改.一般而言,任何调用外部函数或者引用全局变量的指令都要修改.另一个方面,调用本地函数的指令则不需要修改.

.rel//.data:被模块定义或引用的任何全局变量的信息.

.debug:一个调试符号表

.line:原始C源程序中的行号和.text节中机器指令之间的映射.

.strtab:一个字符串表,其中内容包括.symtab和.debug节中的符号表,以及节头部中的节名字.

可以通过readelf -h process查看的ELF文件的头部信息

如何将新程序的数据保存下来

通过shell程序调用execve将命令行参数和环境参数传递给可执行程序的main函数中。而后execve在创建新的用户态堆栈时,则将main函数中参数压入堆栈中。最终执行sys_execve来真正实现在系统下参数的传递。

当新的可执行文件被调用的时候,则旧的可执行文件所占有的空间会被新的可执行文件所占用,从而execve返回时,返回的并非为旧的可执行文件所产生的数据,而是新加载进来的可执行文件的返回数据,从而使新的可执行文件可以被执行。

可执行文件的相关点

start_thread通过修改内核堆栈中EIP的值作为新程序的起点

根据静态链接的可执行文件elf_entry就是可执行文件头中的起点entry,多为main函数对应的位置

若需要依赖动态链接库的话,则elf_entry则指向动态链接器的起点,即将CPU控制权交给ld来加载依赖库并完成动态链接

新的可执行程序被调用前,需要通过修改int 0x80压入内核堆栈的EIP

elf可执行文件会被默认映射到0x8048000这个地址上

execve在内核中的执行过程

execve运行可执行程序的主要步骤:

删除已存在的用户区域:删除当前可执行文件所占有的用户部分中的堆栈空间

隐藏私有区域:为新程序的文本、数据和堆栈创建新的他空间,而这些空间是新的可执行文件所私有的,并且是写时拷贝的。

映射共享区域:如果ELF文件与共享目标连接,就需要动态链接,并映射至用户虚拟地址空间中的共享区域。

设置程序计数器:设置EIP,使其指向新的可执行文件的入口地址

如下图:



execve函数在内核中执行流程

在用户态中调用execve(),引发系统中断,在内核态中执行对应的函数sys_execve

sys_execve函数调用do_execve函数,该函数会读入可执行文件。

接下来系统会调用search_binary_handler,根据可执行文件的类型查找到相应的处理函数。根据每种文件创建一个struct linux_binfmt的结构体,并将其连接到一个链表智商,执行时候系统就会遍历这个链表,从而找到相应的结构。
从而调用对应的load_binary函数开始加载可执行文件。

系统是通过load_elf_binary来加载elf类型的可执行文件。该函数会先读入ELF文件的头部,根据ELF文件的头部信息读入各种数据。

如果存在动态链接库,则需要将动态链接映射到共享区域之中。此时就需要使用load_elf_interp来加载映像,并把返回的入口地址设置为load_elf_interp的动态链接器的入口

如下图所示:



实验部分

实验目的


使用gdb跟踪sys_execve内核函数的处理过程,分析exec*函数对应的系统调用处理过程,理解Linux内核如何装载和启动一个可执行程序。


实验过程

执行MenuOS,其中装载了execve



设置断点



装载和运行一个可执行文件的顺序为:

sys_execve() -> do_execve() -> do_execve_common() -> exec_binprm() -> search_binary_handler() -> load_elf_binary() -> start_thread()

总结

当linux内核或程序(例如shell)用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序。

当进程调用一种exec函数时,该进程执行的程序完全替换为新程序,而新程序则从其main函数开始
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: