ELF格式探析之三:sections

来源:https://www.cnblogs.com/jiqingwu/archive/2018/10/29/elf_explore_3.html
-Advertisement-
Play Games

本章主要講解了section header的定義,各欄位含義和可能的取值。然後介紹了系統預定義的一些section名稱。最後我們綜合運用第二章和第三章的知識,做了一個讀取section names的練習。 ...


前文鏈接:

今天我們講對目標文件(可重定位文件)和可執行文件都很重要的section。

我們在講ELF Header的時候,講到了section header table。它是一個section header的集合,每個section header是一個描述section的結構體。在同一個ELF文件中,每個section header大小是相同的。(其實看了源碼就知道,32位ELF文件中的section header都是一樣的大小,64位ELF文件中的section header也是一樣的大小)

每個section都有一個section header描述它,但是一個section header可能在文件中沒有對應的section,因為有的section是不占用文件空間的。每個section在文件中是連續的位元組序列。section之間不會有重疊。

一個目標文件中可能有未覆蓋到的空間,比如各種header和section都沒有覆蓋到。這部分位元組的內容是未指定的,也是沒有意義的。

section header定義

section header結構體的定義可以在 /usr/include/elf.h 中找到。

/* Section header.  */

typedef struct
{
  Elf32_Word    sh_name;        /* Section name (string tbl index) */
  Elf32_Word    sh_type;        /* Section type */
  Elf32_Word    sh_flags;       /* Section flags */
  Elf32_Addr    sh_addr;        /* Section virtual addr at execution */
  Elf32_Off sh_offset;      /* Section file offset */
  Elf32_Word    sh_size;        /* Section size in bytes */
  Elf32_Word    sh_link;        /* Link to another section */
  Elf32_Word    sh_info;        /* Additional section information */
  Elf32_Word    sh_addralign;       /* Section alignment */
  Elf32_Word    sh_entsize;     /* Entry size if section holds table */
} Elf32_Shdr;

typedef struct
{
  Elf64_Word    sh_name;        /* Section name (string tbl index) */
  Elf64_Word    sh_type;        /* Section type */
  Elf64_Xword   sh_flags;       /* Section flags */
  Elf64_Addr    sh_addr;        /* Section virtual addr at execution */
  Elf64_Off sh_offset;      /* Section file offset */
  Elf64_Xword   sh_size;        /* Section size in bytes */
  Elf64_Word    sh_link;        /* Link to another section */
  Elf64_Word    sh_info;        /* Additional section information */
  Elf64_Xword   sh_addralign;       /* Section alignment */
  Elf64_Xword   sh_entsize;     /* Entry size if section holds table */
} Elf64_Shdr;

ELF section header結構體

下麵我們依次講解結構體各個欄位:

  1. sh_name,4位元組,是一個索引值,在shstrtable(section header string table,包含section name的字元串表,也是一個section)中的索引。第二講介紹ELF文件頭時,裡面專門有一個欄位e_shstrndx,其含義就是shstrtable對應的section header在section header table中的索引。

  2. sh_type,4位元組,描述了section的類型,常見的取值如下:

    • SHT_NULL 0,表明section header無效,沒有關聯的section。
    • SHT_PROGBITS 1,section包含了程式需要的數據,格式和含義由程式解釋。
    • SHT_SYMTAB 2, 包含了一個符號表。當前,一個ELF文件中只有一個符號表。SHT_SYMTAB提供了用於(link editor)鏈接編輯的符號,當然這些符號也可能用於動態鏈接。這是一個完全的符號表,它包含許多符號。
    • SHT_STRTAB 3,包含一個字元串表。一個對象文件包含多個字元串表,比如.strtab(包含符號的名字)和.shstrtab(包含section的名稱)。
    • SHT_RELA 4,重定位節,包含relocation入口,參見Elf32_Rela。一個文件可能有多個Relocation Section。比如.rela.text,.rela.dyn。
    • SHT_HASH 5,這樣的section包含一個符號hash表,參與動態連接的目標代碼文件必須有一個hash表。目前一個ELF文件中只包含一個hash表。講鏈接的時候再細講。
    • SHT_DYNAMIC 6,包含動態鏈接的信息。目前一個ELF文件只有一個DYNAMIC section。
    • SHT_NOTE 7,note section, 以某種方式標記文件的信息,以後細講。
    • SHT_NOBITS 8,這種section不含位元組,也不占用文件空間,section header中的sh_offset欄位只是概念上的偏移。
    • SHT_REL 9, 重定位節,包含重定位條目。和SHT_RELA基本相同,兩者的區別在後面講重定位的時候再細講。
    • SHT_SHLIB 10,保留,語義未指定,包含這種類型的section的elf文件不符合ABI。
    • SHT_DYNSYM 11, 用於動態連接的符號表,推測是symbol table的子集。
    • SHT_LOPROC 0x70000000 到 SHT_HIPROC 0x7fffffff,為特定於處理器的語義保留。
    • SHT_LOUSER 0x80000000 and SHT_HIUSER 0xffffffff,指定了為應用程式保留的索引的下界和上界,這個範圍內的索引可以被應用程式使用。
  3. sh_flags, 32位占4位元組, 64位占8位元組。包含位標誌,用 readelf -S <elf> 可以看到很多標誌。常用的有:

    • SHF_WRITE 0x1,進程執行的時候,section內的數據可寫。
    • SHF_ALLOC 0x2,進程執行的時候,section需要占據記憶體。
    • SHF_EXECINSTR 0x4,節內包含可以執行的機器指令。
    • SHF_STRINGS 0x20,包含0結尾的字元串。
    • SHF_MASKOS 0x0ff00000,這個mask為OS特定的語義保留8位。
    • SHF_MASKPROC 0xf0000000,這個mask包含的所有位保留(也就是最高位元組的高4位),為處理器相關的語義使用。
  4. sh_addr, 對32位來說是4位元組,64位是8位元組。如果section會出現在進程的記憶體映像中,給出了section第一位元組的虛擬地址。
  5. sh_offset,對於32位來說是4位元組,64位是8位元組。section相對於文件頭的位元組偏移。對於不占文件空間的section(比如SHT_NOBITS),它的sh_offset只是給出了section邏輯上的位置。
  6. sh_size,section占多少位元組,對於SHT_NOBITS類型的section,sh_size沒用,其值可能不為0,但它也不占文件空間。

  7. sh_link,含有一個section header的index,該值的解釋依賴於section type。

    • 如果是SHT_DYNAMICsh_link是string table的section header index,也就是說指向字元串表。
    • 如果是SHT_HASHsh_link指向symbol table的section header index,hash table應用於symbol table。
    • 如果是重定位節SHT_RELSHT_RELAsh_link指向相應符號表的section header index。
    • 如果是SHT_SYMTABSHT_DYNSYMsh_link指向相關聯的符號表,暫時不解。
    • 對於其它的section type,sh_link的值是SHN_UNDEF
  8. sh_info,存放額外的信息,值的解釋依賴於section type。

    • 如果是SHT_RELSHT_RELA類型的重定位節,sh_info是應用relocation的節的節頭索引。
    • 如果是SHT_SYMTABSHT_DYNSYMsh_info是第一個non-local符號在符號表中的索引。推測local symbol在前面,non-local symbols緊跟在後面,所以文檔中也說,sh_info是最後一個本地符號的在符號表中的索引加1。
    • 對於其它類型的section,sh_info是0。
  9. sh_addralign,地址對齊,如果一個section有一個doubleword欄位,系統在載入section時的記憶體地址必須是doubleword對齊。也就是說sh_addr必須是sh_addralign的整數倍。只有2的正整數冪是有效的。0和1說明沒有對齊約束。
  10. sh_entsize,有些section包含固定大小的記錄,比如符號表。這個值給出了每個記錄大小。對於不包含固定大小記錄的section,這個值是0。

系統預定義的section name

系統預定義了一些節名(以.開頭),這些節有其特定的類型和含義。

  • .bss:包含程式運行時未初始化的數據(全局變數和靜態變數)。當程式運行時,這些數據初始化為0。 其類型為SHT_NOBITS,表示不占文件空間。SHF_ALLOC + SHF_WRITE,運行時要占用記憶體的。
  • .comment 包含版本控制信息(是否包含程式的註釋信息?不包含,註釋在預處理時已經被刪除了)。類型為SHT_PROGBITS
  • .data和.data1,包含初始化的全局變數和靜態變數。 類型為SHT_PROGBITS,標誌為SHF_ALLOC + SHF_WRITE(占用記憶體,可寫)。
  • .debug,包含了符號調試用的信息,我們要想用gdb等工具調試程式,需要該類型信息,類型為SHT_PROGBITS
  • .dynamic,類型SHT_DYNAMIC,包含了動態鏈接的信息。標誌SHF_ALLOC,是否包含SHF_WRITE和處理器有關。
  • .dynstr,SHT_STRTAB,包含了動態鏈接用的字元串,通常是和符號表中的符號關聯的字元串。標誌 SHF_ALLOC
  • .dynsym,類型SHT_DYNSYM,包含動態鏈接符號表, 標誌SHF_ALLOC
  • .fini,類型SHT_PROGBITS,程式正常結束時,要執行該section中的指令。標誌SHF_ALLOC + SHF_EXECINSTR(占用記憶體可執行)。現在ELF還包含.fini_array section。
  • .got,類型SHT_PROGBITS,全局偏移表(global offset table),以後會重點講。
  • .hash,類型SHT_HASH,包含符號hash表,以後細講。標誌SHF_ALLOC
  • .init,SHT_PROGBITS,程式運行時,先執行該節中的代碼。SHF_ALLOC + SHF_EXECINSTR,和.fini對應。現在ELF還包含.init_array section。
  • .interp,SHT_PROGBITS,該節內容是一個字元串,指定了程式解釋器的路徑名。如果文件中有一個可載入的segment包含該節,屬性就包含SHF_ALLOC,否則不包含。
  • .line,SHT_PROGBITS,包含符號調試的行號信息,描述了源程式和機器代碼的對應關係。gdb等調試器需要此信息。
  • .note Note Section, 類型SHT_NOTE,以後單獨講。
  • .plt 過程鏈接表(Procedure Linkage Table),類型SHT_PROGBITS,以後重點講。
  • .relNAME,類型SHT_REL, 包含重定位信息。如果文件有一個可載入的segment包含該section,section屬性將包含SHF_ALLOC,否則不包含。NAME,是應用重定位的節的名字,比如.text的重定位信息存儲在.rel.text中。
  • .relaname 類型SHT_RELA,和.rel相同。SHT_RELASHT_REL的區別,會在講重定位的時候說明。
  • .rodata和.rodata1。類型SHT_PROGBITS, 包含只讀數據,組成不可寫的段。標誌SHF_ALLOC
  • .shstrtab,類型SHT_STRTAB,包含section的名字。有讀者可能會問:section header中不是已經包含名字了嗎,為什麼把名字集中存放在這裡? sh_name 包含的是.shstrtab 中的索引,真正的字元串存儲在.shstrtab中。那麼section names為什麼要集中存儲?我想是這樣:如果有相同的字元串,就可以共用一塊存儲空間。如果字元串存在包含關係,也可以共用一塊存儲空間。
  • .strtab SHT_STRTAB,包含字元串,通常是符號表中符號對應的變數名字。如果文件有一個可載入的segment包含該section,屬性將包含SHF_ALLOC。字元串以\0結束, section以\0開始,也以\0結束。一個.strtab可以是空的,它的sh_size將是0。針對空字元串表的非0索引是允許的。
  • symtab,類型SHT_SYMTAB,Symbol Table,符號表。包含了定位、重定位符號定義和引用時需要的信息。符號表是一個數組,Index 0 第一個入口,它的含義是undefined symbol index, STN_UNDEF。如果文件有一個可載入的segment包含該section,屬性將包含SHF_ALLOC

練習:讀取section names

從這一講開始,都會有練習,方便我們把前面的理論知識綜合運用。

下麵這個練習的目標是:從一個ELF文件中讀取存儲section name的字元串表。前面講過,該字元串表也是一個section,section header table中有其對應的section header,並且ELF文件頭中給出了節名字元串表對應的section header的索引,e_shstrndx

我們的思路是這樣:

  1. 從ELF header中讀取section header table的起始位置,每個section header的大小,以及節名字元串表對應section header的索引。
  2. 計算section_header_table_offset + section_header_size * e_shstrndx 就是節名字元串表對應section header的偏移。
  3. 讀取section header,可以從中得到節名字元串表在文件中的偏移和大小。
  4. 把節名字元串表讀取到記憶體中,列印其內容。

代碼如下:

/* 64位ELF文件讀取section name string table */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(int argc, char *argv[])
{
    /* 打開本地的ELF可執行文件hello */
    FILE *fp = fopen("./hello", "rb");
    if(!fp) {
        perror("open ELF file");
        exit(1);
    }

    /* 1. 通過讀取ELF header得到section header table的偏移 */
    /* for 64 bit ELF,
       e_ident(16) + e_type(2) + e_machine(2) +
       e_version(4) + e_entry(8) + e_phoff(8) = 40 */
    fseek(fp, 40, SEEK_SET);
    uint64_t sh_off;
    int r = fread(&sh_off, 1, 8, fp);
    if (r != 8) {
        perror("read section header offset");
        exit(2);
    }
    /* 得到的這個偏移值,可以用`reaelf -h hello`來驗證是否正確 */
    printf("section header offset in file: %ld (0x%lx)\n", sh_off, sh_off);

    /* 2. 讀取每個section header的大小e_shentsize,
       section header的數量e_shnum,
       以及對應section name字元串表的section header的索引e_shstrndx
       得到這些值後,都可以用`readelf -h hello`來驗證是否正確 */
    /* e_flags(4) + e_ehsize(2) + e_phentsize(2) + e_phnum(2) = 10 */
    fseek(fp, 10, SEEK_CUR);
    uint16_t sh_ent_size;            /* 每個section header的大小 */
    r = fread(&sh_ent_size, 1, 2, fp);
    if (r != 2) {
        perror("read section header entry size");
        exit(2);
    }
    printf("section header entry size: %d\n", sh_ent_size);

    uint16_t sh_num;            /* section header的數量 */
    r = fread(&sh_num, 1, 2, fp);
    if (r != 2) {
        perror("read section header number");
        exit(2);
    }
    printf("section header number: %d\n", sh_num);

    uint16_t sh_strtab_index;   /* 節名字元串表對應的節頭的索引 */
    r = fread(&sh_strtab_index, 1, 2, fp);
    if (r != 2) {
        perror("read section header string table index");
        exit(2);
    }
    printf("section header string table index: %d\n", sh_strtab_index);

    /* 3. read section name string table offset, size */
    /* 先找到節頭字元串表對應的section header的偏移位置 */
    fseek(fp, sh_off + sh_strtab_index * sh_ent_size, SEEK_SET);
    /* 再從section header中找到節頭字元串表的偏移 */
    /* sh_name(4) + sh_type(4) + sh_flags(8) + sh_addr(8) = 24 */
    fseek(fp, 24, SEEK_CUR);
    uint64_t str_table_off;
    r = fread(&str_table_off, 1, 8, fp);
    if (r != 8) {
        perror("read section name string table offset");
        exit(2);
    }
    printf("section name string table offset: %ld\n", str_table_off);

    /* 從section header中找到節頭字元串表的大小 */
    uint64_t str_table_size;
    r = fread(&str_table_size, 1, 8, fp);
    if (r != 8) {
        perror("read section name string table size");
        exit(2);
    }
    printf("section name string table size: %ld\n", str_table_size);

    /* 動態分配記憶體,把節頭字元串表讀到記憶體中 */
    char *buf = (char *)malloc(str_table_size);
    if(!buf) {
        perror("allocate memory for section name string table");
        exit(3);
    }
    fseek(fp, str_table_off, SEEK_SET);
    r = fread(buf, 1, str_table_size, fp);
    if(r != str_table_size) {
        perror("read section name string table");
        free(buf);
        exit(2);
    }
    uint16_t i;
    for(i = 0; i < str_table_size; ++i) {
        /* 如果節頭字元串表中的位元組是0,就列印`\0` */
        if (buf[i] == 0)
            printf("\\0");
        else
            printf("%c", buf[i]);
    }
    printf("\n");
    free(buf);
    fclose(fp);
    return 0;
}

把以上代碼存為chap3_read_section_names.c,執行gcc -Wall -o secnames chap3_read_section_names.c進行編譯,輸出的執行文件名叫secnames。執行secnames,輸出如下:

./secnames
section header offset in file: 14768 (0x39b0)
section header entry size: 64
section header number: 29
section header string table index: 28
section name string table offset: 14502
section name string table size: 259
\0.symtab\0.strtab\0.shstrtab\0.interp\0.note.ABI-tag\0.note.gnu.build-id\0.gnu.hash\0.dynsym\0.dynstr\0.gnu.version\0.gnu.version_r\0.rela.dyn\0.rela.plt\0.init\0.text\0.fini\0.rodata\0.eh_frame_hdr\0.eh_frame\0.init_array\0.fini_array\0.dynamic\0.got\0.got.plt\0.data\0.bss\0.comment\0

可以發現,節頭字元串表以\0開始,以\0結束。如果一個section的name欄位指向0,則他指向的位元組值是0,則它沒有名稱,或名稱是空。

總結

本章主要講解了section header的定義,各欄位含義和可能的取值。然後介紹了系統預定義的一些section名稱。最後我們綜合運用第二章和第三章的知識,做了一個讀取section names的練習。

下一章我們將講述符號表和重定位的原理。此系列文章也會在微信公眾號“歡欣之翼”上同步更新,歡迎關註。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 主調度器 在內核中的許多地方, 如果要將CPU分配給與當前活動進程不同的另一個進程, 都會直接調用主調度器函數schedule, 從系統調用返回後, 內核也會檢查當前進程是否設置了重調度標誌 例如, 前述的周期性調度器的scheduler_tick就會設置該標誌, 如果是這樣則內核會調用schedu ...
  • 常用指令 ls 顯示文件或目錄 -l 列出文件詳細信息l(list) -a 列出當前目錄下所有文件及目錄,包括隱藏的a(all) mkdir 創建目錄 -p 創建目錄,若無父目錄,則創建p(parent) cd 切換目錄 touch 創建空文件 echo 創建帶有內容的文件。 cat 查看文件內容 ...
  • 1. 第一章 a. 符號約定 a1. 位元組順序 a2. 保留的比特位和軟體相容性 a3. 指令操作數 a4. 十六進位和二進位數 a5. 分段地址 a. 符號約定 a1. 位元組順序 intel的32和64架構均使用小端地址模式。小端:高位元組在高地址,大端:高位元組在低地址。例如: 31 24 23 1 ...
  • 搭建好Git伺服器後,在伺服器創建了一個空項目,我在本地使用git clone 拉取項目時,報了fatal: protocol error: bad line length character: This的錯誤。這個問題是出在Git伺服器端,不是Git客戶端的問題,所以Git客戶端不需要修改任何東西... ...
  • 假設遠程伺服器IP地址為 192.168.1.100 1.從伺服器複製文件到本地: scp [email protected]:/data/test.txt /home/myfile/ [email protected] root是目標伺服器(有你需要拷貝文件的伺服器)的用戶名,192.168. ...
  • lvs: Linux Virtual Server l4:四層交換;四層路由; 根據請求報文的目標IP和PORT將其轉發至後端主機集群中的某一臺主機(根據挑選演算法); netfilter: PREROUTING --> INPUT PREROUTING --> FORWARD --> POSTROU ...
  • 概念 簡稱brew,是Mac OSX上的軟體包管理工具,能在Mac中方便的安裝軟體或者卸載軟體,相當於Red hat的yum、Ubuntu的apt get。 安裝命令 ruby e "$(curl fsSL "https://raw.githubusercontent.com/Homebrew/in ...
  • #本文並非原創,屬於本人學習中的記錄筆記或是轉存筆記,如果涉及到哪位高人的創作權益,敬請海涵! Vim 是一個上古神器,本篇文章主要持續總結使用 Vim 的過程中不得不瞭解的一些指令和註意事項,以及持續分享一個前端工作者不得不安裝的一些插件,而關於 Vim 的簡介,主題的選擇,以及為何使用 vim- ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...