第3章_目标文件里有什么

目标文件:已经编译过,还没有链接

3.1 目标文件的格式

主流格式

现在PC平台流行的Executable File Format主要是COFF格式(Common File Format)包括

  1. windows下的PE-COFF(Portable Executable)
  2. Linux下ELF(Executable Linking File)

不太流行的

  1. Intel/Microsoft OMF (Object Module Format)
  2. Unix a.out
  3. MS-DOS .COM

DLL, lib

动态链接库DLL, Dynamic Linking Library和静态链接库Static Linking Library(静态链接库将许多目标文件打包并加上索引)都按照PE-COFF或ELF文件格式存储

ELF文件标准

文件类型 说明 实例
Relocatable File 可重定位文件 1. 可以被用于链接成为共享目标文件或者可执行文件 2.包括静态链接库 linux .o,.a win .obj,.lib
Executable File 可执行文件 可执行的程序 linux下可以没有拓展名,win .exe
Shared Object File 共享目标文件 1. 可以被链接为新目标文件 2. 可以被动态链接器链接入可执行文件作为程序一部分 linux .so win .dll
Core Dump File核心转储文件 程序意外终止时可以用于保存地址空间的内容和其他信息 Linux core dump

目标文件小历史

a.out太过简单 COFF格式定义了段和调试数据格式

3.2 目标文件是什么样的

section 和segment在链接和装载时有细微区别

BSS历史

是UA-SAP汇编器中的一个伪指令,用于为符号预留一块内存空间

为何指令数据分离

  1. 可以分别映射为可读写和只读,保护程序数据
  2. 方便CPU,如缓存命中
  3. 程序代码共享

3.3 挖掘SimpleSection.o

3.3.1 代码段

  1. 汇编代码长度不一定
  2. 有些函数会被单独分段

3.3.2 数据段和只读数据段

  1. 有时编译器不会把字符串常量放在只读数据段
  2. 采用只读数据段可以贴合只读存储器比如ROM

3.3.3 BSS段

  1. 有些编译器会将未定义的未初始化变量放在bss段,有些则不会只是预留一个未定义的全局变量符号,等到最终链接为可执行文件再在bss段分配空间,这可能与强符号和弱符号,common块等有关
  2. 编译单元内部的静态变量的确存放在bss段

3.3.4 其他段

常用段名 说明
.rodata1 同.rodata
.comment 存放编译器版本信息
.debug debug
.dynamic 动态链接信息
.hash 符号hash表
.line 行号表
.note 额外的编译器信息,比如程序公司名,发布版本号等
.strtab string table
.symtab symbol table
.shstrtab section header string table
.plt && .got 动态链接的跳转表和全局入口表
.init && .fini 程序初始化与终结代码段

自定义段

  1. 自定义段名不应该使用.作为前缀
  2. 可以使用objcopy将二进制文件作为程序的一个段

3.4 ELF文件结构描述

3.4.1 文件头

ELF文件头结构及相关参数被定义在/usr/include/elf.h中

typedef struct elf32_hdr{
  unsigned char e_ident[EI_NIDENT]; //Magic 16字节 
  Elf32_Half e_type;  //elf文件类型
  Elf32_Half e_machine;  //CPU平台属性
  Elf32_Word e_version;  //ELF文件版本号,一般为1,ELF 文件最新版本为1.2
  Elf32_Addr e_entry; /* Entry point 程序入口的虚拟地址,可重定位文件一般为0*/
  Elf32_Off e_phoff;  //start of program headers
  Elf32_Off e_shoff;  //start of section headers
  Elf32_Word e_flags;  //
  Elf32_Half e_ehsize;  //size of elf header
  Elf32_Half e_phentsize;  // size of program headers
  Elf32_Half e_phnum;  //num of program header
  Elf32_Half e_shentsize; //size of section headers
  Elf32_Half e_shnum; //num of section header
  Elf32_Half e_shstrndx; //section header string table index
 } Elf32_Ehdr;  //此结构体一共52个字节

magic numbers

7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

del E L F class(0x01 32,0x02 64) endian version

==系统在加载时会确定魔数是否正确,不正确会停止加载==

文件类型

系统通过e_type而不是扩展名判断elf文件类型

3.4.2 段表

ELF段表数组的第一个元素是无效的段描述符,类型为NULL

typedef struct
{
Elf32_Word        sh_name;        /* 在section string table中的索引 */
Elf32_Word        sh_type;        /* Section type */
Elf32_Word        sh_flags;       /* Section flags */
Elf32_Addr        sh_addr;        /* Section virtual addr at execution */
Elf32_Off        sh_offset;      /* Section file offset,如果该段存在于文件中,bss段offset无意义 */
Elf32_Word        sh_size;        /* Section size in bytes */
Elf32_Word        sh_link;        /* Section Link */
Elf32_Word        sh_info;        /* Section information */
Elf32_Word        sh_addralign;   /* Section Address alignment 表示地址对齐数量的指数,2**sh_addralign,如果sh_addralign为0或1,则表示没有对齐要求 */
Elf32_Word       sh_entsize;     /* Section Entry Size,如果段包含固定大小的项,表示项的大小 */
} Elf32_Shdr;

sh_type

==段的名字只在链接和编译过程有意义,决定段的属性的是sh_type和sh_flags==

常量 含义
0 SHT_NULL 无效段
1 SHT_PROGBITS 程序段代码段数据段等都是这种类型
2 SHT_SYMTAB 符号表
3 SHT_STRTAB 字符串表
4 SHT_RELA 重定位表
5 SHT_HASH 符号hash表
6 SHT_DYNAMIC 动态链接信息
7 SHT_NOTE 提示性信息
8 SHT_NOBITS 该段在文件中没有内容,bss
9 SHT_REL 重定位信息
10 SHT_SHLIB 保留
11 SHT_DNYSYM 动态链接的符号表

sh_flags

常量 含义
1 SHF_WRITE 可写
2 SHF_ALLOC 需要在进程空间中分配空间
3 SHF_EXECINSTR 可执行

常见表的属性

Name sh_type sh_flag
.bss SH_NOBITS SHF_ALLOC,SHF_WRITE
.comment SHT_PROGBITS none
.data SHT_PROGBITS SHF_ALLOC, SHF_WRITE
.debug SHT_PROGBITS none
.dynamic SHT_DYNAMIC SHF_ALLOC, SHF_WRITE?
.hash SHT_HASH SHF_ALLOC
.line SHT_PROGBITS none
.note SHT_NOTE none
.rodata SHT_PROGBITS SHF_ALLOC
.shstrtab SHT_STRTAB none
.strtab SHT_STRTAB 如果有可装载的段需要用到该字符串表,那么SHF_ALLOC
.symtab SHT_SYMTAB 如果有可装载的段需要用到该字符串表,那么SHF_ALLOC
.text SHT_PROGBITS SHF_ALLOC,SHF_EXECINSTR
sh_type sh_link sh_info
SHT_DYNAMIC 该段使用的字符串表在段表中的下标 0
SHT_HASH 该段使用的符号表在段表中的下标 0
SHT_REL或SHT_RELA 该段使用的相应符号表在段表中的下标 该重定位表所作用的段在段表中的下标
SHT_SYMTAB或SHT_DYNSYM 操作系统相关 操作系统相关
other SHN_UNDEF 0

3.4.3 重定位表

代码段和数据段中对绝对地址的引用都需要重定位,每个需要重定位的表都有相应的重定位表段

sh_type为SHT_REL,sh_info表明其作用表的下标

3.4.4 字符串表

把字符串集中起来存放,然后使用字符串在表中的偏移来引用字符串

3.5 链接的接口-符号

  1. 函数和变量统称为Symbol,Symbol Name,Symbol Value

符号可能包含以下几种

  1. 变量 Symbol Value为地址
  2. 函数 value为地址
  3. 定义在本目标文件,可以被其他目标文件引用的全局符号
  4. 在本目标文件中引用
  5. 段名,value是该段的起始地址
  6. 局部符号,只在编译单元内部可见,这些局部符号对于链接器没有作用
  7. 行号信息

3.5.1 ELF符号表结构

typedef struct{
    ELF32_Word st_name,//符号名在字符串表中的下标
    ELF32_Addr st_value,//对应的值
    ELF32_Word st_size,//符号大小,数据类型大小或者0(0或未知)
    unsigned char st_info,//符号类型和绑定信息
    unsigned char st_other,//0
    ELF_Half st_shndx;//符号所在的段
}ELF32_Sym

st_info

==低4位表示Symbol Type,高28位表示Symbol Binding==

symbol binding

宏定义名 说明
0 STB_LOCAL 局部
1 STB_GLOBAL 全局外部可见
2 STB_WEAK 弱引用

symbol type

宏定义名 说明
0 STT_NOTYPE 未知
1 STT_OBJECT 数据对象
2 STT_FUNC 函数
3 STT_SECTION 表示一个段,该符号必须是STB_LOCAL的
4 STT_FILE 表示文件名,该符号必须是STB_LOCAL的,st_shndx一定是SHN_ABS

st_shndx

如果符号定义在本目标文件中,则代表该符号所在段的下标

宏定义名 说明
0xfff1 SHN_ABS 绝对的值
0xfff2 SHN_COMMON COMMON块类型符号
0 SHN_UNDEF 未定义,在其他目标文件中

st_value

  1. 可执行文件中,表示符号的虚拟地址
  2. 目标文件中,若不是COMMON块,表示相对于st_shndx的偏移
  3. COMMON块类型,表示对齐属性

3.5.2 特殊符号

有些符号被定义在==ld的链接脚本==中(只有使用ld链接才会存在)

__executable_start 程序起始地址
__etext或_etext或etext 代码段结束地址
_edata或edata 数据段结束地址
_end或end 程序结束地址

3.5.3 符号修饰与函数签名

  1. 20世纪70年代之前符号名与变量名或者函数名相同
  2. 为了防止不同语言的目标文件冲突,后来c语言符号名在前面加_,fortran在前后都加
  3. 现在gcc(可以通过-fleading-underscore来控制是否加下划线)在linux下不用加下划线,但是win下回家,win下的编译器也还加下划线

c++符号修饰Name Decoration,Name Mangling

gcc的修饰方法

_ZN1N1C4funcEi//N::C::func(int)
  1. _Z开头
  2. 嵌套的部分:N[len(name)][name]
  3. E结尾
  4. 函数参数列表跟在E后面,比如int就是i

也用来防止静态变量名称冲突

function signature

3.5.4 extern "C"

c++编译器将会把extern "C"大括号中的内容当做c语言代码处理

__cplusplus

c++编译器会在编译时默认定义这个宏,所以可以使用条件宏判断当前是不是c++

3.5.5 弱符号和强符号Weak Symbol,Strong Symbol

  1. 默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号
  2. 外部全局变量不是强符号也不是弱符号

选择规则

  1. 不允许强符号被多次定义
  2. 如果一个符号在某个目标文件中是强符号,其他文件中都是弱符号,那么选择强符号
  3. ==如果都是弱符号,选择占空间最大的,容易引发bug==

强引用和弱引用

strong ref:对外部目标文件的符号引用在链接时需要被正确决议,如果没有找到符号定义,链接器就会报错

weak ref:如果没有定义,则不报错,而是默认其为0或者是特殊值

__attribute((weakref)),gcc,设置对外部函数的引用为弱引用

作用

  1. 可以用于重定义库函数,或者链接拓展功能模块
  2. 可以使用弱引用来判断是否有-lpthread选项
int pthread_create(pthread_t *, const pthread_attr_t *, void * (*) (void *), void *) __attrbute__((weak));

int main(){
    if(pthread_create){
        puts("multi-thread");
    }
}

3.6 调试信息

ELF文件采用DWARF(Debug With Arbitrary Record Format)标准的调试信息格式

命令和工具

file

查看相应文件格式

gcc -c

只编译不链接

objdump

-h 打印ELF文件中主要段的基本信息

-s 将段内容以16进制打印

-d 将所有包含指令的段反汇编

COTENTS属性表示该段在elf文件中实际存在

size

用于查看ELF文件text,data,bss段长度

readelf

详细查看elf文件

objcopy

objcopy -I binary -O elf32-i386 -B -i386 image.jpg image.o 可以将image.jpg作为单独的段加入image.o中

attribute((section("name")))

放在全局变量或者函数前面可以将其指定到对应name的段中

nm

用于查看elf文件的符号表

c++file

可以解析gcc被修饰的名称

UnDecorateSymbolName

解析visual c++被修饰的名称

extern "C"

c++编译器将会把extern "C"大括号中的内容当做c语言代码处理

__cplusplus

c++编译器会在编译时默认定义这个宏,所以可以使用条件宏判断当前是不是c++

__attribute((weakref))

gcc,设置对外部函数的引用为弱引用

strip

linux,去除elf文件调试信息