日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深入分析ELF文件结构及其载入过程

發布時間:2023/12/14 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入分析ELF文件结构及其载入过程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 前言
    • ELF目標文件類型
    • 以下面例子深入分析ELF
      • 詳解file命令結果的各個部分
      • ELF的文件結構
      • ELF知識擴展
    • Linux系統裝載ELF的過程
      • 用戶層面
      • 系統層面

前言

一般程序符號和數據,包括:全局變量,靜態全局變量,全局函數,靜態全局函數,外部符號(函數/變量),局部變量,局部靜態變量,字面量(常量)等。程序從源碼(如:C語言)到ELF二進制可執行文件,一般需要通過編譯器和鏈接器來處理并生產。

ELF文件由4部分組成,分別是ELF頭(ELF header)、程序頭表(Program header table)、節(Section)和節頭表(Section header table)。實際上,一個文件中不一定包含全部內容,而且它們的位置也未必如同所示這樣安排,只有ELF頭的位置是固定的,其余各部分的位置、大小等信息由ELF頭中的各項值來決定。

ELF目標文件類型

(1)可重定位的對象文件(Relocatable file)

Linux中.o文件。這類文件包含了代碼和數據,可以用來鏈接生成可執行或共享目標文件,靜態鏈接庫也可以歸為這一類。

(3)可執行的對象文件(Executable file)

ELF可執行文件。

(3)可共享庫文件(Shared object file)

Linux中.so文件。這類文件可以跟其他的重定位文件和.so文件鏈接,產生新的.so文件。第二種是動態鏈接器可以將幾個這種.so文件與可執行文件結合,作為進程映像的一部分來運行。

(4) Linux下的核心轉存文件(Core Dump File)

當進程意外終止時,系統可以將該進程的地址空間的內容及終止時的一些其它信息轉存到此Dump File。

以下面例子深入分析ELF

以下面的C程序為例:

#include <pthread.h> #include <stdio.h>const char *FLAG = "[INFO]";char *infoprefixstr = "ThdID:";const int const_num = 111; int gbl_num = 222;static void * static_func(){printf("static_func be called.\n");return NULL; }void *thread_start(void *args) {printf("%s%s%ld. const_num:%d. gbl_num:%d\n",FLAG, infoprefixstr, *((pthread_t *) args),const_num, gbl_num);return static_func(); }int main(int argc, char **argv) {pthread_t thds[argc - 1];for (int i = 0; i < argc; i++) {pthread_create(&thds[i], NULL, &thread_start, &thds[i]);}for (int i = 0; i < argc; i++) {pthread_join(thds[i], NULL);}static int static_scope_var = 333;printf("main exitting......static_scope_var:%d\n",static_scope_var);return 0; }

CMakeList.txt配置如下:

cmake_minimum_required(VERSION 3.15) project(test1 C)set(CMAKE_C_STANDARD 99)add_executable(test1 main.c) target_link_libraries(test1 PUBLIC -lpthread)

編譯構建產生test1二進制程序。

詳解file命令結果的各個部分

使用file命令查看test1的文件詳情,得到如下結果:

$ file test1/cmake-build-debug/test1 test1/cmake-build-debug/test1: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=45d6523007a7906dfb699d5f6fc66f3f4b7ec720, with debug_info, not stripped

ELF 64-bit表示文件是64位ELF格式的。

LSB shared object表示ELF文件是一個共享對象。

注:“LSB executable”(ET_EXEC)和"LSB shared object"(ET_DYN)的區別是什么?

  • 在Linux內核/動態加載程序中ET_EXEC與ET_DYN的主要作用是通知可執行文件是否可以通過ASLR放置在隨機存儲器中。GCC在編譯時,默認會增加-pie選項,使得生成的ELF是ET_DYN的。PIE可執行文件是DYN的,它們可以被地址隨機化,就像共享庫so一樣。

注:-pie、-fpie、-fPIE、-fpie、fPIC的區別是什么?

  • -fPIE與-fpie是等價的。

  • -pie,往往和-fpie或-fPIE配合使用,用于在目標機器上生成與位置無關的可執行文件。-pie選項在鏈接時指定,-fpie或-fPIE選項在編譯時指定。PIE(Position-Independent-Executable)是Binutils,glibc和gcc的一個功能,能用來創建能像共享庫一樣可重分配地址的程序,這種程序須連接到Scrt1.o。標準的可執行程序需要固定的地址,并且只有被裝載到這個地址時,程序才能正確執行。PIE能使程序像共享庫一樣在主存任何位置裝載,這需要將程序編譯成位置無關,并鏈接為ELF共享對象。

  • -fpic,使用于在目標機支持編譯共享庫時使用。編譯出的代碼將通過全局偏移表(Global Offset Table)中的常數地址訪存,動態裝載器將在程序開始執行時解析GOT表項(注意,動態裝載器操作系統的一部分,連接器是GCC的一部分)。而gcc中的-fPIC選項則是針對某些特殊機型做了特殊處理,比如適合動態鏈接并能避免超出GOT大小限制之類的錯誤。

  • -fPIC與-fpic都是在編譯時加入的選項,用于生成位置無關的代碼(Position-Independent-Code)。這兩個選項都是可以使代碼在加載到內存時使用相對地址,所有對固定地址的訪問都通過全局偏移表(GOT)來實現。-fPIC和-fpic最大的區別在于是否對GOT的大小有限制。-fPIC對GOT表大小無限制,所以如果在不確定的情況下,使用-fPIC是更好的選擇。

x86-64表示目標機CPU指令集架構。

version 1 (SYSV)表示操作系統和ABI標識符,ELF規范中包含如下幾類:

Table 5. Operating System and ABI Identifiers, e_ident[EI_OSABI] Name Value Meaning ELFOSABI_SYSV 0 System V ABI ELFOSABI_HPUX 1 HP-UX operating system ELFOSABI_STANDALONE 255 Standalone (embedded) application

dynamically linked表示ELF是動態鏈接的。

interpreter /lib64/ld-linux-x86-64.so.2表示程序的加載器。

for GNU/Linux 3.2.0表示操作系版本號。

BuildID[sha1]=45d6523007a7906dfb699d5f6fc66f3f4b7ec720表示文件的構建碼。個人理解是m

with debug_info表示ELF文件帶有debug信息。

not stripped表示保留ELF的所有符號表信息,未刪除一些符號表信息。如果輸出的是stripped表示已經刪除了ELF中一些符號表信息。

注:一般編譯出來的ELF中都有符號表(symbol table),該表中包括所有的符號(程序的入口點還有變量的地址等等)。這些符號表可以用 strip工具去除,這樣的話這個文件就無法讓debug程序跟蹤了,但是會生成比較小的可執行文件。ELF可執行文件中的符號表可以部分去除,由于部分符號在加載運行時起著重要的作用,所以用strip永遠不可能完全去除elf格式文件中的符號表。對未連接的目標文件來說如果用strip去掉符號表的話,會導致連接器無法連接。

ELF文件中除了包含指令、數據,還包括符號表、調試信息、字符串等,如果是可重定位對象文件還包含鏈接時所須的一些信息。一般目標文件將這些信息按不同的屬性以Section(節)的形式存儲,有時候也叫Segment(段),在一般情況下,它們都表示一個一定長度的區域,基本上不加以區別。后面將統一稱為“段”。

ELF的文件結構

基本結構如下所示:

+====================+ + ELF header + // 包含了整個文件的基本屬性,如:文件版本,目標機器型號,入口地址。 +====================+ +Program header table+ // 程序標頭表是一組程序標頭,它們定義了運行時程序的內存布局。對于.obj文件可選的 +====================+ + .interp + // 可執行文件所需要的動態鏈接器的位置。 +--------------------+ + .note.ABI-tag + // 用于聲明ELF的預期運行時ABI。包括操作系統名稱及其運行時版本。 +--------------------+ + .note.gnu.build-id + // 表示唯一的構建ID位串。 +--------------------+ + .gnu.hash + // 符號hash表。若段名是.hash,則使用的是SYSV hash,其比gnu hash性能差。 +--------------------+ + .dynsym + // 動態符號表用來保存與動態鏈接相關的導入導出符號,不包括模塊內部的符號。 +--------------------+ + .dynstr + // 動態符號字符串表,用于保存符號名的字符串表。靜態鏈接時為.strtab。 +--------------------+ + .gnu.version + // 表中條目與.dynsym動態符號表相同。每個條目指定了相應動態符號定義或版本要求。 +--------------------+ + .gnu.version_r + // 版本定義。 +--------------------+ + .rela.dyn + // 包含共享庫(PLT除外)所有部分的RELA類型重定位信息。 +--------------------+ + .rela.plt + // 包含共享庫或動態鏈接的應用程序的PLT節的RELA類型重定位信息。 +--------------------+ + .init + // 程序初始化段。 +--------------------+ + .plt + // 過程鏈接表(Procedure Linkage Table),用來實現延遲綁定。 +--------------------+ + .plt.got + // 暫無。。。。。 +--------------------+ + .text + // 代碼段 +--------------------+ + .fini + // 程序結束段 +--------------------+ + .rodata + // 只讀變量(const修飾的)和字符串變量。 +--------------------+ + .rodata1 + // 據我所知,.rodata和.rodata1是相同的。一些編譯器會.rodata分為2個部分。 +--------------------+ + .eh_frame_hdr + // 包含指針和二分查找表,(一般在C++)運行時可以有效地從eh_frame中檢索信息。 +--------------------+ + .eh_frame + // 它包含異常解除和源語言信息。此部分中每個條目都由單個CFI(呼叫幀信息)表示。 +--------------------+ + .init_array + // 包含指針指向了一些初始化代碼。初始化代碼一般是在main函數之前執行的。 +--------------------+ + .fini_array + // 包含指針指向了一些結束代碼。結束代碼一般是在main函數之后執行的。 +--------------------+ + .dynamic + // 保存動態鏈接器所需的基本信息。 +--------------------+ + .got + // 全局偏移表,存放所有對于外部變量引用的地址。 +--------------------+ + .got.plt + // 保存所有對于外部函數引用的地址。延遲綁定主要使用.got.plt表。 +--------------------+ + .data + // 全局變量和靜態局部變量。 +--------------------+ + .data1 + // 據我所知,.data和.data1是相同的。一些編譯器會.data分為2個部分。 +--------------------+ + .bss + // 未初始化的全局變量和局部局部變量。 +--------------------+ + .comment + // 存放編譯器版本信息 +--------------------+ + .debug_aranges + // 內存地址和編譯之間的映射 +--------------------+ + .debug_info + // 包含DWARF調試信息項(DIE)的核心DWARF數據 +--------------------+ + .debug_abbrev + // .debug_info部分中使用的縮寫 +--------------------+ + .debug_line + // 程序行號 +--------------------+ + .debug_str + // .debug_info使用的字符串表 +--------------------+ + .symtab + // 靜態鏈接時的符號表,保存了所有關于該目標文件的符號的定義和引用。 +--------------------+ + .strtab + // 默認字符串表。 +--------------------+ + .shstrtab + // 字符串表。 +====================+ +Section header table+ // 用于引用Sections的位置和大小,并且主要用于鏈接和調試目的。對于Exec文件可選 +====================+

ELF知識擴展

關于ELF格式說明的更多信息,點擊查看ELF Specification、Object File Format。

關于Program Header Table的更多信息,點擊查看Program Header、Program Header Table。

關于Section header table的更多信息,點擊查看Section header table。

關于.debug_xxx段的更多信息,點擊查看[DWARF調試格式介紹](http://www.dwarfstd.org/doc/Debugging using DWARF-2012.pdf)。

Linux系統裝載ELF的過程

用戶層面

bash進程會調用fork()系統調用創建一個新的進程,然后在新的進程調用execve()系統調用執行指定的ELF文件。進入execve()系統調用之后,Linux內核就開始進行真正的裝載工作。

系統層面

注:以下分析將使用linux-3.18.6的內核,其他版本大同小異。

在內核中execve()系統調用相應的入口是sys_execve(),它被定義在linux-3.18.6/include/linux/syscalls.h。sys_execve()函數將調用linux-3.18.6/fs/exec.c文件中第1430行的do_execve_common函數進行處理

1427 /* 1428 * sys_execve() executes a new program. 1429 */ 1430 static int do_execve_common(struct filename *filename, 1431 struct user_arg_ptr argv, 1432 struct user_arg_ptr envp) 1433 { ... 1474 file = do_open_exec(filename); // 打開可執行文件 1475 retval = PTR_ERR(file); 1476 if (IS_ERR(file)) 1477 goto out_unmark; 1478 1479 sched_exec(); // 是一個寶貴的平衡機會,因為此時任務具有最小的有效內存和高速緩存占用空間。 1480 1481 bprm->file = file; 1482 bprm->filename = bprm->interp = filename->name; 1483 1484 retval = bprm_mm_init(bprm); // 創建一個新的mm_struct(將賦值給bprm->mm字段),并使用臨時堆棧vm_area_struct填充它。 此時我們沒有足夠的上下文來設置堆棧標志,權限和偏移量,因此我們使用臨時值。稍后將在setup_arg_pages()中對其進行更新。 1485 if (retval) 1486 goto out_unmark; 1487 1488 bprm->argc = count(argv, MAX_ARG_STRINGS); // 參數個數 1489 if ((retval = bprm->argc) < 0) 1490 goto out; 1491 1492 bprm->envc = count(envp, MAX_ARG_STRINGS); // 環境變量 1493 if ((retval = bprm->envc) < 0) 1494 goto out; 1495 1496 retval = prepare_binprm(bprm); // 檢查文件權限,并讀取文件前128個byte確定文件格式和類型 1497 if (retval < 0) 1498 goto out; ... 1513 retval = exec_binprm(bprm); // 執行 1514 if (retval < 0) 1515 goto out; ... 1547 }

do_execve_common中1496行,將調用linux-3.18.6/fs/exec.c文件中prepare_binprm函數,讀取文件首128個字節來判斷文件格式。(注:每種可執行文件格式的開頭幾個字節都是很特殊的,特別是開頭的魔數Magic Number,通過對魔數的判斷可以確定文件的格式和類型。)如下:

1253 /* 1254 * Fill the binprm structure from the inode. 1255 * Check permissions, then read the first 128 (BINPRM_BUF_SIZE) bytes 1256 * 1257 * This may be called multiple times for binary chains (scripts for example). 1258 */ 1259 int prepare_binprm(struct linux_binprm *bprm) 1260 { 1261 struct inode *inode = file_inode(bprm->file); 1262 umode_t mode = inode->i_mode; 1263 int retval; 1264 1265 1266 /* clear any previous set[ug]id data from a previous binary */ 1267 bprm->cred->euid = current_euid(); // 清除之前的信任證 1268 bprm->cred->egid = current_egid(); // 清除之前的信任證 ... 1292 /* fill in binprm security blob */ 1293 retval = security_bprm_set_creds(bprm); // 設置安全信任證 1294 if (retval) 1295 return retval; 1296 bprm->cred_prepared = 1; 1297 1298 memset(bprm->buf, 0, BINPRM_BUF_SIZE); 1299 return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE); // BINPRM_BUF_SIZE定義為128 1300 }

do_execve_common中1513行,調用exec_binprm函數執行文件。exec_binprm函數中1416行調用search_binary_handler函數,來搜索和匹配合適的可執行文件裝載處理程序。

1405 static int exec_binprm(struct linux_binprm *bprm) 1406 { 1407 pid_t old_pid, old_vpid; 1408 int ret; 1409 1410 /* 需要在load_binary更改之前獲取pid */ 1411 old_pid = current->pid; 1412 rcu_read_lock(); 1413 old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent)); 1414 rcu_read_unlock(); 1415 1416 ret = search_binary_handler(bprm); // 搜索和匹配合適的可執行文件裝載處理過程。 1417 if (ret >= 0) { 1418 audit_bprm(bprm); 1419 trace_sched_process_exec(current, old_pid, bprm); 1420 ptrace_event(PTRACE_EVENT_EXEC, old_vpid); 1421 proc_exec_connector(current); 1422 } 1423 1424 return ret; 1425 }

看一下search_binary_handler函數是如何搜索匹配,并執行加載的。search_binary_handler函數。

注意:

  • search_binary_handler函數第1369行中的formats是一個靜態全局變量,formats是struct list_head類型。實際上formats的作用是一個列表的頭,列表中每個struct linux_binfmt元素是經過register_binfmt/insert_binfmt函數注冊/插入進列表的。linux-3.18.6內核版本中注冊的文件加載器有:

  • register_binfmt(&elf_fdpic_format); // 將fdpic二進制文件加載到內存。load an fdpic binary into various bits of memory
  • register_binfmt(&aout_format); // 這些是用于加載a.out樣式的可執行文件和共享庫的函數。 在其他任何地方都沒有二進制相關代碼。These are the functions used to load a.out style executables and shared libraries. There is no binary dependent code anywhere else.
  • register_binfmt(&elf_format); // 加載elf二進制文件。load elf binary
  • register_binfmt(&em86_format); //
  • register_binfmt(&som_format); // 這些是用于加載SOM可執行文件和共享庫的功能。 在其他任何地方都沒有二進制相關代碼。These are the functions used to load SOM executables and shared libraries. There is no binary dependent code anywhere else.
  • **register_binfmt(&script_format); ** // 加載腳本文件。load script file
  • register_binfmt(&flat_format); // 這些是用于加載flat樣式可執行文件和共享庫的函數。 在其他任何地方都沒有二進制相關代碼。These are the functions used to load flat style executables and shared libraries. There is no binary dependent code anywhere else.

代碼如下:

1349 /* 1350 * cycle the list of binary formats handler, until one recognizes the image 1351 */ 1352 int search_binary_handler(struct linux_binprm *bprm) 1353 { 1354 bool need_retry = IS_ENABLED(CONFIG_MODULES); 1355 struct linux_binfmt *fmt; ... 1367 retry: 1368 read_lock(&binfmt_lock); 1369 list_for_each_entry(fmt, &formats, lh) { // 循環便利formats列表,fmt是每個元素的指針 1370 if (!try_module_get(fmt->module)) 1371 continue; 1372 read_unlock(&binfmt_lock); 1373 bprm->recursion_depth++; 1374 retval = fmt->load_binary(bprm); // load_binary是struct linux_binfmt結構體中的一個成員,指定加載函數的指針。 1375 read_lock(&binfmt_lock); ... 1388 } 1389 read_unlock(&binfmt_lock); ... 1400 1401 return retval; 1402 } 1403 EXPORT_SYMBOL(search_binary_handler);

最終search_binary_handler函數將在1374行調用./linux-3.18.6/fs/binfmt_elf.c文件中571行的load_elf_binary函數,load_elf_binary函數將對ELF文件進行裝載。

load_elf_binary函數主要做的事情包括:

  • 檢查ELF可執行文件格式的有效性,比如魔數、程序頭表中段(Segment)的數量。
  • 尋找動態鏈接的.interp段,設置動態鏈接器路徑(與動態鏈接器有關)。
  • 根據ELF可執行文件的程序頭表的描述,對ELF文件進行映射,比如代碼、數據、只讀數據。
  • 初始化ELF進程環境,比如進程啟動時EDX寄存器的地址應該是DT_FINI的地址(參照動態鏈接)。
  • 將系統調用的返回地址修改成ELF可執行文件的入口點,這個入口點取決于程序的鏈接方式,對于靜態鏈接的ELF可執行文件,這個程序入口就是ELF文件頭中e_entry所指的地址;對于動態鏈接的ELF可執行文件,程序入口點是動態鏈接器。
  • 571 static int load_elf_binary(struct linux_binprm *bprm)572 { ...599 /* Get the exec-header */600 loc->elf_ex = *((struct elfhdr *)bprm->buf); // 獲取ELF程序頭部信息601 602 retval = -ENOEXEC;603 /* 一些簡單的一致性檢查 */604 if (memcmp(loc->elf_ex.e_ident, ELFMAG, SELFMAG) != 0)605 goto out;606 607 if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN) // 類型檢查608 goto out;609 if (!elf_check_arch(&loc->elf_ex)) // 指令集架構檢查610 goto out;611 if (!bprm->file->f_op->mmap) // 與文件關聯的mmap操作有效性檢查612 goto out;613 614 /* 讀取所有頭部信息 */615 if (loc->elf_ex.e_phentsize != sizeof(struct elf_phdr))616 goto out;617 if (loc->elf_ex.e_phnum < 1 ||618 loc->elf_ex.e_phnum > 65536U / sizeof(struct elf_phdr))619 goto out;620 size = loc->elf_ex.e_phnum * sizeof(struct elf_phdr);621 retval = -ENOMEM;622 elf_phdata = kmalloc(size, GFP_KERNEL); // elf程序頭表(program header table)數據623 if (!elf_phdata)624 goto out;625 626 retval = kernel_read(bprm->file, loc->elf_ex.e_phoff,627 (char *)elf_phdata, size); // 讀取elf程序頭表數據628 if (retval != size) {629 if (retval >= 0)630 retval = -EIO;631 goto out_free_ph;632 }633 634 elf_ppnt = elf_phdata;635 elf_bss = 0;636 elf_brk = 0;637 638 start_code = ~0UL;639 end_code = 0;640 start_data = 0;641 end_data = 0;642 643 for (i = 0; i < loc->elf_ex.e_phnum; i++) {644 if (elf_ppnt->p_type == PT_INTERP) { // 尋找動態鏈接的.interp段645 /* This is the program interpreter used for646 * shared libraries - for now assume that this647 * is an a.out format binary648 */649 retval = -ENOEXEC;650 if (elf_ppnt->p_filesz > PATH_MAX ||651 elf_ppnt->p_filesz < 2)652 goto out_free_ph;653 654 retval = -ENOMEM;655 elf_interpreter = kmalloc(elf_ppnt->p_filesz,656 GFP_KERNEL);657 if (!elf_interpreter)658 goto out_free_ph;659 660 retval = kernel_read(bprm->file, elf_ppnt->p_offset,661 elf_interpreter,662 elf_ppnt->p_filesz); // 讀取并設置動態鏈接器路徑。663 if (retval != elf_ppnt->p_filesz) {664 if (retval >= 0)665 retval = -EIO;666 goto out_free_interp;667 }668 /* make sure path is NULL terminated */669 retval = -ENOEXEC;670 if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0') // 檢查動態鏈接器路徑671 goto out_free_interp;672 673 interpreter = open_exec(elf_interpreter); // 打開執行動態鏈接器674 retval = PTR_ERR(interpreter);675 if (IS_ERR(interpreter))676 goto out_free_interp;677 678 /*679 * If the binary is not readable then enforce680 * mm->dumpable = 0 regardless of the interpreter's681 * permissions.682 */683 would_dump(bprm, interpreter); // 如果二進制文件不可讀,則不管解釋器的權限如何,都強制執行mm->dumpable = 0。684 685 retval = kernel_read(interpreter, 0, bprm->buf,686 BINPRM_BUF_SIZE);687 if (retval != BINPRM_BUF_SIZE) {688 if (retval >= 0)689 retval = -EIO;690 goto out_free_dentry;691 }692 693 /* 獲取ELF Header */694 loc->interp_elf_ex = *((struct elfhdr *)bprm->buf);695 break;696 }697 elf_ppnt++;698 }699 700 elf_ppnt = elf_phdata;701 for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++)702 if (elf_ppnt->p_type == PT_GNU_STACK) { // 根據ELF文件中的GNU_STACK段的存在性,以及對應的標識位來確定是否要使棧可執行。703 if (elf_ppnt->p_flags & PF_X)704 executable_stack = EXSTACK_ENABLE_X;705 else706 executable_stack = EXSTACK_DISABLE_X;707 break;708 }709 710 /* 解釋器的一些簡單一致性檢查 */711 if (elf_interpreter) {712 retval = -ELIBBAD;713 /* 檢查不是ELFinterpreter */714 if (memcmp(loc->interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0)715 goto out_free_dentry;716 /* 驗證解釋器是否具有有效的ch */717 if (!elf_check_arch(&loc->interp_elf_ex))718 goto out_free_dentry;719 } ...746 /* 現在,我們通過一些繁瑣的工作來將ELF文件映射到內存中的正確位置。747 */748 for(i = 0, elf_ppnt = elf_phdata;749 i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {750 int elf_prot = 0, elf_flags;751 unsigned long k, vaddr;752 753 if (elf_ppnt->p_type != PT_LOAD) // 篩選出程序頭中指定的可加載的段754 continue; ...791 vaddr = elf_ppnt->p_vaddr;792 if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {793 elf_flags |= MAP_FIXED;794 } else if (loc->elf_ex.e_type == ET_DYN) {795 /* Try and get dynamic programs out of the way of the796 * default mmap base, as well as whatever program they797 * might try to exec. This is because the brk will798 * follow the loader, and is not movable. */799 #ifdef CONFIG_ARCH_BINFMT_ELF_RANDOMIZE_PIE // 使內存地址映射隨機化800 /* Memory randomization might have been switched off801 * in runtime via sysctl or explicit setting of802 * personality flags.803 * If that is the case, retain the original non-zero804 * load_bias value in order to establish proper805 * non-randomized mappings.806 */807 if (current->flags & PF_RANDOMIZE)808 load_bias = 0; // 裝入的起點就是映像自己提供的地址vaddr。809 else810 load_bias = ELF_PAGESTART(ELF_ET_DYN_BASE - vaddr);811 #else812 load_bias = ELF_PAGESTART(ELF_ET_DYN_BASE - vaddr);813 #endif814 }815 816 error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,817 elf_prot, elf_flags, 0); // 建立用戶空間虛存區間與目標映像文件中某個連續區間之間的映射。818 if (BAD_ADDR(error)) {819 retval = IS_ERR((void *)error) ?820 PTR_ERR((void*)error) : -EINVAL;821 goto out_free_dentry;822 } ...864 }865 866 loc->elf_ex.e_entry += load_bias;867 elf_bss += load_bias;868 elf_brk += load_bias;869 start_code += load_bias;870 end_code += load_bias;871 start_data += load_bias;872 end_data += load_bias;873 874 /* Calling set_brk effectively mmaps the pages that we need875 * for the bss and break sections. We must do this before876 * mapping in the interpreter, to make sure it doesn't wind877 * up getting placed where the bss needs to go.878 */879 retval = set_brk(elf_bss, elf_brk); // 調用set_brk有效地映射了我們用于bss和break部分的頁面。 我們必須在解釋器中進行映射之前執行此操作,以確保不會將其放置在需要放bss的位置。880 if (retval)881 goto out_free_dentry;882 if (likely(elf_bss != elf_brk) && unlikely(padzero(elf_bss))) {883 retval = -EFAULT; /* Nobody gets to see this, but.. */884 goto out_free_dentry;885 }886 887 if (elf_interpreter) {888 unsigned long interp_map_addr = 0;889 890 elf_entry = load_elf_interp(&loc->interp_elf_ex,891 interpreter,892 &interp_map_addr,893 load_bias); //僅讀取具有ELF標頭的ld.so庫。894 if (!IS_ERR((void *)elf_entry)) {895 /*896 * load_elf_interp() returns relocation897 * adjustment898 */899 interp_load_addr = elf_entry;900 elf_entry += loc->interp_elf_ex.e_entry; // 調整入口地址901 }902 if (BAD_ADDR(elf_entry)) {903 retval = IS_ERR((void *)elf_entry) ?904 (int)elf_entry : -EINVAL;905 goto out_free_dentry;906 }907 reloc_func_desc = interp_load_addr;908 909 allow_write_access(interpreter);910 fput(interpreter);911 kfree(elf_interpreter);912 } else {913 elf_entry = loc->elf_ex.e_entry;914 if (BAD_ADDR(elf_entry)) {915 retval = -EINVAL;916 goto out_free_dentry;917 }918 }919 920 kfree(elf_phdata);921 922 set_binfmt(&elf_format);923 924 #ifdef ARCH_HAS_SETUP_ADDITIONAL_PAGES925 retval = arch_setup_additional_pages(bprm, !!elf_interpreter);926 if (retval < 0)927 goto out;928 #endif /* ARCH_HAS_SETUP_ADDITIONAL_PAGES */929 930 install_exec_creds(bprm); // 安裝新的信任證931 retval = create_elf_tables(bprm, &loc->elf_ex,932 load_addr, interp_load_addr);933 if (retval < 0)934 goto out;935 /* N.B. passed_fileno might not be initialized? */936 current->mm->end_code = end_code;937 current->mm->start_code = start_code;938 current->mm->start_data = start_data;939 current->mm->end_data = end_data;940 current->mm->start_stack = bprm->p;941 942 #ifdef arch_randomize_brk943 if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {944 current->mm->brk = current->mm->start_brk =945 arch_randomize_brk(current->mm);946 #ifdef CONFIG_COMPAT_BRK947 current->brk_randomized = 1;948 #endif949 }950 #endif951 952 if (current->personality & MMAP_PAGE_ZERO) {953 /* Why this, you ask??? Well SVr4 maps page 0 as read-only,954 and some applications "depend" upon this behavior.955 Since we do not have the power to recompile these, we956 emulate the SVr4 behavior. Sigh. */957 error = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC,958 MAP_FIXED | MAP_PRIVATE, 0);959 }960 961 #ifdef ELF_PLAT_INIT // 初始化進程啟動環境,進程啟動時EDX寄存器的地址應該是DT_FINI的地址962 /*963 * The ABI may specify that certain registers be set up in special964 * ways (on i386 %edx is the address of a DT_FINI function, for965 * example. In addition, it may also specify (eg, PowerPC64 ELF)966 * that the e_entry field is the address of the function descriptor967 * for the startup routine, rather than the address of the startup968 * routine itself. This macro performs whatever initialization to969 * the regs structure is required as well as any relocations to the970 * function descriptor entries when executing dynamically links apps.971 */972 ELF_PLAT_INIT(regs, reloc_func_desc);973 #endif974 975 start_thread(regs, elf_entry, bprm->p); // 調用start_thread()函數修改保存在內核態堆棧但屬于用戶態寄存器的EIP和ESP的值,以使它們分別指向DL的入口(如果沒有獲得DL則指向ELF的入口)和新的用戶態棧的棧頂;976 retval = 0;977 out:978 kfree(loc);979 out_ret:980 return retval;981 982 /* error cleanup */983 out_free_dentry:984 allow_write_access(interpreter);985 if (interpreter)986 fput(interpreter);987 out_free_interp:988 kfree(elf_interpreter);989 out_free_ph:990 kfree(elf_phdata);991 goto out;992 }

    當load_elf_binary()執行完畢,返回到do_execve_common函數,再返回到sys_execve()函數時,load_elf_binary()中已經把系統調用的返回地址改成了被裝載的ELF程序的入口地址了。

    所以當sys_execve()系統調用從內核態返回到用戶態時,RIP寄存器直接跳到了ELF程序的入口地址,于是新的程序開始執行,ELF可執行文件加載完成。

    總結

    以上是生活随笔為你收集整理的深入分析ELF文件结构及其载入过程的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。