链接/装载/运行(3)-目标文件内容解析

声明:此内容是阅读《程序员的自我修养–链接,运行与库》而整理的学习笔记。

1 范例解析

1.1代码

/* SimpleSection.c
 *
 * Linux: gcc -c SimpleSection.c
 *
*/

int printf(const char* format, ...);

int global_init_var = 84;
int global_uninit_var;

void func1(int i)
{
	printf("%d\r\n", i);
}

int main(void)
{
	static int static_var = 85;
	static int static_var2;
	
	int a = 1;
	int b;
	
	func1( static_var+static_var2+a+b );
	return a;
}

1.2 解析目标文件

SimpleSection.o的二进制

具体这部分二进制都代表那些内容,见下表:

地址区间描述
0x00000000----0x0000003F文件头
0x00000040----0x00000095.text
0x00000098----0x0000009F.data
.bss
0x000000A0----0x000000A4.rodata
0x000000A5----0x000000DA.comment
.note.GUN-stack
0x000000E0----0x00000137.eh_frame
0x00000138----0x000002B7.symtab
0x000002B8----0x0000031D.strtab
0x00000320----0x00000397.rela.text
0x00000398----0x000003C7.rela.eh_frame
0x000003C8----0x00000428.shstrtab
0x00000430----0x0000076F段表

1.3 段头信息

将目标文件SimpleSection.o文件中一些段的基本信息打印出来:

$ objdump -h SimpleSection.o

SimpleSection.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000055  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  0000000000000000  0000000000000000  00000098  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  0000000000000000  0000000000000000  000000a0  2**2
                  ALLOC
  3 .rodata       00000005  0000000000000000  0000000000000000  000000a0  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      00000036  0000000000000000  0000000000000000  000000a5  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  0000000000000000  0000000000000000  000000db  2**0
                  CONTENTS, READONLY
  6 .eh_frame     00000058  0000000000000000  0000000000000000  000000e0  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

这里只说明CONTENTS表示该段在文件中存在,没有该字段,表明该段在该文件中不占用空间。
段的属性需要再了解一下。

2 目标文件结构

说明:

  • 目标文件结构体定义在/usr/include/elf.h文件中
  • 这里的分析基于64位Linux系统

2.1 文件头

读取目标文件SimpleSection.o的文件头:

$ readelf -h SimpleSection.o
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          1072 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         13
  Section header string table index: 10

文件头的结构体定义如下:

typedef struct
{
  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */		// 16 Bytes
  Elf64_Half	e_type;				/* Object file type */					// 2 Bytes
  Elf64_Half	e_machine;			/* Architecture */						// 2 Bytes
  Elf64_Word	e_version;			/* Object file version */				// 4 Bytes
  Elf64_Addr	e_entry;			/* Entry point virtual address */ 		// 8 Bytes
  Elf64_Off		e_phoff;			/* Program header table file offset */ 	// 8 Bytes
  Elf64_Off		e_shoff;			/* Section header table file offset */ 	// 8 Bytes
  Elf64_Word	e_flags;			/* Processor-specific flags */ 			// 4 Bytes
  Elf64_Half	e_ehsize;			/* ELF header size in bytes */ 			// 2 Bytes
  Elf64_Half	e_phentsize;		/* Program header table entry size */ 	// 2 Bytes
  Elf64_Half	e_phnum;			/* Program header table entry count */	// 2 Bytes
  Elf64_Half	e_shentsize;		/* Section header table entry size */	// 2 Bytes
  Elf64_Half	e_shnum;			/* Section header table entry count */	// 2 Bytes
  Elf64_Half	e_shstrndx;			/* Section header string table index */	// 2 Bytes
} Elf64_Ehdr;
  • e_ident: 对应于文件头中的 Magic字段。
    byte0-byte3表示ELF文件的魔数,这里的魔数是0x7F, 0x45, 0x4C, 0x46,其他文件类型的魔数可以Google。
    byte4表示ELF文件类,对应于文件头中的Class字段,这里0x02表示64位,具体值表示的含义参考elf.h文件中以ELFCLASS开头定义的宏。
    byte5表示字节序,对应于文件头中的Data字段,这里0x01表示小端字节序,具体值表示的含义参考elf.h文件中以ELFDATA开头定义的宏。
    byte6表示ELF文件主版本号,对应于文件头中的Version字段,一般是0x01,因为ELF标准自1.2版本后就没有更新了。
    byte7表示操作系统ABI类型,对应于文件头中的OS/ABI字段,这里0x00表示Unix System V ABI, 具体值表示的含义参考elf.h文件中以ELFOSABI_开头定义的宏。
    byte8表示ABI版本,对应于文件头中的ABI Version字段,这里是0x00。
    byte9-byte15这几个字节ELF标准没有定义,默认是0。
  • e_type: 对应于文件头中的Type字段,表示ELF文件类型,这里0x0001表示重定位文件,具体值表示的含义参考elf.h文件中以ET_开头定义的宏。
  • e_machine: 对应于文件头中 的Machine字段,表示机器架构,这里是0x003E,表示AMD x86-64,具体值表示的含义参考elf.h文件中以EM_开头定义的宏。
  • e_version: 对应于文件头中的Verison字段,表示ELF文件版本号,一般为常数1,具体值表示的含义参考elf.h文件中以EV_开头定义的宏。
  • e_entry: 对应于文件头中的Entry point address字段,表示ELF程序的入口虚拟地址,可重定位文件一般没有入口地址,所以这里的值为0。
  • e_phoff: 对应于文件头中的Start of program headers字段,表示。。。。
  • e_shoff: 对应于文件头中的Start of section headers字段,表示段表在文件中的偏移字节数,这里是1072表示段表从文件的第1072个字节开始。
  • e_flags: 对应于文件头中的Flags字段,表示与文件关联的特定于处理器的标志。对于 x86,此成员目前为0。具体值表示的含义参考elf.h文件中以EF_machine_flag格式定义的宏。
  • **e_ehsize:**对应于文件头中的Size of this header字段,这里是64表示文件头的长度是64字节。
  • e_phentsize: 对应于文件头中的Size of program headers字段,这里是0表示。。。
  • e_phnum: 对应于文件头中的Number of program headers字段,这里是0表示。。。
  • e_shentsize: 对应于文件头中的Size of section headers字段,这里是64表示段表描述符的大小是64字节。
  • e_shnum: 对应于文件头中的Number of section headers字段,这里是13表示段表描述符的数量是13个。
  • e_shstrndx: 对应于文件头中的Section header string table index字段,这里是10表示段表字符串段在段表中的下标是10。

2.2 段

2.2.1 .text/.data/.rodata/.comment/.eh_frame

读取目标文件SimpleSection.o的部分段:

$ objdump -s -d SimpleSection.o

SimpleSection.o:     file format elf64-x86-64

Contents of section .text:
 0000 554889e5 4883ec10 897dfc8b 45fc89c6  UH..H....}..E...
 0010 bf000000 00b80000 0000e800 00000090  ................
 0020 c9c35548 89e54883 ec10c745 f8010000  ..UH..H....E....
 0030 008b1500 0000008b 05000000 0001c28b  ................
 0040 45f801c2 8b45fc01 d089c7e8 00000000  E....E..........
 0050 8b45f8c9 c3                          .E...           
Contents of section .data:
 0000 54000000 55000000                    T...U...        
Contents of section .rodata:
 0000 25640d0a 00                          %d...           
Contents of section .comment:
 0000 00474343 3a202855 62756e74 7520352e  .GCC: (Ubuntu 5.
 0010 342e302d 36756275 6e747531 7e31362e  4.0-6ubuntu1~16.
 0020 30342e31 30292035 2e342e30 20323031  04.10) 5.4.0 201
 0030 36303630 3900                        60609.          
Contents of section .eh_frame:
 0000 14000000 00000000 017a5200 01781001  .........zR..x..
 0010 1b0c0708 90010000 1c000000 1c000000  ................
 0020 00000000 22000000 00410e10 8602430d  ...."....A....C.
 0030 065d0c07 08000000 1c000000 3c000000  .]..........<...
 0040 00000000 33000000 00410e10 8602430d  ....3....A....C.
 0050 066e0c07 08000000                    .n......        

Disassembly of section .text:

0000000000000000 <func1>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	48 83 ec 10          	sub    $0x10,%rsp
   8:	89 7d fc             	mov    %edi,-0x4(%rbp)
   b:	8b 45 fc             	mov    -0x4(%rbp),%eax
   e:	89 c6                	mov    %eax,%esi
  10:	bf 00 00 00 00       	mov    $0x0,%edi
  15:	b8 00 00 00 00       	mov    $0x0,%eax
  1a:	e8 00 00 00 00       	callq  1f <func1+0x1f>
  1f:	90                   	nop
  20:	c9                   	leaveq 
  21:	c3                   	retq   

0000000000000022 <main>:
  22:	55                   	push   %rbp
  23:	48 89 e5             	mov    %rsp,%rbp
  26:	48 83 ec 10          	sub    $0x10,%rsp
  2a:	c7 45 f8 01 00 00 00 	movl   $0x1,-0x8(%rbp)
  31:	8b 15 00 00 00 00    	mov    0x0(%rip),%edx        # 37 <main+0x15>
  37:	8b 05 00 00 00 00    	mov    0x0(%rip),%eax        # 3d <main+0x1b>
  3d:	01 c2                	add    %eax,%edx
  3f:	8b 45 f8             	mov    -0x8(%rbp),%eax
  42:	01 c2                	add    %eax,%edx
  44:	8b 45 fc             	mov    -0x4(%rbp),%eax
  47:	01 d0                	add    %edx,%eax
  49:	89 c7                	mov    %eax,%edi
  4b:	e8 00 00 00 00       	callq  50 <main+0x2e>
  50:	8b 45 f8             	mov    -0x8(%rbp),%eax
  53:	c9                   	leaveq 
  54:	c3                   	retq   
  • .text: 是代码段,也就是这个目标文件中的指令。
  • .data: 是数据段,初始化不为0的全局变量和初始化不为0的静态变量。这里存放的是变量global_init_var和static_var的值。
  • .bss: 这个段只是为一些变量(未初始化的全局变量,未初始化的静态变量,初始化为0的全局变量,初始化为0的静态变量)预留位置,并不占用目标文件的空间。
    这里的.bss只有4个字节,也就是这里只将未初始化的静态局部变量static_var2放在了该段中,全局未初始化的变量global_uninit_var没有放在任何段中,只是一个未定义的COMMON符号,这与不同的语言和编译器的实现有关,有写编译器会将未初始化的全局变量放在.bss段,有写则不放,只是预留一个未定义的全局变量符号,等到最终链接成可执行文件时再在.bss中分配空间。
    static int x1 = 0; static int x2 = 1; x1会放在.bss段,x2会放在.data段中,因为未初始化的静态变量都是0,放在.bss可以节省磁盘空间。
  • .rodata: 只读数据段,这里存放的是常量数据,这里存放的是字符串"%d\r\n"。
  • .comment: 注释信息段。
  • .note.GNU-stack: 堆栈提示段。
  • .eh_frame: 调试信息段
2.2.2 符号段

符号表中的符号有以下几类:

  • 定义在本目标文件中的全局符号;
  • 在本目标文件中引用的全局符号,却没有定义在本目标文件;
  • 段名;
  • 局部符号,这类符号对链接过程没有作用,调试器可以用这些符号来分析程序或崩溃时的核心转储文件;
  • 行号信息,目标文件指令与源代码中代码行的对应关系

链接过程只关注全局符号,也就是前两类,其他类型的符号对于别的目标文件是不可见的,对于链接过程无关紧要。

查看目标文件SimpleSection.o中的符号表:

$ readelf -s SimpleSection.o 

Symbol table '.symtab' contains 16 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS SimpleSection.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     6: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    3 static_var.1840
     7: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 static_var2.1841
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
    10: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
    11: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_init_var
    12: 0000000000000004     4 OBJECT  GLOBAL DEFAULT  COM global_uninit_var
    13: 0000000000000000    34 FUNC    GLOBAL DEFAULT    1 func1
    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
    15: 0000000000000022    51 FUNC    GLOBAL DEFAULT    1 main

符号表的结构体:

typedef struct
{
  Elf64_Word	st_name;		/* Symbol name (string tbl index) */	// 4 Bytes
  unsigned char	st_info;		/* Symbol type and binding */		// 1 Bytes
  unsigned char st_other;		/* Symbol visibility */				// 1 Bytes
  Elf64_Section	st_shndx;		/* Section index */					// 2 Bytes
  Elf64_Addr	st_value;		/* Symbol value */					// 8 Bytes
  Elf64_Xword	st_size;		/* Symbol size */					// 8 Bytes
} Elf64_Sym;
  • st_name: 该值是符号名字符串在.strtab中的偏移位置,符号名字符串对应于符号表中的Name字段。Name字段是空的,表示的是段名。
  • st_info: 低4位对应于符号表中的Type字段,高4位对应于符号表中的Bind字段。具体的Type值表示的含义参考elf.h文件中以STB_开头的宏;具体的Bind值表示的含义参考elf.h文件中以STT_开头的宏。
  • st_other: 对应于符号表中的Vis字段,目前默认为0,没有用到。
  • st_shndx: 如果符号定义在本目标文件中,那么该值表示符号所在的段在段表中的下标;如果符号不是定义在本目标文件中或者对于特殊符号,则该值的含义参考elf.h文件中以SHN_开头的宏。
  • st_value: 对应于符号表中的Value字段。如果符号不是COMMON类型,表示的是地址,该值表示是在索引为st_shndx段中的偏移量;如果符号是COMMON类型,则该值表示符号的对齐属性;在可执行文件中,该值表示符号的虚拟地址。
  • st_size: 对应于符号表中的Size字段。表示符号的大小。如果是变量,表示变量所占字节数;如果是函数,表示函数所占字节数。

2.2.3 重定位段

说明:链接器在处理目标文件时,必须对目标文件中某些部位进行重定位,即代码段和数据段中那些对绝对地址的引用的位置,这些重定位信息都记录在重定位表中。对于每一个需要重定位的代码段或数据段,都会有一个相应的重定位表。
显示目标文件SimpleSection.o中的重定位表:

$ readelf -r SimpleSection.o

Relocation section '.rela.text' at offset 0x320 contains 5 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000011  00050000000a R_X86_64_32       0000000000000000 .rodata + 0
00000000001b  000e00000002 R_X86_64_PC32     0000000000000000 printf - 4
000000000033  000300000002 R_X86_64_PC32     0000000000000000 .data + 0
000000000039  000400000002 R_X86_64_PC32     0000000000000000 .bss - 4
00000000004c  000d00000002 R_X86_64_PC32     0000000000000000 func1 - 4

Relocation section '.rela.eh_frame' at offset 0x398 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0
000000000040  000200000002 R_X86_64_PC32     0000000000000000 .text + 22

2.2.4 字符串段

  • .strtab: 保存普通的字符串,例如符号的名字。
  • .shstrtab: 保存段表中用到的字符串,常见的是段名。

2.2.5 自定义段

  • 以.开头表示名字是系统保留的段名;也可以自定义段名,但是这时候不能用.前缀,否则容易与系统保留段名冲突;一个ELF文件可以拥有几个相同段名的段,比如两个或两个以上的.text段。
  • gcc可以指定变量或函数所存放的段:
__atrribute__((setcion("FOO"))) int global = 54;        // 将global放在FOO段
__atrribute__((section("FOOR"))) void foo(){}      	// 将函数foo放在FOOR段,这里是只在声明里加,还是需要在定义中加???有时间可以验证下
  • 将一个图片image.JPG作为一个段:
$ objcopy -I binary -O elf32-i386 -Bi386 image.JPG image.o
$ objdump -ht image.o

image.o:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .data         001c5d72  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, DATA
SYMBOL TABLE:
00000000 l    d  .data    00000000 .data
00000000 g       .data    00000000 _binary_image_JPG_start
001c5d72 g       .data    00000000 _binary_image_JPG_end
001c5d72 g       *ABS*    00000000 _binary_image_JPG_size

符号 _binary_image_JPG_start_binary_image_JPG_end_binary_image_JPG_size分别表示图片在内存中的起始位置,结束位置,和大小,可以在程序中直接调用

2.3 段表

读取目标文件SimpleSection.o的段表信息:

$ readelf -S SimpleSection.o
There are 13 section headers, starting at offset 0x430:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000055  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000320
       0000000000000078  0000000000000018   I      11     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000098
       0000000000000008  0000000000000000  WA       0     0     4
  [ 4] .bss              NOBITS           0000000000000000  000000a0
       0000000000000004  0000000000000000  WA       0     0     4
  [ 5] .rodata           PROGBITS         0000000000000000  000000a0
       0000000000000005  0000000000000000   A       0     0     1
  [ 6] .comment          PROGBITS         0000000000000000  000000a5
       0000000000000036  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  000000db
       0000000000000000  0000000000000000           0     0     1
  [ 8] .eh_frame         PROGBITS         0000000000000000  000000e0
       0000000000000058  0000000000000000   A       0     0     8
  [ 9] .rela.eh_frame    RELA             0000000000000000  00000398
       0000000000000030  0000000000000018   I      11     8     8
  [10] .shstrtab         STRTAB           0000000000000000  000003c8
       0000000000000061  0000000000000000           0     0     1
  [11] .symtab           SYMTAB           0000000000000000  00000138
       0000000000000180  0000000000000018          12    11     8
  [12] .strtab           STRTAB           0000000000000000  000002b8
       0000000000000066  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

段表描述符的结构体定义如下:

typedef struct
{
  Elf64_Word	sh_name;		/* Section name (string tbl index) */	// 4 Bytes
  Elf64_Word	sh_type;		/* Section type */						// 4 Bytes
  Elf64_Xword	sh_flags;		/* Section flags */						// 8 Bytes
  Elf64_Addr	sh_addr;		/* Section virtual addr at execution */	// 8 Bytes
  Elf64_Off		sh_offset;		/* Section file offset */				// 8 Bytes
  Elf64_Xword	sh_size;		/* Section size in bytes */				// 8 Bytes
  Elf64_Word	sh_link;		/* Link to another section */			// 4 Bytes
  Elf64_Word	sh_info;		/* Additional section information */		// 4 Bytes
  Elf64_Xword	sh_addralign;	/* Section alignment */					// 8 Bytes
  Elf64_Xword	sh_entsize;		/* Entry size if section holds table */	// 8 Bytes
} Elf64_Shdr;
  • sh_name: 表示段的名字,对应于段表中的Name字段。该值表示段名字符串在.shstrtab中的偏移量。
  • sh_type: 表示段的类型,对应于段表中的Type字段。具体值表示的含义参考elf.h文件中以SHT_开头定义的宏。
  • sh_flags: 表示段的标志位,对应于段表中的Flags字段。具体值表示的含义可以参考打印出段表末尾的Key to Flagself.h文件中以SHF_开头定义的宏。
  • sh_addr: 表示段的虚拟地址,对应于段表中的Address字段。表示如果该段可以被加载,则该值就表示该段被加载后在进程地址空间中的虚拟地址。否则该值为0。
  • sh_offset: 表示段在文件中的偏移位置,对应于段表中的Offset字段。如果该段在文件中没有内容,则该值没有意义。
  • sh_size: 表示段的长度,占用多少字节,对应于段表中的Size字段。这里有些段例外,因为有些段在文件中不占用空间,加载时才占用空间,所以这里的长度也就是加载时需要占用的字节数,例如.bss段。
  • sh_link和sh_info: 表示段的链接信息,对应于段表中的LinkInfo字段。如果段的类型是与链接(不论是静态链接还是动态链接)有关的,那么其表示含义见下表:
sh_typesh_linksh_info
SHT_DYNAMIC该段所使用的字符串表在段表中的下标0
SHT_HASH该段所使用的符号表在段表中的下标0
SHT_REL该段所使用的相应符号表在段表中的下标该重定位表所使用的段在段表中的下标
SHT_RELA该段所使用的相应符号表在段表中的下标该重定位表所使用的段在段表中的下标
SHT_SYMTAB操作系统相关的操作系统相关的
SHT_DYNSYM操作系统相关的操作系统相关的
otherSHN_UNDEF0
  • sh_addralign: 段地址对齐信息,对应段表中的Align字段。
  • sh_entsize: 项的长度,对应段表中的EntSize字段。有些段包含了一些固定大小的项,例如符号表(这里符号表的项的长度是0x18,也就是符号中每个元素所占的字节数是24个字节,刚好是符号结构体的大小)。如果该值为0,则表示不包含固定大小的项。

3 其他

3.1 可执行文件格式演化

在这里插入图片描述

  • 可执行文件格式:Windows下的PE-Portable Executable,例如.exe文件;Linux下的ELF-Executable linkable format,例如/bin/bash文件
  • 可重定位文件(Relocatable File): .o(Linux) .obj(Windows)
  • 共享目标文件(Shared Object File): .so(Linux) .dll(Windows)
  • 核心转储文件(Core Dump File): Linux下的core dump
  • 不仅可执行文件按照可执行文件格式存储,目标文件,动态链接库和静态链接库都按照可执行文件格式存储。所以这些文件都可以看成一类类型的文件:Windows(PE-COFF格式),Linux(ELF),Intel/Microsoft(OMF),Unix(a.out),MS-DOS(.COM);静态链接库稍有不同,只是多个目标文件打成了一个包。
  • COFF的贡献:在目标文件中引入了段的机制,使不同的目标
    文件可以拥有不同数量和不同类型的段;还定义了调试数据格式。
    通过file命令来查看文件属于什么类型:
$ file a.out 
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9b4b914409d190840aebf0a1560de29826d89898, not stripped
$ file SimpleSection.o
SimpleSection.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
$ file SimpleSection.c
SimpleSection.c: C source, ASCII text

3.2 可执行文件结构概述

可执行文件采用了程序指令(代码段)和程序数据(.data和.bss)分离,这样有以下几点好处:
1、程序装载后,数据和指令分别映射到两个虚存区域。并且有不同的属性可以避免指令被改写
2、分离有利于提高程序的局部性,可以提高CPU的缓存命中率。因为CPU的缓存一般也被设计为数据缓存和指令缓存
3、当运行该程序的多个副本时,可以共享指令和只读数据,节省内存

3.3 特殊符号

特殊符号定义在ld链接器的链接脚本中,用户可以声明并使用它们,链接器会在程序最终链接成可执行文件的时候将其解析成正确的值,注意,只有使用ld链接器生成最终可执行文件的时候这些符号才会存在,这些符号的值都是程序被装载时的虚拟地址。

  • __executable_start: 程序起始地址,是程序最开始的地址,不是入口地址
  • __etext或_etext或etext:代码段结束地址,也就是代码段最末尾的地址
  • _edata或edata:数据段结束地址,即数据段最末尾的地址
  • _end或end:程序结束地址

可以使用下面的代码来查看这些符号的值:

/*
 * SpecialSymbol.c
 */
 
#include <stdio.h>

extern char __executable_start[];
extern char __etext[], _etext[], etext[];
extern char _edata[], edata[];
extern char _end[], end[];

int main(int argc, char **argv)
{
	printf("Executable start addr: %p\n", __executable_start);
	printf("Text end addr: %p %p %p\n", __etext, _etext, etext);
	printf("Data end addr: %p %p\n", _edata, edata);
	printf("Executable end addr: %p %p\n", _end, end);
	return 0;
}

运行结果如下:

$ gcc SpecialSymbol.c -o SpecialSymbol
samsara@samsara-VirtualBox-ubuntu-16:/mnt/hgft/c/link-load-run/SimpleSection$ ./SpecialSymbol
Executable start addr: 0x400000
Text end addr: 0x40061d 0x40061d 0x40061d
Data end addr: 0x601038 0x601038
Executable end addr: 0x601040 0x601040

3.4 符号修饰和函数签名

为了减少多种语言目标文件之间的符号冲突:

  • C 语言源代码文件中所有全局的变量和函数经过编译以后,相对应的符号名前加上下划线_
  • Fortran 语言的源代码文件经过编译后,所有的符号名前加上_,后面也加上_

现在 linux 下的 gcc 编译器默认情况下去掉了 C 语言符号前的 _,windows 下 Visual C++ 还会保持C 语言符号前的_,windows 下的 GCC(cygwin,mingw) 也会加 _,gcc 可以通过参数选项-fleading-underscore-fno-leading-underscore来打开或关闭是否在 C 语言符号前加_

  • C++ 符号修饰:不仅用于函数,也用于全局变量或者静态变量(变量的类型并没有加入到修饰后的名称中)
  • 函数签名:包含一个函数的信息,包括函数名和参数类型,所在的类和名称空间及其他信息。每个函数签名对应一个修饰后的名称。

GCC 中基本 C++ 名称修饰方法:_Z开头;对于嵌套的名字(名称空间或类)后面紧跟 N;然后是各个名称空间和类,函数的名字,每个名字前是名字字符串长度,再以 E结尾;参数列表紧跟在 'E'后面。具体可以参考 GCC 的名称修饰标准。例如:N::C::func(int)修饰后名字为_ZN1N1C4funcEi

$ c++filt _ZN1N1C4funcEi
N::C::func(int)

Visual C++ 编译器的修饰规则与 GCC 不一样;Microsoft 提供 UnDecorateSymbolName() 来将修饰后名称转换成函数签名。

由于不同编译器采用不同的名称修饰方法,导致不同编译器编译产生的目标文件无法正常相互连接,这也是导致不同编译器之间不能互相操作的主要原因之一。

3.5 extern “C”

C++ 为了与 C 兼容,extern "C"用来声明或者定义一个 C 符号,C++ 编译器会将在大括号内部的代码当做 C 语言代码处理;这个应该主要是为了避免C++的名称修饰。

extern "C" {
int func(int);
int var;
}

extern "C" int func(int);
extern "C" int var;

C 语言不支持extern "C"语法,所以一般需要使用 C++ 的宏__cplusplus

3.6 强符号与弱符号

对于 C/C++ 语言来说编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号
可以使用 GCC 的__attribute__((weak))来定义任何一个强符号为弱符号。
强符号与弱符号都是针对定义来说的,不针对引用

针对强弱符号,链接器的处理规则:(可以使用c/link-load-run/StrongWeakSymbol来测试)

  • 不允许强符号被多层次定义;如果有多个强符号定义,则链接器报符号重复定义错误
  • 如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号
  • 如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间最大的一个(比如intfloat,选择float)。
/*
 * main.c
 *
 * gcc -o main main.c ref.c
*/

#include <stdio.h>

int global;

void ref_printf(void);

int main(int argc, char **argv)
{
	ref_printf();
	printf("main global = %d\n", global);
}

/*
 * ref.c
 */
#include <stdio.h>

int global = 12;

void ref_printf(void)
{
	printf("ref global = %d\n", global);
}

运行结果如下:

$ gcc -o main main.c ref.c
$ ./main
ref global = 12
main global = 12

3.7 强引用与弱引用

参考:https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes

  • 链接器处理强引用时,如果符号未定义,则报错
  • 链接器处理弱引用时,如果符号有定义,则链接器将该符号的引用决议;如果符号未被定义,链接器对于该引用不报错,而是用一个指定的值,有机会需要将这个再研究一下

3.8 strip

GCC 编译时加上-g参数,编译器就会在产生的目标文件里面加上调试信息。可以用strip命令去掉文件中的调试信息,该命令还会去掉符号段,普通字符串段,重定位段。

参考:https://docs.oracle.com/cd/E24847_01/html/E22196/chapter6-93046.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值