zoukankan      html  css  js  c++  java
  • Linux内核设计第七周 ——可执行程序的装载

    Linux内核设计第七周

    ——可执行程序的装载

    第一部分 知识点总结

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

    1、可执行程序是怎么得来的

    编译链接的过程

    • 预处理阶段

    gcc -E -o XX.cpp XX.c -m32 
    XX.cpp是预处理文件

    • 编译器生成汇编代码阶段

    gcc -x cpp-output -S -o hello.s hello.cpp -m32 
    XX.s是汇编代码

    • 汇编器生成目标代码阶段

    gcc -x assembler -c hello.s -o hello.o -m32 
    XX.o是目标代码

    • 链接器生成可执行文件阶段

    gcc -o hello.static hello.c -m32 -static

    • 查看hello和hello.static的区别:

    ls -l

    2、目标文件的格式ELF

    (1)ELF目标文件格式的分类:

    (2)ABI和目标文件格式的关系?

    ABI是应用程序二进制接口。 
    目标文件是二进制兼容,目标文件已经适应到一种CPU体系结构的二进制指令。 
    可以说二者可以相互交换,基本上是一样的。

    (3)ELF文件格式

    • 查看ELF文件的头部

    shiyanlou:Code/ $ readelf -h hello

    • 查看该ELF文件依赖的共享库

    shiyanlou:sharelib/ $ ldd main

    linux-gate.so.1 =>  (0xf774e000) # 这个是vdso - virtual DSO:dynamically shared object,并不存在这个共享库文件,它是内核的一部分,为了解决libc与新版本内核的系统调用不同步的问题,linux-gate.so.1里封装的系统调用与内核支持的系统调用完全匹配,因为它就是内核的一部分嘛。而libc里封装的系统调用与内核并不完全一致,因为它们各自都在版本更新。
    libshlibexample.so => /home/shiyanlou/LinuxKernel/sharelib/libshlibexample.so (0xf7749000)
    libdl.so.2 => /lib32/libdl.so.2 (0xf7734000)
    libc.so.6 => /lib32/libc.so.6 (0xf7588000)
    /lib/ld-linux.so.2 (0xf774f000)     
    

    shiyanlou:sharelib/ $ ldd /lib32/libc.so.6

    /lib/ld-linux.so.2 (0xf779e000)
    linux-gate.so.1 =>  (0xf779d000)
    
    • readelf -d 也可以看依赖的so文件

    shiyanlou:sharelib/ $ readelf -d main

    Dynamic section at offset 0xf04 contains 26 entries:
     0x00000001 (NEEDED)                     共享库:[libshlibexample.so]
     0x00000001 (NEEDED)                     共享库:[libdl.so.2]
     0x00000001 (NEEDED)                     共享库:[libc.so.6]
     0x0000000c (INIT)                       0x80484f0
     0x0000000d (FINI)                       0x8048804
     0x00000019 (INIT_ARRAY)                 0x8049ef8
    

    3、静态链接的ELF可执行文件和进程的地址空间

    • 静态链接将会把所有代码放到代码段
    • 动态链接的进程会有多个代码段

    二、可执行程序、共享库和动态加载

    1.装载可执行程序之前的工作

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

    • 一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。
    • Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身。

    int main(int argc, char *argv[]) 
    int main(int argc, char argv[], char envp[])//envp是shell的执行环境

    • Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数

    int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

    • 库函数exec*都是execve的封装例程

    • shell中使用fork()来创建新进程。

    • 命令行参数和环境串都放在用户态堆栈中。

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

    动态链接分为可执行程序装载时动态链接和运行时动态链接,如下代码演示了这两种动态链接。

    (1)准备.so文件 
    共享库

    shlibexample.h (1.3 KB) - Interface of Shared Lib Example shlibexample.c (1.2 KB) - Implement of Shared Lib Example

    • 编译成libshlibexample.so文件

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

    动态链接库

    dllibexample.h (1.3 KB) - Interface of Dynamical Loading Lib Example 
    dllibexample.c (1.3 KB) - Implement of Dynamical Loading Lib Example

    • 编译成libdllibexample.so文件

    $ gcc -shared dllibexample.c -o libdllibexample.so -m32

    分别以共享库和动态加载共享库的方式使用libshlibexample.so文件和libdllibexample.so文件。

    编译main,只提供

    • shlibexample的-L(库对应的接口头文件所在目录)
    • -l(库名,如libshlibexample.so去掉lib和.so的部分)

    并没有提供dllibexample的相关信息,只是指明了-ldl。

    $ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32 
    $ export LDLIBRARYPATH=$PWD 
    # 将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件copy到默认路径下。 
    $ ./main

    三、可执行程序的装载

    1.可执行程序的装载相关关键问题分析

    (1)execve与fork比较

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

    • 是同一个进程,只不过是进程执行的可执行程序被换成了新的,只返回一次。

    fork函数的返回点ret_ from_ fork是用户态起点。

    • 进入内核态后,返回两次。

    (2)sys_ execve内核处理过程

    do_ execve -> do_ execve_ common -> exec_ binprm

    最后,根据文件头部信息寻找对应的文件格式处理模块

       list_for_each_entry(fmt, &formats, lh) {//在链表中寻找可以处理这种格式(比如ELF)的模块
           if (!try_module_get(fmt->module))
               continue;
           read_unlock(&binfmt_lock);
           bprm->recursion_depth++;
           retval = fmt->load_binary(bprm);
           //对于ELF格式的可执行文件fmt->load_binary(bprm);执行的应该是load_elf_binary其内部是和ELF文件格式解析的部分需要和ELF文件格式标准结合起来阅读
           read_lock(&binfmt_lock);
    

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

    2.sys_execve的内部处理过程

    elf_interpreter:需要动态链接

    startthread(regs,elfentry,bprm->p)中的elf_entry

    3.使用gdb跟踪sys_execve内核函数的处理过程

    见实验部分

    4.可执行程序的装载与庄生梦蝶的故事

    庄周梦蝶

    庄周(调用execve的可执行程序)入睡(调用execve陷入内核),醒来(系统调用execve返回用户态)发现自己是蝴蝶(被execve加载的可执行程序)。

    5.浅析动态链接的可执行程序的装载

    • 动态链接的过程中,内核做了什么

    (1)可执行程序需要依赖动态链接库,而这个动态链接库可能会依赖其他的库,这样形成了一个关系图——动态链接库会生成依赖树。

    (2)依赖动态链接器进行加载库并进行解析(这就是一个图的遍历),装载所有需要的动态链接库;之后ld将CPU的控制权交给可执行程序

    (3)动态链接的过程主要是动态链接器在起作用,而不是内核完成的。

    第二部分 实验部分

    1、更新内核

    2、查看test.c文件,更新如图所示代码。

    3、在exec函数中执行了动态链接代码。

    4、查看makefile内容,添加对hello程序的处理代码。并修改相关代码。

    5、图为Menu系统运行的结果

    6、gdb调试过程中,执行到start_ thread,查看new_ ip的内容。

    如上图所示,new_ ip中存储的地址就是静态链接中程序的进入地址。

    7、图为建立新堆栈的具体步骤

    总结:

    阐明对“Linux内核装载和启动一个可执行程序”的理解。

    • linux内核首先经过预处理、编译、汇编生成一个可执行文件。
    • 然后使用链接器对该可执行程序进行链接,生成可执行程序。
    • 在连接的过程中,又分为动态链接和静态链接,动态链接过程主要是有链接器ld完成的,而不是内核完成的。

    宋宸宁+ 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程、http://mooc.study.163.com/course/USTC-1000029000

  • 相关阅读:
    联想 Vibe Shot(Z90-3) 免recovery 获取ROOT权限 救砖 VIBEUI V3.1_1625
    联想 Z5S(L78071)免解锁BL 免rec 保留数据 ROOT Magisk Xposed 救砖 ZUI 10.5.370
    联想 Z5(L78011) 免解锁BL 免rec 保留数据 ROOT Magisk Xposed 救砖 ZUI 10.5.254
    联想 S5 Pro(L78041)免解锁BL 免rec 保留数据 ROOT Magisk Xposed 救砖 ZUI 5.0.123
    第二阶段 冲刺八
    第二阶段 冲刺七
    第二阶段 冲刺六
    第二阶段 冲刺五
    代码大全阅读笔记03
    学习进度十二
  • 原文地址:https://www.cnblogs.com/java-stx/p/5360009.html
Copyright © 2011-2022 走看看