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

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 Flagself.h文件中以SHF_开头定义的宏。
  • sh_addr: 表示段的虚拟地址,对应于段表中的Address字段。表示如果该段可以被加载,则该值就表示该段被加载后在进程地址空间中的虚拟地址。否则该值为0。
  • sh_offset: 表示段在文件中的偏移位置,对应于段表中的Offset字段。如果该段在文件中没有内容,则该值没有意义。
  • sh_size: 表示段的长度,占用多少字节,对应于段表中的Size字段。这里有些段例外,因为有些段在文件中不占用空间,加载时才占用空间,所以这里的长度也就是加载时需要占用的字节数,例如.bss段。
  • sh_link和sh_info: 表示段的链接信息,对应于段表中的LinkInfo字段。如果段的类型是与链接(不论是静态链接还是动态链接)有关的,那么其表示含义见下表:
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来测试)

  • 不允许强符号被多层次定义;如果有多个强符号定义,则链接器报符号重复定义错误
  • 如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号
  • 如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间最大的一个(比如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

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

  1. activiti7(二):画业务流程图的工具以及bpmn文件内容解析

    上一篇我们学习了BPMN2.0规范    链接    ,知道了BPMN2.0规范是为了帮助我们规范的画流程图的,那么到底应该怎么画呢?这里给大家介绍一些遵循BPMN2.0规范的画图工具. 说明:业务流 ...

  2. bam文件读取_SAM/BAM 格式文件内容解析

    一.首先需要知道以下几个知识点: 1.1-based coordinate system A coordinate system where the rst base of a sequence is ...

  3. Dex文件内容解析APK相关信息

    Dex文件格式 我们都知道Android项目在构建的时候,会将class文件的jar包通过dx工具将其转化成dex文件,目的是将所有的class文件整合到一个dex文件中,这样的目的是降低冗余,因为每 ...

  4. python读取txt文件为字典username_如何使用txt和ids将.txt文件内容解析为python中的字典?...

    下面是文本文件内容,没有文本数据的标题或引号.我如何在字典中解析这些?我的文件中的每条记录都在新行上面.下面是我的文本文件内容:B00308CJ12 Bulletproof Salesman (200 ...

  5. 10046 trace 文件内容解析

    使用10046 trace跟踪SQL 的方法,请参考文章  https://blog.csdn.net/u010692693/article/details/75765958 生成的trace文件   ...

  6. 链接详解--多目标文件的链接

    注:1. 可用nm查看文件符号表. 2. 可用readelf -a main查看elf格式文件. 3. bss段:未初始化的数据,block storage start,或better save ap ...

  7. filebeat相关registry文件内容解析

    filebeat的registry文件中存放的是被采集的所有日志的相关信息. linux中registry中一条日志记录的内容如下 {"source":"/var/log ...

  8. DASH MPD 文件内容解析

    拿个MPD内容举个例子: <?xml version="1.0" encoding="utf-8"?> <!-- Created with B ...

  9. 【看表情包学Linux】man 手册 | 复制文件或目录 | 移动文件和重命名操作 | 查看目标文件内容

最新文章

  1. python数据库学习--Mysql
  2. 支撑亿级用户“刷手机”​,百度Feed流背后的新技术装备有多牛?
  3. 网络视频会议整体解决方案
  4. ConstraintLayout 不能作为activity的根节点,否则fragment显示不出来
  5. python的内置对象有哪些、列举说明_Python内置对象汇总
  6. socket编程实现回声客户端
  7. SMPP Java示例(客户端)
  8. GIPS语音编解码器家族
  9. Java、JS、OC、Flutter的Base64编码和解码
  10. RabbitMQ 入门 Helloworld
  11. 高中同窗被叫“码子”的!如今转行软件测试,不止是年轻,还有拼搏的勇气
  12. 平凡的故事:年轻开发者的那些伤心事
  13. Ext 介绍入门之 Templates(模板)
  14. Word 2013新建文档默认使用自己设置的样式
  15. 2023秋招大厂经典面试题及答案整理归纳(141-160)校招必看
  16. 准备半年,面试2个月,上岸快手拿个35K应该不算高吧?
  17. 怎么定位html的坐标,css怎么定位图片的位置?
  18. DPI和分辨率的转换
  19. ASPNet请求处理机制初步探索之旅Part2核心
  20. winmail的安装及使用说明流程

热门文章

  1. whatsapp逆向协议--漏洞分析
  2. 首届AAAI/ACM SIGAI博士论文奖公布, 清华姚班毕业、MIT学霸吴佳俊获奖
  3. SAP-PS-01-001预算类型
  4. 线段树 java_线段树合并:从入门到放弃(示例代码)
  5. html5控制字体样式,HTML5之CSS-网页文本美化-字体样式
  6. 电动汽车会是新的智能手机吗?
  7. 手动更新(rpi-update)树莓派固件
  8. 苹果将要发布128GB的iPad产品售价799美元
  9. 前端,新增DIV,自增html片段代码实现,append、attr等方法
  10. c语言向上取整计算方法