目标文件的格式

目前主要流行两种,均是 COFF格式 的变体

  • windows -> PE (portable executable)
  • linux -> ELF (executable linkable format)

ELF格式文件类型 (PE类似)

  • 可重定位文件 (relocatable file)

    • linux 下的 .o 和 windows 下的 .obj

    用于链接成可执行文件或者共享目标文件,静态库

  • 可执行文件 (executable file)

    • linux 下的 /bin/bash 和 windows 下的 .exe

    可以直接执行

  • 共享目标文件 (shared object file)

    • linux 下的 .so 和 windows 下的 .dll

    链接器将其与可重定位文件或其他共享目标文件链接形成新的目标文件,或者动态链接器将其与可执行文件结合,作为进程映像的一部分运行,动态库

  • 核心转储文件 (core dump file)

目标文件是什么样的

1

以上是一个c程序的ELF文件的简化结构,信息之间是按照 section 又称为 segment 为单位进行存储的

  • File Header
    文件头描述了整个文件的属性,包括文件类型、入口地址、目标操作系统以及段表等等

  • .text section
    代码段存储的是 整个程序运行的指令,一般是 只读

  • .data section
    数据段存储的是 初始化的全局变量和静态变量,一般是 可读写 的,重点是 已初始化

  • .bss section
    bss段存储的是 未初始化的全局变量和静态变量,一般是 可读写 的,重点是 未初始化 的。需要注意的是,未初始化的默认值是0,所以为了节省磁盘空间,.bss段并不占用磁盘空间,它仅是 预留位置,等到程序运行时在实际分配内存,ELF文件也只是存储了.bss段的信息大小,比如要预留多少字节

分段存放信息的好处:

  • 安全性

    信息分段处理,可以更好的 区分对不同信息的访问权限,比如代码段只读,数据段可读写,这样可以保护代码段不被破坏,如果混合在一起,就不好区分了

  • 提高CPU缓存命中率

    CPU访问数据是依照局部性的,就是从A处获取数据后,下一次从A附近获取数据的概率较大,而且CPU还有强大的缓存功能,所以将同类信息放在一起,可以提高CPU的命中率

  • 共享性

    进程拥有独立的地址空间,如果给每个进程都单独准备一份数据,即使他们可能有部分相同,这会造成极大的浪费,而将相同类型的信息放在同一段,则可以方便的实现资源共享,比如父子进程共享一个代码段,待到要修改时在copy一份专属于各自的代码段,这样可以节省空间

ELF文件结构

  • 文件头: 定义了 ELF魔数、数据存储方式(大小端)、文件机器字节长度等等

  • 段表: 记录了各个段的信息,包括段名、段长、段属性等等(第一个段是无效段,也就是说有效段的索引从1开始)

  • 重定位表: 以.rela开头的段名,比如说.rela.text就是针对.text段的重定位表,它表明,.text段至少包含了一个对绝对地址的引用,需要进行重定位,比如函数调用之类的,函数名需要替换成实际地址

  • 字符串表: ELF文件用到了许多字符串,包括段名、变量名等等,字符串长度不等,要求是以 空字节 表示结尾,所以使用 偏移量 访问对应字符串,也就是只用知道字符串开头的偏移位置,就能获取到整个字符串

    • 字符串表一般作为段的形式保存,普通字符串表 保存普通的字符串,比如变量名这些,段表字符串表 用来保存段表中用到的字符串,比如段名
  • 符号表: 存储符号名、符号值、符号大小、符号类型(变量、函数等)等等

    • 符号修饰: 符号也是有自己的作用域的,所以在链接阶段,为了避免冲突,通常还会在原有的符号名上,加一些其他修饰,比如表示作用域,类型大小等等

    • 特殊符号:

      • etext 代码段结束地址
      • edata 数据段结束地址
      • end bss段结束地址

      需要注意的是,这三个符号是由链接器提供的外部符号,并没有显示的类型,指向某一处地址,也可以理解void *,在程序中使用时,需要给一个类型,避免编译器报错

      #include <stdio.h>
      
      extern char etext, edata, end;
      
      int main(void)
      {
         printf("Test End %10p\n", &etext);
         printf("Data End %10p\n", &edata);
         printf("Executable End %10p\n", &end);
         return 0;
      }
      
      
  • 调试信息: 保存了源代码与目标代码的关系,比如目标代码对应与源代码的行号信息、函数、变量等等,占有很大的空间

 posted on 2025-03-12 19:35  Dylaris  阅读(37)  评论(0)    收藏  举报