elf文件格式


   android是建立在linux的基礎上,其底層代碼是安裝linux可執行文件——elf的格式來組裝的。本文結合android中的so文件來了解elf格式,資料大多收集於網上;elf格式位於android源碼:elf.h(下面涉及到的結構體和宏定義都可以在此頭文件中找到)。

  elf大致可分為三部分:elf頭、程序頭表、節區頭表;當然還有上圖沒標出的動態符號表,

                                       

 elf頭:

#define EI_NIDENT       16
 typedef struct {
      unsigned char       e_ident[EI_NIDENT];    //magic    
      Elf32_Half          e_type;          //type 1:重定位文件;2:可執行文件;3:共享文件
      Elf32_Half          e_machine;        //cpu結構
      Elf32_Word          e_version;        //版本
      Elf32_Addr          e_entry;          //程序進入點 可執行:main;so:無用
      Elf32_Off           e_phoff;        //程序頭表偏移
      Elf32_Off           e_shoff;        //節區表偏移
      Elf32_Word          e_flags;        //文件和處理器相關的標志
      Elf32_Half          e_ehsize;        //elf頭大小
      Elf32_Half          e_phentsize;      //程序頭占用空間即大小
      Elf32_Half          e_phnum;        //程序頭項目數
      Elf32_Half          e_shentsize;    //節區頭占用空間即大小
      Elf32_Half          e_shnum;        //節區數
      Elf32_Half          e_shstrndx;      //字符串表,在節區中索引
  } Elf32_Ehdr;

 

  我們會發現 e_ehsize = e_phoff(為什么?看第一幅圖)。在elf頭中我們很容易發現其實主要分三部分:info相關,程序頭相關,節區頭相關;剛好對應着鏈接器和裝載器所需內容。e_phoff、e_phentsize、e_phnum裝載器必須;e_shoff、e_shentsize、e_shnum、e_shstrndx是鏈接器必須;即section是供給linker使用,而segment是供給loader使用。注意下e_shstrndx是字符串表頭在section頭中的索引,例如e_shstrndx=2,則section頭中的第3個舊市字符串表頭。

phdr頭:

typedef struct elf32_phdr{
Elf32_Word p_type;         //segment類型
Elf32_Off p_offset;        //該segment在文件的偏移地址
Elf32_Addr p_vaddr;        //segment映射到內存中的地址
Elf32_Addr p_paddr;        //segment物理地址,現代操作系統基本無法觸及,基本無效
Elf32_Word p_filesz;      //segment在文件中所占大小,有些segment在文件中不存在卻占據一定的內存大小,則為0
Elf32_Word p_memsz;        //segment在內存中所占的地址空間大小
Elf32_Word p_flags;        //segment可操作的讀寫權限
Elf32_Word p_align;        //按幾個字節對齊
} Elf32_Phdr;

 

  重點是p_offset和p_filesz,它們是segment的起始地址和大小;p_type為segment類型詳見elf.h中的PT開頭的宏定義;p_flags為segment的可操作權限詳見elf.h中的PF開頭宏定義(跟linux的文件權限rwx是一樣的)。

 shdr頭:

typedef struct {
Elf32_Word sh_name;        //section name:.data、.dynamic、.got、.init......
Elf32_Word sh_type;        //section類型
Elf32_Word sh_flags;    //section權限
Elf32_Addr sh_addr;        //section映射到內存中起始地址
Elf32_Off sh_offset;    //該section在文件中的偏移
Elf32_Word sh_size;        //section大小
Elf32_Word sh_link;        //一般來說是該section所用的string table在section header table中的索引,見參考資料3
Elf32_Word sh_info;        //
Elf32_Word sh_addralign;//section按幾字節對齊
Elf32_Word sh_entsize;    //section內容中表項所占大小,例如.dynamic為8下面解釋
} Elf32_Shdr;  

 

動態符號表(dynamic_symbol_table):

  

    介紹完elf格式的整體框架后,來深入了解內在的聯系和一些section。

   .shstrtab:字符串表保存着一系列以NULL結尾的的字符串

   .dynstr:該section包含了用於動態鏈接的字符串,通常是符號表項名稱字符串;

 .dynamic:該section包含了動態鏈接信息,該section屬性將包含SHF_ALLOC比特位,而SHF_WRITE比特位是否為1取決於處理器(通常.dynamic會獨占一個segment叫dynamic);簡單來說它包含着一連串的dynamic結構

typedef struct dynamic{
Elf32_Sword d_tag;
union{
    Elf32_Sword d_val;
    Elf32_Addr d_ptr;
    } d_un;
} Elf32_Dyn;

  d_tag控制d_un是d_val還是d_ptr;可通過d_tag來識別是屬於哪個section(elf.h中DT開頭的宏定義),d_un為d_tag在文件中的偏移量(不完全正確!,以后再補充)。例如d_tag為6則是DT_SYMTAB為.dynsym,則d_un為.dynsym為偏移量。值得一提的是在該section中,sh_addralign為4,sh_entsize為8(為什么看dynamic結構體)。這個節區非常重要,android linker就是通過它解析出其他section,看源碼:http://androidxref.com/4.4_r1/xref/bionic/linker/linker.cpp#1339。注意d_tag = NEEDED表示為共享庫,而其共享庫的name是strtab[dun]為首字母的字符串。

 

 .hash:包含了符號hash表,hash表內容的組合形式如下:

Symbol Hash Table 

nbucket 
nchain 
bucket[0] 
... 
bucket[nbucket - 1]   /
chain[0] 
... 
chain[nchain - 1] 

   由此可以看出hash表的長度為(nbucket+nchain+2)*4。假設函數hash值為funHash,在.hash中得到值funIndex=bucket[funHash]或chain[funHash]。

 

     .dynsym:該section包含了動態鏈接符號表;其實該section是elf32_sym結構體數組

typedef struct elf32_sym{
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;      //不是很理解,看源碼http://androidxref.com/4.4_r1/xref/bionic/libc/include/sys/exec_elf.h  STT_FUNC
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;

  sh_name值是以.dynstr為基址的索引,st_value為fun指令偏移地址,st_size為fun指令長度。funInfo = sym[funIndex],可得到fun的信息。st_shndx為STN_UNDEF表示該函數為外部引用需要被重定位,即st_value為0。這里提下st_info這個字符,它由類型和綁定屬性組成,可以源碼

 

  .rel:重定位表

typedef struct elf32_rel {
Elf32_Addr r_offset;            //
Elf32_Word r_info;        //
} Elf32_Rel;    

   r_offset為需要重定位的內容地址,而r_info分為2部分:elf.h中定義了宏定義,ELF32_R_SYM是在dynsym的索引,ELF32_R_TYPE是重定位的類型:

 #define R_ARM_ABS32       2   //外部函數局部指針函數調用方式 位於.rl.dyn    
/*
20-31 are reserved for ARM Linux. */ 位於源碼/bionic/libc/arch-arm/include/machine/elf_machdep.h #define R_ARM_COPY 20 #define R_ARM_GLOB_DAT 21  //外部函數全局指針函數調用方式 位於.rl.dyn #define R_ARM_JUMP_SLOT   22 //外部函數直接調用方式 位於.rl.plt #define R_ARM_RELATIVE 23 #define R_ARM_GOTOFF  24 #define R_ARM_GOTPC 25 #define R_ARM_GOT32 26 #define R_ARM_PLT32 27

     r_offset是該函數的指令或者數據的值在內存中的指針,比如r_offset = addr1,在地址addr1中存放addr2,則addr2為函數指令地址

                                     

 

Tips:

  1 字符串符號表.shstrtab后跟着section_header_table;節區表頭分布在elf文件最后,而字符串符號表往往是在最靠后的內容

  2 section name需要在shstr table找;而segment 沒有name只有type,只需比較就能確定類型

  3 根據函數名找到函數指令:函數名hash值funHash,在hash表得到索引值funIndex,在dynsym表索引得到funInfo,funIndo.st_name為.dynstr的索引(這可以判斷是否為我們要找到的函數),funInfo.st_value為函數指令偏移地址

  4 .dynsym的項數=.hash中nchain

 

資料:

 1 【原創】簡單粗暴的so加解密實現

   2 【原創】手工打造ELF文件

   3 ELF文件格式解析 (嵌入式很多用這個格式)

   4 ELF格式文件符號表全解析及readelf命令使用方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM