Linux內核分析 第七週 可執行程序的裝載

張嘉琪 原創做品轉載請註明出處 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000

Linux內核分析 第七週 可執行程序的裝載

1、預處理、編譯、連接和目標文件的格式

1.可執行程序是怎麼得來的

  • 可執行文件的建立——預處理、編譯和連接

shiyanlou:~/ $ cd Code                                                [9:27:05]
shiyanlou:Code/ $ vi hello.c                                          [9:27:14]
shiyanlou:Code/ $ gcc -E -o hello.cpp hello.c -m32                    [9:34:55]
shiyanlou:Code/ $ vi hello.cpp                                        [9:35:04]
shiyanlou:Code/ $ gcc -x cpp-output -S -o hello.s hello.cpp -m32      [9:35:21]
shiyanlou:Code/ $ vi hello.s                                          [9:35:28]
shiyanlou:Code/ $ gcc -x assembler -c hello.s -o hello.o -m32         [9:35:58]
shiyanlou:Code/ $ vi hello.o                                          [9:38:44]
shiyanlou:Code/ $ gcc -o hello hello.o -m32                           [9:39:37]
shiyanlou:Code/ $ vi hello                                            [9:39:44]
shiyanlou:Code/ $ gcc -o hello.static hello.o -m32 -static            [9:40:21]
shiyanlou:Code/ $ ls -l                                               [9:41:13]
-rwxrwxr-x 1 shiyanlou shiyanlou   7292  3\u6708 23 09:39 hello
-rw-rw-r-- 1 shiyanlou shiyanlou     64  3\u6708 23 09:30 hello.c
-rw-rw-r-- 1 shiyanlou shiyanlou  17302  3\u6708 23 09:35 hello.cpp
-rw-rw-r-- 1 shiyanlou shiyanlou   1020  3\u6708 23 09:38 hello.o
-rw-rw-r-- 1 shiyanlou shiyanlou    470  3\u6708 23 09:35 hello.s
-rwxrwxr-x 1 shiyanlou shiyanlou 733254  3\u6708 23 09:41 hello.static

 

2.目標文件的格式ELF

  • ELF文件格式http://www.muppetlabs.com/~breadbox/software/ELF.txtlinux

  • 目標文件三種形式:shell

    1. 可重定位文件(o文件)
    2. 可執行文件(指出了應該從哪裏開始執行)
    3. 共享文件(主要是.so文件,用來被連接編輯器和動態連接器連接)
  • 查看ELF文件的頭部bash

    shiyanlou:Code/ $ readelf -h hello

     

  • 查看該ELF文件依賴的共享庫
shiyanlou:sharelib/ $ ldd main   [21:25:56]
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 [21:37:00]
/lib/ld-linux.so.2 (0xf779e000)
linux-gate.so.1 =>  (0xf779d000)
# readelf -d 也能夠看依賴的so文件
shiyanlou:sharelib/ $ readelf -d main  [21:28:04]
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

2、可執行程序、共享庫和動態連接

 

可執行程序的執行環境編輯器

  •  命令行參數和shell環境,通常咱們執行一個程序的Shell環境,咱們的實驗直接使用execve系統調用。
  • $ ls -l /usr/bin 列出/usr/bin下的目錄信息
  • Shell自己不限制命令行參數的個數, 命令行參數的個數受限於命令自身
  • Shell會調用execve將命令行參數和環境參數傳遞給可執行程序的main函數
  • int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
  • 庫函數exec*都是execve的封裝例程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
    int pid;
    /* fork another process */
    pid = fork();
    if (pid<0) 
    { 
        /* error occurred */
        fprintf(stderr,"Fork Failed!");
        exit(-1);
    } 
    else if (pid==0) 
    {
        /*   child process   */
        execlp("/bin/ls","ls",NULL);
    } 
    else 
    { 
        /*     parent process  */
        /* parent will wait for the child to complete*/
        wait(NULL);
        printf("Child Complete!");
        exit(0);
    }
}
 
  • 命令行參數和環境串都放在用戶態堆棧中
  • 裝載時動態連接和運行時動態連接應用舉例
  •  動態連接分爲可執行程序裝載時動態連接和運行時動態連接,以下代碼演示了這兩種動態連接。
  • 準備.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
  1. $ 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、可執行程序的裝載

  • Shell會調用execve將命令行參數和環境參數傳遞給可執行程序的main函數
1 int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
  • 庫函數exec*都是execve的封裝例程

1.可執行程序的裝載相關關鍵問題分析

  • sys_execve內部會解析可執行文件格式spa

    • doexecve -> doexecvecommon -> execbinprm命令行

    • searchbinaryhandler符合尋找文件格式對應的解析模塊,以下:3d

    1369    list_for_each_entry(fmt, &formats, lh) {
    1370        if (!try_module_get(fmt->module))
    1371            continue;
    1372        read_unlock(&binfmt_lock);
    1373        bprm->recursion_depth++;
    1374        retval = fmt->load_binary(bprm);
    1375        read_lock(&binfmt_lock);

     

  • 對於ELF格式的可執行文件fmt->loadbinary(bprm);執行的應該是loadelf_binary其內部是和ELF文件格式解析的部分須要和ELF文件格式標準結合起來閱讀調試

  • Linux內核是如何支持多種不一樣的可執行文件格式的?code

    82  static struct linux_binfmt elf_format = {
    83  .module     = THIS_MODULE,
    84  .load_binary    = load_elf_binary,
    85  .load_shlib = load_elf_library,
    86  .core_dump  = elf_core_dump,
    87  .min_coredump   = ELF_EXEC_PAGESIZE,
    88   };
    2198 static int __init init_elf_binfmt(void)
    2199 {
    2200    register_binfmt(&elf_format);
    2201    return 0;
    2202 }

     

2.sys_execve的內部處理過程

裝載和啓動一個可執行程序依次調用如下函數:

sysexecve() -> doexecve() -> doexecvecommon() -> execbinprm() -> searchbinaryhandler() -> loadelfbinary() -> startthread()

  • elfformat 和 initelf_binfmt,這裏是否是就是觀察者模式中的觀察者?

  • 可執行文件開始執行的起點在哪裏?如何才能讓execve系統調用返回到用戶態時執行新程序?

3.可執行程序的裝載與莊生夢蝶的故事

  • 莊生夢蝶 —— 醒來迷惑是莊周夢見了蝴蝶仍是蝴蝶夢見了莊周?
  • 莊周(調用execve的可執行程序)入睡(調用execve陷入內核),醒來(系統調用execve返回用戶態)發現本身是蝴蝶(被execve加載的可執行程序)
  • 修改int 0x80壓入內核堆棧的EIP
  • loadelfbinary -> start_thread

4.淺析動態連接的可執行程序的裝載

  1. 能夠關注ELF格式中的interp和dynamic。
  2. 動態連接庫的裝載過程是一個圖的遍歷。
  3. 裝載和鏈接以後ld將CPU的控制權交給可執行程序。

實驗報告 使用gdb跟蹤sys_execve內核函數的處理過程

 

 

  • 可執行文件的建立——預處理、編譯和連接

  •  查看ELF文件頭

  •  vi Makefile 發現增長了gcc -o hello hello.c -m32 -static一句

  •  啓動內核並驗證execv函數

  •  gdb調試

 

  • 先停在sys_execve處,再設置其它斷點;按c一路運行下去直到斷點sys_execve

 

總結部分


 

  • 對「Linux內核裝載和啓動一個可執行程序」的理解

    當linux內核或程序(例如shell)用fork函數建立子進程後,子進程每每要調用一種exec函數以執行另外一個程序。當進程調用一種exec函數時,該進程執行的程序徹底替換爲新程序,而新程序則從其main函數開始執行。由於調用exec並不建立新進程,因此先後的進程ID並未改變。exec只是用一個全新的程序替換了當前進程的正文、數據、堆和棧段。

相關文章
相關標籤/搜索