实验七:Linux内核如何装载和启动一个可执行程序

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

 

  1. 题目自拟,内容围绕对Linux内核如何装载和启动一个可执行程序

  2. 可以结合实验截图、ELF可执行文件格式、用户态的相关代码等

  3. 博客内容中需要仔细分析新可执行程序的执行起点及对应的堆栈状态等

  4. 总结部分需要阐明自己对“Linux内核装载和启动一个可执行程序”的理解

实验报告:

 

  1. 理解编译链接的过程和ELF可执行文件格式;

    1. C代码——预处理——汇编代码——目标代码——可执行文件
    2. gcc -E hello.c -o hello.i

       gcc –hello.i –hello.s
       gcc –hello.s –hello.o

    1. ELF可执行文件格式
    2. 预处理负责把include的文件包含进来及宏替换工作。 hello和hello.o都是ELF格式的文件
    3. ELF是一个可重定位(relocatable)文件保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件
  2. 编程使用exec*库函数加载一个可执行文件,动态链接分为可执行程序装载时动态链接和运行时动态链接,编程练习动态链接库的这两种使用方式;

    1. 静态链接的ELF可执行文件和进程的地址空间
      程序从0x804800开始
    2. 动态链接加载前准备的工作

       (1)命令行参数和shell环境,一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。 

      • $ ls -l /usr/bin 列出/usr/bin下的目录信息
      • Shell本身不限制命令行参数的个数, 命令行参数的个数受限于命令自身
        • 例如,int main(int argc, char *argv[])
        • 又如, int main(int argc, char *argv[], char *envp[])
      • Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数
        • int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
        • 库函数exec*都是execve的封装例程

      (2)命令行参数和环境变量是如何传递和保存的

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

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

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

      • 准备.so文件 

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

      编译成libshlibexample.so文件

      1
      $ 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文件

      1
      $ gcc -shared dllibexample.c -o libdllibexample.so -m32 #编译方式和上面一样
      • 分别以共享库和动态加载共享库的方式使用libshlibexample.so文件和libdllibexample.so文件 

      main.c (1.9 KB) - Main program 
      编译main,注意这里只提供shlibexample的-L(库对应的接口头文件所在目录)和-l(库名,如libshlibexample.so去掉lib和.so的部分),并没有提供dllibexample的相关信息,只是指明了-ldl

      1
      2
      3
      4
      5
      6
      7
      8
      $ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
      $ export LD_LIBRARY_PATH=$PWD #将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件copy到默认路径下。
      $ ./main
      This is a Main program!
      Calling SharedLibApi() function of libshlibexample.so!
      This is a shared libary!
      Calling DynamicalLoadingLibApi() function of libdllibexample.so!
      This is a Dynamical Loading libary!
  3. 使用gdb跟踪分析一个execve系统调用内核处理函数sys_execve ,验证您对Linux系统加载可执行程序所需处理过程的理解
    1.首先将exec函数写入test.c 函数


    2.重新make rootfs 发现已经成功写进一个exec 函数

    3.运行一下exec 


    4.正式来调试了设置断点

    5.单步进入第一个断点

    6.单步进入第二个断点

    7.单步进入第三个断点

    8.查看new_ip 地址

    9.查看hello文件信息 ==》 发现elf文件的hello入口地址就是new_ip的地址


  4. 特别关注新的可执行程序是从哪里开始执行的?为什么execve系统调用返回后新的可执行程序能顺利执行?对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时会有什么不同?

         新的可执行程序通过修改内核堆栈eip作为新程序的起点,
    从new_ip开始执行后start_thread把返回到用户态的位置从int 0x80的下一条指令变成新加载的可执行文件的入口位置。

        当执行到execve系统调用时,进入内核态,用execve()加载的可执行文件覆盖当前进程的可执行程序,
    当execve系统调用返回时,返回新的可执行程序的执行起点(main函数),所以execve系统调用返回后新的可执行程序能顺利执行。

        execve系统调用返回时,如果是静态链接,elf_entry指向可执行文件规定的头部(main函数对应的位置0x8048***);如果需要依赖动态链接库,elf_entry指向动态链接器的起点。动态链接主要是由动态链接器ld来完成的。

  5. 总结

    装载和启动一个可执行程序依次调用以下函数:

    sys_execve() -> do_execve() -> do_execve_common() -> exec_binprm() -> search_binary_handler() -> load_elf_binary() -> start_thread()

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

    可执行文件开始执行的起点在哪里?如何才能让execve系统调用返回到用户态时执行新程序?

    • 庄生梦蝶 —— 醒来迷惑是庄周梦见了蝴蝶还是蝴蝶梦见了庄周?

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

    • 修改int 0x80压入内核堆栈的EIP

    • load_elf_binary -> start_thread

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

    (1)可以关注ELF格式中的interp和dynamic。

    (2)动态链接库的装载过程是一个图的遍历。

    (3)装载和连接之后ld将CPU的控制权交给可执行程序。

posted @ 2016-04-10 09:46  ailx10  阅读(758)  评论(0编辑  收藏  举报