zoukankan      html  css  js  c++  java
  • 解析目标文件

    解析目标文件

     

    最近在看《程序员的自我修养》,颇有体会,故化繁为简,整理书中部分内容,作为学习笔记。

    1. PC平台上流行的可执行文件格式主要是windows下的PE(Portable Executable)和Linux下的ELF(Executable Linkable Format),他们都是COFF(common file format)格式的变种。
    2. 可执行文件(windows下.exe和Linux下的ELF可执行文件)、动态链接库(DLL,Dynamic Linking Library)(windows下的.dll和Linux下的.so)、静态链接库(Static Linking Library)(windows下的.lib和Linux下的.a)文件都是按照可执行文件格式存储。
    3. 目标文件中的内容至少有编译后的机器指令代码、数据,还有链接时需要的一些信息,如符号表、调试信息、字符串等。以“段”的形式存储。
    4. 代码段(.text或.code):程序源代码编译后的机器指令;
    5. 数据段(.data):放置全局变量和局部静态变量;
    6. .bss段:放置未初始化的全局变量和局部静态变量;
    7. 程序指令和数据分开存放的好处:
    • 程序被装载后,数据和指令分别被映射到两个虚存区域。数据区域对于进程来说可读写,指令区域对于进程来说是只读的,所以两个虚存区域的权限可以被分别设置成可读写和只读,这样可以防止程序的指令被有意或者无意的修改;
    • 程序的指令和数据分开存对CPU的缓存命中率提高有好处;
    • 当系统中运行多个改程序的副本时,他们的指令都是一样,因此在内存中只须保存一份该程序的指令部分。当然每个副本进程的数据区域是不一样的,他们是进程私有的。

    挖掘目标文件SimpleSection.o

    1.    程序代码清单

    只编译不链接此文件:

    $ gcc –c SimpleSection.c

    利用binutils的工具objdump查看object内容的结构:

    $ objdump –h SimpleSection.o

    参数-h就是把ELF文件的各个段的基本信息打印出来。结果如下:

    除了最基本的代码段、数据段、BSS段之外,SimpleSection.o还有只读数据段(.rodata)、注释信息段(.comment)、堆栈提示段(.note.GNU-stack)、eh_frame段。

    从上图可以理解,段的长度(Size)和段所在的位置(File Offset),“CONTENTS”表示该段在文件中存在,“ALLOC”表示实际上ELF文件中不存在的内容。各段在ELF中的结构如下图所示。

      

    $size SimpleSection.o

    用于查看ELF文件的代码段、数据段和BSS段的长度。dec表示三段长度和的十进制,hex表示长度和的十六进制。

    2.  代码段

    objdump的“-s”参数可以将所有段的内容以十六进制的方式打印出来,“-d”参数可以将所有包含指令的段反汇编。

    $ objdump –s –d SimpleSection.o

    最左面一列是偏移量,中间4列是十六进制内容,最右面的一列是.text段的ASCII码。

    3.  数据段和只读数据段

    .data段保存的是那些已经初始化了的全局静态变量和局部静态变量。

    .rodata段存放的是只读数据,一般是程序里面的只读变量,如const修饰的变量和字符串常量。

    $objdump –x –s –d SimpleSection.o

    可以看出.data段里的前四个字节,从低到高分别是0x54、0x00、0x00、0x00。这个值刚好是global_init_varable,即十进制84。

    4.  BSS

    .bss段存放的是未初始化的全局变量和局部静态变量。如代码中的global_uninit_var和static_var2就是存放在.bss段,更准确的说法是.bss段为它们预留了空间。有些编译器会将全局的未初始化变量存放在目标文件的.bss段,有些则不放,只是预留一个未定义的全局变量符号,等到最终链接成可执行文件的时候再在.bss段分配空间。

    $objdump –x –s –d SimpleSection.o

    5.  其他段

    ELF文件结构

    ELF目标文件格式的最前端是ELF文件头(ELF Header,包含了描述整个文件的基本属性,如ELF版本、目标机器型号、程序入口地址等。

    ELF文件中与段有关的重要结构就是段表(Section Header Table),该表描述了所有段的信息,如每个段的段名、段的长度、在文件中的偏移、读写权限和段的其他属性。

    ELF中的其他辅助结构,如字符串表、符号表等。

    1.         ELF文件头

    $readelf –h SimpleSection.o

    从上图可以看出,ELF文件头中定义了ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台、ABI版本、ELF重定位类型、硬件平台、硬件平台版本、入口地址、程序头入口和长度、段表的入口和长度、段表的位置和长度、段的数量等。

    ELF文件头结构及相关常熟被定义在“/usr/include/elf.h”里,因为ELF文件在各种平台下都通用,ELF文件有32位版本和64版本。分为为 “Elf32_Ehdr”和 “Elf64_Ehdr”。

    “elf.h”使用typedef定义了一套自己的变量体系,如下图。

    以32位版本的文件头结构“Elf32_Ehdr”为例,其定义如下:

    复制代码
      1 typedef struct{
      2     unsigned char e_ident[16];
      3     Elf32_Half e_type;
      4     Elf32_Half e_machine;
      5     Elf32_Word e_version;
      6     Elf32_Addr e_entry;
      7     Elf32_Off e_phoff;
      8     Elf32_Off e_shoff;
      9     Elf32_Word e_flags;
     10     Elf32_Half e_ehsize;
     11     Elf32_Half e_phentsize;
     12     Elf32_Half e_phnum;
     13     Elf32_Half e_shentsize;
     14     Elf32_Half e_shnum;
     15     Elf32_Half e_shstrndx;
     16 }Elf32_Ehdr;
     17     
    复制代码

    各个成员的含义如下:

    • ELF魔数

    最开始的4个字节是所有ELF文件都必须相同的标识码,分别为0x7F、0x45、0x4c、0x46,第一个字节对应的ASCII字符里的DEL控制符,后面的3个字符刚好是ELF这三个字符的ASCII码。这4个字节被称为ELF文件的魔数,几乎所有的可执行文件格式的最开始几个字节都是魔数。

    • 文件类型

    即前面提到过的3种ELF文件类型,每个文件类型对应一个常量。系统通过这个常量来判断ELF文件的真正文件类型,而不是通过文件的扩展名。

    2.  段表

    段表(Section Header Table)就是保持ELF文件各段基本属性的结构。编译器、链接器、装载器都是依靠段表来定位和访问各个段的属性的。使用readelf工具来查看ELF文件段的结构。

    $readelf –S SimpleSection.o

    段表的结构比较简单,它是以“Elf32_Shdr”结构体为元素的数组,数组元素的个数等于段的个数。“Elf32_Shdr”也被称为段描述符(Section Descriptor

    Elf32_Shdr各成员的含义如下:

    至此,才把SimpleSection的所有段的位置和长度分析清楚,如下图所示。段表Section Table长度为0x208,即520个字节,包含了13个段描述符。每个段描述符为4×10=40Bytes。

    3.  重定位表

    链接器在处理目标文件时,需要对目标文件中的某些部位进行重定位,即代码段和数据段中的那些对绝对位置的引用的位置,如.rel.text就是针对.text段的重定位表,因为.text段中至少有一个绝对地址的引用,那就是printf函数的调用。

    4.  字符串表

    ELF文件中用到了很多字符串,如段名、变量名等,由于字符串的长度往往不定,因此常把字符串集中起来存放到一个表,然后使用字符串在表中的偏移来引用字符串。

    一般字符串在ELF文件中也以段的形式保存,常见的段名如.strtab和.shstrtab。字符串表(.strtab)保存普通的字符串,段表字符串表(.shstrtab)保存段表中用到的字符串,最常见的就是段名。

    参考资料:《程序员的自我修养——链接、装载与库》

    Jacky Liu

    当你心中只有一个目标时,全世界都会给你让路!Read more! Write more! Practise more! 新浪微博:liu_军
     
    分类: 操作系统
    标签: 操作系统
  • 相关阅读:
    学习心得总结(1)
    Git使用总结
    Git管理分支
    Git 常用命令整理
    C#Windows窗体中添加了AxWindowsMediaPlayer的详细用法影响键盘操作的问题
    连接SQLsever数据库在C#中不能操作的问题
    C#中字符串的操作
    iOS TableView如何刷新指定的cell或section
    iOS所有的子视图
    iOStextFiled判断输入长度
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2881488.html
Copyright © 2011-2022 走看看