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

  • 相关阅读:
    各种锁
    几百兆的sql文件无法编辑
    og4j1.x升级log4j2.x及异步日志开启
    TSNE/分析两个数据的分布
    _tkinter.TclError: no display name and no $DISPLAY environment variable
    split分割文件
    ubuntu+jdk
    进程操作
    ImportError: No module named apex
    Ubuntu 16.04.4安装Anaconda
  • 原文地址:https://www.cnblogs.com/java-stx/p/5360009.html
Copyright © 2011-2022 走看看