zoukankan      html  css  js  c++  java
  • 《Linux内核分析》第七周 可执行程序的装载

    第七周 可执行程序的装载

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

    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系统调用

    2. 直接e hello.c切换到hello.c

    3. 查看Makefile,发现增加了gcc -o hello hello.c -m32 -static一句

    4. 启动内核并验证execv函数

    5. 冻结内核,启动GDB调试

    6. 进行调试

      • 先停在sys_execve处,再设置其它断点;按c一路运行下去直到断点sys_execve

      • 按s跳入函数内单步执行

      • new_ip是返回到用户态的第一条指令

    7. 退出调试状态,输入redelf -h hello可以查看hello的EIF头部

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

    • 动态链接库的依赖关系会形成一个图
    • load_elf_interp实际加载动态链接器,entry返回的是动态链接器的入口,根据需求加载动态链接库,根据库的需要再加载更多的库(遍历)
  • 相关阅读:
    使用netty实现im聊天
    使用rabbitmq实现集群im聊天服务器消息的路由
    springcloud feign使用
    10万用户一年365天的登录情况如何用redis存储,并快速检索任意时间窗内的活跃用户
    redis的rdb与aof持久化机制
    springcloud-zinpin的安装与使用
    kafka的基本安装与使用
    RabbitMq 实现延时队列-Springboot版本
    RabbitMq 基本命令
    Dcoker 安装 rabbitMq
  • 原文地址:https://www.cnblogs.com/shadow135211/p/5362143.html
Copyright © 2011-2022 走看看