7.Linux内核如何装载和启动一个可执行程序

请注意:>原作者:张澍> 原创作品转载请注明出处> 《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来完成的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值