美文网首页
ELF文件详解

ELF文件详解

作者: Alanone0x0 | 来源:发表于2020-06-18 15:29 被阅读0次

    ELF文件详解

    ELF文件分为四个部分:elf header,program header table,section header table,dynamic symbol table

    其中节头表(section header table) 和 段头表(program header table) 中用到的数据相同,只是组织方式不同

    一、ELF header

    每个ELF文件都必须存在一个ELF_Header,这里存放了很多重要的信息用来描述整个文件的组织,如: 版本信息,入口信息,偏移信息等,程序执行也必须依靠其提供的信息
    image-20200617152243112
    Magic  前4字节: \x7F、'E'、'L'、'F'  文件标识
           第五字节:文件类别,0(无效类型)、1(32位)、2(64位)
           第六字节:数据编码,0(无效编码)、1(小端)、2(大端)
           第七字节:文件版本,1(当前版本)
           第八字节:补齐字节开始处
           之后魔数:留用
    Class  类别
    Data   2 补码,小端序(little endian)
    Version  版本
    OS/ABI   系统
    Type     EXEC(Executable file 可执行文件)
    Machine  ARM平台
    Version  版本
    Entry point address                入口点地址
    Start of program headers           程序头开始地址
    Start of section headers           节区头开始地址
    Flags                              标识
    Size of this header                本头大小
    Size of program headers            程序头大小
    Number of program headers          程序头数量
    Size of section headers            节头大小
    Number of section headers          节头数量
    Section header string table index  节头字符串表索引
    

    数据结构如下

    #define EI_NIDENT   16
    typedef struct {
        unsigned char   e_ident[EI_NIDENT];    ##magic魔数
        ELF32_Half      e_type;                ##类别
        ELF32_Half      e_machine;             ##系统架构:3(Intel 80386)
        ELF32_Word      e_version;             ##版本:2(可执行文件)
        ELF32_Addr      e_entry;               ##入口点地址
        ELF32_Off       e_phoff;               ##程序头部表
        ELF32_Off       e_shoff;               ##节区头部表
        ELF32_Word      e_flags;               ##标志
        ELF32_Half      e_ehsize;              ##本头的大小
        ELF32_Half      e_phentsize;           ##程序头大小
        ELF32_Half      e_phnum;               ##程序头数量
        ELF32_Half      e_shentsize;           ##节头大小
        ELF32_Half      e_shnum;               ##节头数量
        ELF32_Half      e_shstrndx;            ##字符串表索引节头
    } Elf32_Ehdr;
    
    在系统中是以e_xxx存在的,但是在elfread中,为了看着方便,给我们另一种展示,如下图
    

    e_xxx 和上面对应表如下图:

    image-20200617231624311 image-20200617231649152

    其中数据类型如下

    名称 长度 对齐方式 用途
    Elf32_Addr 4 4 无符号程序地址
    Elf32_Half 2 2 无符号半整型
    Elf32_Off 4 4 无符号文件偏移
    Elf32_Sword 4 4 有符号大整型
    Elf32_Word 4 4 无符号大整型
    unsigned char 1 1 无符号小整型

    二、Program header table 程序头表

    存储so文件运行时所需要的信息,这部分信息会直接被linker使用,用于加载so文件,告诉系统如何在内存中创建映像,在图中也可以看出来,有程序头部表才有段,有段就必须有程序头部表,其中存放各个段的基本信息(包括地址指针)
    image-20200617155215988
    上图中的程序头表列出了8个段,这些组成了最终在内存的执行程序
    PHDR    保存程序头表 
            offset 偏移值 
            VirtAddr 虚拟内存中地址,加载后会被重定向,readelf的时候显示的是默认值,如上图的0x00008034
            PhysAddr 和上面类似,基本用不到
            FileSiz  文件大小
            MemSiz   内存中大小
            Flg      R读 W写 X执行
            Align    对齐 0x4是4字节对齐
    INTERP  程序已经从可执行映射到内存后,必须调用解释器,在这里解释器并不意味着二进制文件的内存必须由另一个程序解释,它指的是这样的一个程序:通过链接其他库,来满足未解决的引用,在ELF文件中保存的实际为/system/bin/linker
    LOAD    表示一个从二进制文件映射到虚拟地址空间的段,其中保存了常量数据(如字符串),程序的目标代码等
    DYNAMIC 保存了其他动态链接器(INTERP中指定的解释器)使用的信息
    GNU_STACK  未定义的,unknow
    EXIDX    未定义的,unknow
    GNU_RELRO    未定义的,unknow
    NOTE    保存了专有信息
    

    节到段的映射

    image-20200617223713107
    链接视图是以节(section)为单位,执行视图是以段(segment)为单位。链接视图就是在链接时用到的视图,而执行视图则是在执行时用到的视图。上图左侧的视角是从链接来看的,右侧的视角是执行来看的
    段(Segment): 就是将文件分成一段一段映射到内存中,段中通常包括一个或多个节区
    
    那么为什么需要节和段两种视图?    当ELF文件被加载到内存中后,系统会将多个具有相同权限(flg值)section合并一个segment。操作系统往往以页为基本单位来管理内存分配,一般页的大小为4096B,即4KB的大小。同时,内存的权限管理的粒度也是以页为单位,页内的内存是具有同样的权限等属性,并且操作系统对内存的管理往往追求高效和高利用率这样的目标。ELF文件在被映射时,是以系统的页长度为单位的,那么每个section在映射时的长度都是系统页长度的整数倍,如果section的长度不是其整数倍,则导致多余部分也将占用一个页。而我们从上面的例子中知道,一个ELF文件具有很多的section,那么会导致内存浪费严重。这样可以减少页面内部的碎片,节省了空间,显著提高内存利用率
    
    image-20200617223826822
    readelf -S xxx    # 用来查看可执行文件中有哪些section,如下图
    
    image-20200617224647502
    readelf --segments xxx    # 可以查看该文件的执行视图,下图红框部分为上图的节信息在段中的显示
    
    image-20200617224748561
    最后加载进内存的只有program header table 程序头表里的load段,其他都只是描述信息,加载过程中用到,但是最后加载进去内存的只有load段
    
    image-20200617235850414

    三、Section header table 节头部表

    类似与程序头部表,但与其相对应的是节区(Section)
    节区(Section): 将文件分成一个个节区,每个节区都有其对应的功能,如符号表,哈希表等
    image-20200617224647502
    .interp 程序解释器的绝对路径,类似于linker的绝对路径
    .dynamic 指的是包含当前so文件中的依赖信息
    .dynstr 指的是包含了自己写的函数的函数名,和导入函数的函数名
    .hash 表示导入导出函数的函数名称的hash表
    .relname和.relaname: 解释在下面图中
    .plt 程序链接表,具体解释在下面图中
    .text  已编译程序的机器代码
    .note 指明编译器信息
    .ARM.extab 标识设备信息
    .fini 类似于Android里面的onDestory,用来进入销毁阶段,进行释放资源等一系列操作
    .init_array 初始化函数的地址
    .preinit_arry  执行时先执行init_array,再执行的这个
    .dynsym 是上面.dynstr的子集,表示导入的函数的函数名
    .got 导入函数的地址和一些全局变量的地址,实际是放在data数据段的
    .data  数据区
    .bss  保存未初始化数据,当程序运行时,系统用0初始化该区域,该section不占用系统空间
    .comment  版本控制信息
    .note.gnu.gold-ve  当前处理器信息
    .ARM.attributes  标识设备信息
    .shstrtab  存在所有节名称的字符串表
    .bug  指的是ELF生成中间文件时候产生的debug信息
    .line  类似于smali代码中的.line,smali表示该行代码在java中的位置,这里就表示该行代码在.c文件中的位置
    
    .relname和.relaname: 010Editor打开so,展现形式为下图,.rel.dyn 和 .rel.plt ,是用来重定向dyn和plt的,也就是静态情况下,存放偏移值,如果进行动态调试的时候,就会加上基址变成绝对地址(重定向)
    下面第二张图中,左边红框就是偏移值,右边红框只要把基址加进来,就是绝对地址,把基址加进来的过程就是重定向的过程
    
    image-20200617233702555 image-20200617234205166
    .plt  程序链接表,用于做映射关系,拿到依赖so的绝对地址,做重定向的
    
    image-20200617233823702

    四、Dynamic symbol table

    这里是符号表,也就是会用到的所有函数名称表,包括自己写的函数和依赖的系统so中的函数,到时候.plt会对这部分重定向

    相关文章

      网友评论

          本文标题:ELF文件详解

          本文链接:https://www.haomeiwen.com/subject/rruuxktx.html