《Linux內核分析》第七週學習總結

《Linux內核分析》第七週學習總結shell

                        ——可執行程序的裝載函數

姓名:王瑋怡  學號:20135116學習

1、理論部分總結spa

(一)可執行程序的裝載操作系統

一、預處理、編譯、連接和目標文件的格式命令行

  C代碼通過編譯器的預處理(.cpp),而後編譯成彙編代碼(.asm/.s),由彙編器成目標代碼(.o,二進制文件),再連接成可執行文件,最後由操做系統加載到內存中執行。翻譯

  • 預處理:編譯器將C源代碼中包含的頭文件編譯進來和執行宏替換等工做

  gcc -E hello.c -o hello.icode

  • 編譯:gcc首先要檢查代碼後,把代碼翻譯成彙編語言

  gcc –S hello.i –o hello.sorm

  • 彙編:把編譯階段生成的.s文件轉成二進制目標代碼

  gcc –c hello.s –o hello.oblog

  • 連接:將編譯輸出.o文件連接成最終的可執行文件(hello也是一個二進制文件)

  gcc hello.o –o hello

二、目標文件的格式ELF

(1)目標文件格式分類

 

(2)ABI和目標文件

ABI:應用程序二進制接口,在目標文件中二進制兼容模式

(3)ELF中三種目標文件

  • 可重定位文件  .o文件
  • 可執行文件    
  • 共享目標文件    .so文件

(4)ELF頭

 

*當建立或增長一個進程映像時,系統在理論上將拷貝一個文件的段到一個虛擬的內存段

三、靜態連接的ELF可執行文件與進程的地址空間

  • 可執行文件加載到內存中開始執行的第一行
  • 代碼通常靜態連接會將全部代碼放在一個代碼段
  • 動態連接的進程會有多個代碼段

(二)可執行程序、共享庫和動態進程

一、裝載可執行程序以前的工做

(1)命令行參數和shell環境

  • 列出/usr/bin下的目錄信息:

  $ ls -l /usr/bin

  • Shell自己不限制命令行參數的個數,命令行參數的個數受限於命令自身

  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)命令行參數和環境變量是如何保存和傳遞的

  • shell程序 —> execve —> sys_execve
  • 命令行參數和環境串都放在用戶態堆棧中
  • 初始化新程序堆棧時拷貝進去

 

  • 先函數調用參數傳遞,再系統調用參數傳遞

二、裝載時動態連接和運行時動態連接應用

  動態連接分爲可執行程序裝載時動態連接和運行時動態連接

 

(三)可執行程序的裝載

一、execve系統調用的內核處理過程

1)新的可執行程序起點——通常是地址空間爲0x8048000或0x8048300

(2)execve和fork都是特殊的系統調用——通常的都是陷入到內核態再返回到用戶

  • fork兩次返回,第一次返回到父進程繼續向下執行,第二次是子進程返回到ret_from_fork而後正常返回到用戶態。

  • execve執行的時候陷入到內核態,用execve中加載的程序把當前正在執行的程序覆蓋掉,當系統調用返回的時候也就返回到新的可執行程序起點。

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

  do_execve —> do_execve_common —> exec_binprm

  search_binary_handler符合尋找文件格式對應的解析模塊

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

*load_elf_binary中,調用了start_thread()函數,經過修改內核堆棧中EIP的值做爲新程序的起點

二、sys_execve的內部處理過程

  • 系統調用的入口:do_execve

  return do_execve(getname(filename), argv, envp);

  • 轉到do _ execve _ common函數

  return do_execve_common(filename, argv, envp);

  file = do_ open_exec(filename);     //打開要加載的可執行文件,加載它的文件頭部

  bprm->file = file;

  bprm->filename = bprm->interp = filename->name;    //建立了一個結構體bprm,把環境變量和命令行參數都copy到結構體中

  • exec_binprm:

  ret = search_binary_handler(bprm);  //尋找此可執行文件的處理函數 在其中關鍵的代碼

  list_for_each_entry(fmt, &formats, lh);

  retval = fmt->load_binary(bprm); //在這個循環中尋找可以解析當前可執行文件的代碼並加載出來,實際調用的是load_elf_binary函數

  • 文件解析相關模塊:核心的工做就是把文件映射到進程的空間,對於ELF可執行文件會被默認映射到0x8048000
  • 須要動態連接的可執行文件先加載連接器ld​(load _ elf _ interp 動態連接庫動態連接文件),動態連接器的起點
  • 若是它是一個靜態連接,可直接將文件地址入口進行賦值

三、可執行程序的裝載與「莊生夢蝶」

  莊周(調用execve的可執行程序)入睡(調用execve陷入內核),醒來(系統調用execve返回用戶態)發現本身是蝴蝶(被execve加載的可執行程序)

四、動態連接的可執行程序的裝載

  • 實際上動態連接庫的依賴關係會造成一個「依賴樹」
  • 動態連接庫的裝載過程通常是一個圖的廣度遍歷
  • 動態連接是由動態連接器完成而不是內核

*靜態連接:直接執行可執行程序的入口

*動態連接:裝載和連接以後ld將CPU的控制權交給可執行程序

 

實驗部分 ——Linux內核如何裝載和啓動一個可執行程序

(一)搭建環境

(查看代碼時,可使用shift+G直接跳到文件末尾)

(生成根文件系統時,將init hello放入rootfs地址中,這樣在執行exec文件時,就自動加載hello文件)


(二)使用gdb跟蹤sys_execve內核函數的處理過程

一、加載符號表,並鏈接到端口1234

二、設置斷點

三、執行

輸入c繼續運行,進入到sys_execve系統調用:

輸入s進行跟蹤:

new_ip是返回到用戶態的第一條指令的地址:

相關文章
相關標籤/搜索