链接/装载/运行(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 解析目标文件
具体这部分二进制都代表那些内容,见下表:
地址区间 | 描述 |
---|---|
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.oSimpleSection.o: file format elf64-x86-64Sections:
Idx Name Size VMA LMA File off Algn0 .text 00000055 0000000000000000 0000000000000000 00000040 2**0CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE1 .data 00000008 0000000000000000 0000000000000000 00000098 2**2CONTENTS, ALLOC, LOAD, DATA2 .bss 00000004 0000000000000000 0000000000000000 000000a0 2**2ALLOC3 .rodata 00000005 0000000000000000 0000000000000000 000000a0 2**0CONTENTS, ALLOC, LOAD, READONLY, DATA4 .comment 00000036 0000000000000000 0000000000000000 000000a5 2**0CONTENTS, READONLY5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 000000db 2**0CONTENTS, READONLY6 .eh_frame 00000058 0000000000000000 0000000000000000 000000e0 2**3CONTENTS, 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: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: REL (Relocatable file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x0Start of program headers: 0 (bytes into file)Start of section headers: 1072 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 0 (bytes)Number of program headers: 0Size of section headers: 64 (bytes)Number of section headers: 13Section header string table index: 10
文件头的结构体定义如下:
typedef struct
{unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ // 16 BytesElf64_Half e_type; /* Object file type */ // 2 BytesElf64_Half e_machine; /* Architecture */ // 2 BytesElf64_Word e_version; /* Object file version */ // 4 BytesElf64_Addr e_entry; /* Entry point virtual address */ // 8 BytesElf64_Off e_phoff; /* Program header table file offset */ // 8 BytesElf64_Off e_shoff; /* Section header table file offset */ // 8 BytesElf64_Word e_flags; /* Processor-specific flags */ // 4 BytesElf64_Half e_ehsize; /* ELF header size in bytes */ // 2 BytesElf64_Half e_phentsize; /* Program header table entry size */ // 2 BytesElf64_Half e_phnum; /* Program header table entry count */ // 2 BytesElf64_Half e_shentsize; /* Section header table entry size */ // 2 BytesElf64_Half e_shnum; /* Section header table entry count */ // 2 BytesElf64_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.oSimpleSection.o: file format elf64-x86-64Contents 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 2010030 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 %rbp1: 48 89 e5 mov %rsp,%rbp4: 48 83 ec 10 sub $0x10,%rsp8: 89 7d fc mov %edi,-0x4(%rbp)b: 8b 45 fc mov -0x4(%rbp),%eaxe: 89 c6 mov %eax,%esi10: bf 00 00 00 00 mov $0x0,%edi15: b8 00 00 00 00 mov $0x0,%eax1a: e8 00 00 00 00 callq 1f <func1+0x1f>1f: 90 nop20: c9 leaveq 21: c3 retq 0000000000000022 <main>:22: 55 push %rbp23: 48 89 e5 mov %rsp,%rbp26: 48 83 ec 10 sub $0x10,%rsp2a: 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,%edx3f: 8b 45 f8 mov -0x8(%rbp),%eax42: 01 c2 add %eax,%edx44: 8b 45 fc mov -0x4(%rbp),%eax47: 01 d0 add %edx,%eax49: 89 c7 mov %eax,%edi4b: e8 00 00 00 00 callq 50 <main+0x2e>50: 8b 45 f8 mov -0x8(%rbp),%eax53: 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 Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS SimpleSection.c2: 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.18407: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 static_var2.18418: 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_var12: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM global_uninit_var13: 0000000000000000 34 FUNC GLOBAL DEFAULT 1 func114: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf15: 0000000000000022 51 FUNC GLOBAL DEFAULT 1 main
符号表的结构体:
typedef struct
{Elf64_Word st_name; /* Symbol name (string tbl index) */ // 4 Bytesunsigned char st_info; /* Symbol type and binding */ // 1 Bytesunsigned char st_other; /* Symbol visibility */ // 1 BytesElf64_Section st_shndx; /* Section index */ // 2 BytesElf64_Addr st_value; /* Symbol value */ // 8 BytesElf64_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.oRelocation 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 - 4Relocation 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.oimage.o: file format elf32-i386Sections:
Idx Name Size VMA LMA File off Algn0 .data 001c5d72 00000000 00000000 00000034 2**0CONTENTS, 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 OffsetSize EntSize Flags Link Info Align[ 0] NULL 0000000000000000 000000000000000000000000 0000000000000000 0 0 0[ 1] .text PROGBITS 0000000000000000 000000400000000000000055 0000000000000000 AX 0 0 1[ 2] .rela.text RELA 0000000000000000 000003200000000000000078 0000000000000018 I 11 1 8[ 3] .data PROGBITS 0000000000000000 000000980000000000000008 0000000000000000 WA 0 0 4[ 4] .bss NOBITS 0000000000000000 000000a00000000000000004 0000000000000000 WA 0 0 4[ 5] .rodata PROGBITS 0000000000000000 000000a00000000000000005 0000000000000000 A 0 0 1[ 6] .comment PROGBITS 0000000000000000 000000a50000000000000036 0000000000000001 MS 0 0 1[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000db0000000000000000 0000000000000000 0 0 1[ 8] .eh_frame PROGBITS 0000000000000000 000000e00000000000000058 0000000000000000 A 0 0 8[ 9] .rela.eh_frame RELA 0000000000000000 000003980000000000000030 0000000000000018 I 11 8 8[10] .shstrtab STRTAB 0000000000000000 000003c80000000000000061 0000000000000000 0 0 1[11] .symtab SYMTAB 0000000000000000 000001380000000000000180 0000000000000018 12 11 8[12] .strtab STRTAB 0000000000000000 000002b80000000000000066 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 BytesElf64_Word sh_type; /* Section type */ // 4 BytesElf64_Xword sh_flags; /* Section flags */ // 8 BytesElf64_Addr sh_addr; /* Section virtual addr at execution */ // 8 BytesElf64_Off sh_offset; /* Section file offset */ // 8 BytesElf64_Xword sh_size; /* Section size in bytes */ // 8 BytesElf64_Word sh_link; /* Link to another section */ // 4 BytesElf64_Word sh_info; /* Additional section information */ // 4 BytesElf64_Xword sh_addralign; /* Section alignment */ // 8 BytesElf64_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 Flags
和elf.h
文件中以SHF_
开头定义的宏。 - sh_addr: 表示段的虚拟地址,对应于段表中的
Address
字段。表示如果该段可以被加载,则该值就表示该段被加载后在进程地址空间中的虚拟地址。否则该值为0。 - sh_offset: 表示段在文件中的偏移位置,对应于段表中的
Offset
字段。如果该段在文件中没有内容,则该值没有意义。 - sh_size: 表示段的长度,占用多少字节,对应于段表中的
Size
字段。这里有些段例外,因为有些段在文件中不占用空间,加载时才占用空间,所以这里的长度也就是加载时需要占用的字节数,例如.bss
段。 - sh_link和sh_info: 表示段的链接信息,对应于段表中的
Link
和Info
字段。如果段的类型是与链接(不论是静态链接还是动态链接)有关的,那么其表示含义见下表:
sh_type | sh_link | sh_info |
---|---|---|
SHT_DYNAMIC | 该段所使用的字符串表在段表中的下标 | 0 |
SHT_HASH | 该段所使用的符号表在段表中的下标 | 0 |
SHT_REL | 该段所使用的相应符号表在段表中的下标 | 该重定位表所使用的段在段表中的下标 |
SHT_RELA | 该段所使用的相应符号表在段表中的下标 | 该重定位表所使用的段在段表中的下标 |
SHT_SYMTAB | 操作系统相关的 | 操作系统相关的 |
SHT_DYNSYM | 操作系统相关的 | 操作系统相关的 |
other | SHN_UNDEF | 0 |
- 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来测试)
- 不允许强符号被多层次定义;如果有多个强符号定义,则链接器报符号重复定义错误
- 如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号
- 如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间最大的一个(比如
int
与float
,选择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
链接/装载/运行(3)-目标文件内容解析相关推荐
- activiti7(二):画业务流程图的工具以及bpmn文件内容解析
上一篇我们学习了BPMN2.0规范 链接 ,知道了BPMN2.0规范是为了帮助我们规范的画流程图的,那么到底应该怎么画呢?这里给大家介绍一些遵循BPMN2.0规范的画图工具. 说明:业务流 ...
- bam文件读取_SAM/BAM 格式文件内容解析
一.首先需要知道以下几个知识点: 1.1-based coordinate system A coordinate system where the rst base of a sequence is ...
- Dex文件内容解析APK相关信息
Dex文件格式 我们都知道Android项目在构建的时候,会将class文件的jar包通过dx工具将其转化成dex文件,目的是将所有的class文件整合到一个dex文件中,这样的目的是降低冗余,因为每 ...
- python读取txt文件为字典username_如何使用txt和ids将.txt文件内容解析为python中的字典?...
下面是文本文件内容,没有文本数据的标题或引号.我如何在字典中解析这些?我的文件中的每条记录都在新行上面.下面是我的文本文件内容:B00308CJ12 Bulletproof Salesman (200 ...
- 10046 trace 文件内容解析
使用10046 trace跟踪SQL 的方法,请参考文章 https://blog.csdn.net/u010692693/article/details/75765958 生成的trace文件 ...
- 链接详解--多目标文件的链接
注:1. 可用nm查看文件符号表. 2. 可用readelf -a main查看elf格式文件. 3. bss段:未初始化的数据,block storage start,或better save ap ...
- filebeat相关registry文件内容解析
filebeat的registry文件中存放的是被采集的所有日志的相关信息. linux中registry中一条日志记录的内容如下 {"source":"/var/log ...
- DASH MPD 文件内容解析
拿个MPD内容举个例子: <?xml version="1.0" encoding="utf-8"?> <!-- Created with B ...
- 【看表情包学Linux】man 手册 | 复制文件或目录 | 移动文件和重命名操作 | 查看目标文件内容
最新文章
- python数据库学习--Mysql
- 支撑亿级用户“刷手机”​,百度Feed流背后的新技术装备有多牛?
- 网络视频会议整体解决方案
- ConstraintLayout 不能作为activity的根节点,否则fragment显示不出来
- python的内置对象有哪些、列举说明_Python内置对象汇总
- socket编程实现回声客户端
- SMPP Java示例(客户端)
- GIPS语音编解码器家族
- Java、JS、OC、Flutter的Base64编码和解码
- RabbitMQ 入门 Helloworld
- 高中同窗被叫“码子”的!如今转行软件测试,不止是年轻,还有拼搏的勇气
- 平凡的故事:年轻开发者的那些伤心事
- Ext 介绍入门之 Templates(模板)
- Word 2013新建文档默认使用自己设置的样式
- 2023秋招大厂经典面试题及答案整理归纳(141-160)校招必看
- 准备半年,面试2个月,上岸快手拿个35K应该不算高吧?
- 怎么定位html的坐标,css怎么定位图片的位置?
- DPI和分辨率的转换
- ASPNet请求处理机制初步探索之旅Part2核心
- winmail的安装及使用说明流程