zoukankan      html  css  js  c++  java
  • 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命令使用方法

  • 相关阅读:
    python psutil监控系统资源【转】
    nc用法【转】
    python enumerate用法总结【转】
    find查找时排除目录及文件
    python自动安装mysql5.7【转】
    Nginx报错:upstream timed out (110: Connection timed out)和client intended to send too large body【转】
    linux压缩日志并删除原始文件
    mysql5.7主从复制--在线变更复制类型【转】
    MySQL 5.7在线设置复制过滤【转】
    Linux dd命令中dsync与fdatasync的区别【转】
  • 原文地址:https://www.cnblogs.com/vendanner/p/4986996.html
Copyright © 2011-2022 走看看