目标文件的分类

现在PC流行的可执行文件主要是Windows的PE和linux下的ELF(executable linkable format),他们都是COFF(common file format)的变种。
目标文件就是源代码编译后但是未链接的中间文件(linux下的.o文件),它和可执行文件的内容和结构很相似,一般采用相同格式存储。

ELF文件类型 说明 实例
可重定位文件(Relocatable file) 包含代码和数据,可用来链接成可执行文件或共享目标文件,静态链接库也可以归为此类 linux下的.o,Windows下的.obj
可执行文件(Executable file) 可直接执行的程序,它的代表就是ELF可执行文件,一般没有拓展名 如/bin/bash文件,Windows下的exe
共享目标文件(shared object file) 包含代码和数据。链接器可用该文件和其他可重定位文件和共享目标文件链接,产生新的目标文件。2.动态链接器可将几个共享目标文件和可执行文件结合,作为进程映像的一部分来运行 linux的.so,如/lib/glibc-2.5.so,Windows下的DLL
核心转储文件(core dump file) 当进程意外终止,系统可将该进程地址空间的内容及终止时的一些其他信息转储到该文件 linux下的core dump

可在linux下使用file命令查看文件类型

目标文件的内容

目标文件中被分为各个段来进行存储,例如.text , .data .bss .rodata等,该部分可见博客GDB调试之段信息

此处介绍目标文件的结构,以作了解

ELF文件结构可大致参考该图,其中ELF Header中保存了段表的地址,而段表中描述了各个段的偏移
大小等相关信息。链接器和装载器就是根据段表来定位和访问各个段的属性的。objdump -h只显示了关键的段信息,使用readelf -S可查看全部段信息

链接的接口-符号

  1. 概念

    链接过程的本质就是要把多个不同的目标文件之间相互粘在一起。在链接中,目标文件之间相互拼合实际就是目标文件之间对地址的引用,即对函数和变量的地址的引用。在链接中,我们将函数和变量统称为符号(Symbol),函数名和变量名就是符号名(Symbole name)

    我们可以将符号看作是链接中的粘合剂,整个链接过程正是基于符号才能够正确完成,连接过程中很关键的一部分就是符号的管理,没一个目标文件都会有一个相应的符号表(Symbol table),这个表中记录了目标文件中所有用到的所有符号。每个定义的符号有一个对应的值,叫做符号值(symbole value),对于变量和函数来说,符号值就是他们的地址。

    • 定义在本目标文件的全局符号,可被其他目标文件引用
    • 在本目标文件中引用的全局符号,却没有定义在本目标文件中,一般叫做外部符号,也就是前面说的符号引用
    • 段名,这种符号往往由编译器产生,它的值就是该段的起始地址
    • 局部符号,这类符号只在编译单元内部课件。调试器可以使用这些符号来分析程序或崩溃时的核心转出文件,这些局部符号对于链接过程没有作用,链接器往往也忽略他们
    • 行号信息,即目标文件指令与源代码中代码行的对应关系
  2. C++的符号修饰

    这个概念简单了解下,C++拥有类,继承,重载,命名空间等机制,那么为了支持这些复杂的特性,引入了符号修饰或符号改编,简单来说就是C++的符号拥有独特的前缀和后缀来描述该符号的特性,就是我们使用nm命令看到的符号中的那些杂乱的字母,这些字母的含义此处不做介绍。

    extern C

    为了C和C++兼容,所以在C++中引入了extern “C”,被其修饰的代码,符号修饰机制将不起作用。

    若你的头文件声明了一些C语言的函数和变量,但是这个头文件有可能被C或C++引用,可使用C++中的宏__cplusplus

    1
    2
    3
    4
    5
    6
    7
    8
    #ifdef __cplusplus
    extern "c" {

    #endif

    #ifdef __cplusplus
    }
    #endif

程序的指令和数据分开存放的目的

  • 程序被装载后,数据和指令分别映射到两个虚存,由于数据对于进程可读写,指令是只读的,所以方便设置两个虚存区域的权限,防止程序被意外修改
  • 提高CPU的缓存命中率,现代CPU的缓存设计一般都被设计成为数据缓存和指令缓存分开
  • 最重要的原因,当系统中运行这多个该程序的副本,它们的指令都是一样的,所以内存中只需保存一份该程序的指令部分。