《Linux内核分析》第七周 可执行程序的装载

时间:2021-08-27 23:38:10

第七周 可执行程序的装载

一、预处理、编译、链接和目标文件格式

1.可执行文件的由来

  • .c文件;

  • .s文件(汇编文件);

  • .o文件(目标文件);

  • 多个.o文件链接为一个可执行文件,然后加载到内存执行;

2.目标文件的格式ELF

(1)类型:

  • 可重定位问价(.o文件)
  • 可执行文件(操作系统从哪里执行)
  • 共享object文件

(2)ELF文件已经是适应到某一种CPU体系结构的二进制兼容文件了

(3)格式:

  • 头部含有大量原信息
  • 默认的ELF头加载地址是0x8048000,头部大概要到0x48100处或者0x483000处,也就是可执行文件加载到内存之后执行的第一条代码地址
  • 一般静态链接会将所有代码放在一个代码段;动态链接的进程会有多个代码段

二、可执行程序、共享库和动态链接

1.装载可执行文件之前的工作

(1)可执行程序的执行环境:

  • ls本事也是个命令,加上参数,列出/usr/bin下的目录信息
  • Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身
  • Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数

(2)示例:

  • 要先fork一个进程,不然会覆盖shell
  • execlp加载一个程序

(3)命令行参数和环境变量是如何进入新程序的堆栈的?

  • shell程序-->execve-->sys_execve,然后在初始化新程序堆栈的时候拷贝进去
  • 先传递函数调用参数,再传递系统调用参数

2.装载时动态链接和运行时动态链接举例

(1)动态链接分为可执行程序装载时动态链接和运行时动态链接

(2)准备.so文件(链接文件),编译指令:

$ gcc -shared shlibexample.c -o libshlibexample.so -m32

(3)动态加载库指令:(直接include共享库或下面方式)

#include <dlfcn.h>
void *handl=dlopen("文件名",路径);
func=dlsym(handle,"调用使用的名字");

(4)编译:

  • $ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32 #这里只提供shlibexample的-L(库对应的接口头文件所在目录,也就是path to your dir)和-l(库名,如libshlibexample.so去掉lib和.so的部分),并没有提供dllibexample的相关信息,只是指明了-ldl
  • $ export LD_LIBRARY_PATH=$PWD #将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件copy到默认路径下。
  • $ ./main

三、可执行程序的装载

1.装载中的关键问题分析

(1)execve与fork是比较特殊的系统调用:

  • execve用它加载的可执行文件把当前的进程覆盖掉,返回之后就不是原来的程序而是新的可执行程序起点;
  • fork函数的返回点ret_from_fork是用户态起点

(2)sys_execve内部会解析可执行文件格式

  • 顺序:do_execve -> do_execve_common -> exec_binprm
  • search_binary_handle(寻找能解析文件格式的内核模块)
  • 对于ELF格式的可执行文件fmt->load_binary(bprm);执行的应该是load_elf_binary其内部是和ELF文件格式解析的部分需要和ELF文件格式标准结合起来阅读

(3)Linux内核是如何支持多种不同的可执行文件格式的?

  • elf_format和init_elf_binfmt,是观察者模式中的观察者,search_binary_handle相当于被观察者。(多态机制)

2.sys_execve的内部处理过程

  • do_execve
  • do_open_exec(filename)打开要加载的文件
  • 命令行参数,结构体变量copy到bprm结构体中
  • exce_binprm(bprm),关键代码是寻找能解析当前文件的处理模块
  • register_binfmt($elf_format)注册这个格式到链表里,然后寻找能处理的模块
  • ELF可执行文件默认映射到0x8048000这个地址
  • 需要动态链接的可执行文件先加载连接器ld;否则直接把elf文件entry地址赋值给entry即可。
  • start_thread(regs, elf_entry, bprm->p)会将CPU控制权交给ld来加载依赖库并完成动态链接;对于静态链接的文件elf_entry是新程序执行的起点

3.实验:gdb跟踪sys_execve内核

  1. (更新menu内核之后)查看test.c文件,可以看到新增加了exec系统调用
    • 《Linux内核分析》第七周 可执行程序的装载
  2. 直接e hello.c切换到hello.c
    • 《Linux内核分析》第七周 可执行程序的装载
  3. 查看Makefile,发现增加了gcc -o hello hello.c -m32 -static一句
    • 《Linux内核分析》第七周 可执行程序的装载
  4. 启动内核并验证execv函数
    • 《Linux内核分析》第七周 可执行程序的装载

    • 《Linux内核分析》第七周 可执行程序的装载

  5. 冻结内核,启动GDB调试
    • 《Linux内核分析》第七周 可执行程序的装载

    • 《Linux内核分析》第七周 可执行程序的装载

  6. 进行调试
    • 先停在sys_execve处,再设置其它断点;按c一路运行下去直到断点sys_execve
    • 《Linux内核分析》第七周 可执行程序的装载

    • 按s跳入函数内单步执行
    • 《Linux内核分析》第七周 可执行程序的装载

    • 《Linux内核分析》第七周 可执行程序的装载

    • 《Linux内核分析》第七周 可执行程序的装载

    • new_ip是返回到用户态的第一条指令
    • 《Linux内核分析》第七周 可执行程序的装载

  7. 退出调试状态,输入redelf -h hello可以查看hello的EIF头部
    • 《Linux内核分析》第七周 可执行程序的装载

4.动态链接的可执行程序的装载

  • 动态链接库的依赖关系会形成一个图
  • load_elf_interp实际加载动态链接器,entry返回的是动态链接器的入口,根据需求加载动态链接库,根据库的需要再加载更多的库(遍历)