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命令使用方法

posted @ 2015-11-22 22:43  vendanner  阅读(2516)  评论(0编辑  收藏  举报