计算机系统篇之链接:目标文件

Posted csstormq

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了计算机系统篇之链接:目标文件相关的知识,希望对你有一定的参考价值。

计算机系统篇之链接(2):目标文件

Author:stormQ

Saturday, 21. December 2019 11:08AM


目标文件类型

从技术角度来看,目标文件就是一个字节序列(Technically, object file is a sequence of bytes)。

目标文件可以分为三类:可重定位目标文件、可执行目标文件和可共享目标文件(可共享目标文件是一种特殊的可重定位目标文件)。

三者之间的关系:可重定位目标文件和可共享目标文件是用于生成可执行目标文件的。

目标文件类型生成者是否可以直接被加载到内存中执行Linux 中的目标文件后缀(习惯上)
可重定位目标文件汇编器不可以.o
可执行目标文件链接器可以无后缀
可共享目标文件链接器不可以.so

注:对于 Linux 中的目标文件来说,从技术角度讲,文件后缀可以是任意的或无后缀,习惯上的文件后缀只是为了便于区别。

1)如何生成可重定位目标文件(On X86-64 Linux):

$ g++ -c main.cpp -o main.o
# 或 g++ -c main.cpp
# 这两个命令都会生成名称为 main.o 的可重定位目标文件

注:-c选项表示执行编译和汇编过程,但不执行链接过程(Compile or assemble the source files, but do not link.)。

查看 main.cpp 的内容:

$ cat main.cpp
int main()

    sum(1, 2);
    return 0;

2)如何生成可执行目标文件(On X86-64 Linux):

# 默认生成的可执行目标文件为 a.out
$ g++ main.cpp
# 指定生成的可执行目标文件为 main
$ g++ main.cpp -o main

3)如何生成可共享目标文件(On X86-64 Linux):

# 默认生成的可共享目标文件为 a.out
$ g++ -shared sum.cpp
# 指定生成的可共享目标文件为 sum.so
$ g++ -shared sum.cpp -o sum.so

注:-shared选项表示生成可共享目标文件,默认名称也为 a.out。

注意: 含有main()函数的源文件不能用于成可共享目标文件,但可以用于生成可重定位目标文件(即 .o 文件)。

查看 sum.cpp 的内容:

$ cat sum.cpp 
int sum(int a, int b)

  return a + b;


目标文件格式

目标文件格式是指目标文件的组织方式。目标文件格式因系统而异。

系统目标文件格式
WindowsPE(Portable Executable)
Mac OS-XMach-O(Mach Object)
X86-64 Linux 和 UnixELF-64(Executable and Linkable Format)
aarch64 Linux 和 UnixELF-64

注:aarch64 表示 ARM 64-bit 系统。

1)如何生成目标文件格式为PE的可执行目标文件(On X86-64 Linux):

$ /usr/bin/x86_64-w64-mingw32-g++ -o main_w64.exe main.cpp

在 Windows 64-bit 系统上的命令行窗口中执行 main_w64.exe 程序:

C:\\Users\\x\\Desktop>main_w64.exe
g_val_1=0x0, g_val_2=0x10

查看 main.cpp 的内容:

$ cat main.cpp 
#include <stdio.h>

int g_val_1;
int g_val_2 = 16;
const int g_val_3 = 2;

int main()

  printf("g_val_1=0x%x, g_val_2=0x%x\\n", g_val_1, g_val_2);
  return 0;

注:要在 X86-64 Linux 系统上生成在 Windows 64-bit 系统上运行的可执行目标文件,需要使用交叉编译器,比如:g++-mingw-w64(GNU C++ compiler for MinGW-w64)。安装命令为sudo apt-get install g++-mingw-w64

2)如何生成目标文件格式为ELF-64的可重定位目标文件(On X86-64 Linux):

$ g++ -c sum.cpp -o sum.o

查看 sum.cpp 的内容:

$ cat sum.cpp
extern int g_val;
int g_val_1 = 0;
int g_val_2 = 1;
int g_val_3;

int sum(int a, int b)

  static int val_1;
  static int val_2 = 0;
  static int val_3 = 1;
  static int val_4 = 0;
  static int val_5 = 2;
  const static int val_6 = 0;
  return a + b;

注:在 X86-64 Linux 上,g++ 默认的目标文件格式为 ELF-64(对应的缺省编译选项为-m64)。如果要在 X86-64 Linux 上使用 g++ 生成 ELF-32 的目标文件格式,需要添加-m32选项,如下所示:

$ g++ -c sum.cpp -o sum_elf32.o -m32
$ readelf -h sum_elf32.o
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          740 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
  Number of section headers:         12
  Section header string table index: 9

从上面的打印结果中可以看出,可重定位目标文件 sum_elf32.o 的目标文件格式为 ELF32。


可重定位目标文件(ELF-64 格式)

典型的 ELF-64 可重定位目标文件格式:

组成部分描述如何查看
ELF header描述生成该目标文件的系统的字大小和字节顺序、目标文件类型、机器类型等信息。
  • readelf -h <object file>
.text已编译程序的机器代码。
  • 方式1:objdump -d <object file>(只输出汇编)
  • 方式2:objdump -S <object file>(输出汇编和对应的源码。需要编译时加-g选项,才会打印源码。)
.rodata只读数据,比如:常量字符串、带 const 修饰的全局变量和静态变量等。
.data已初始化的且初始值非0的全局变量和静态变量。
.bss未初始化的或初始值为0的全局变量和静态变量。
.symtab一个符号表,它存放着在目标文件中定义和引用的函数和全局变量、静态变量的信息。
  • readelf -s <object file>(注:删除目标文件中 .symtab section 的命令为:strip <object file>
.rel<name><name> section 的可重定位信息。
  • readelf -r <object file>
.debug一个调试符号表,其条目是程序中定义的局部变量和类型定义(typedefs),程序中定义和引用的全局变量,以及原始的 C 源文件。(只有带 -g 编译时才会产生)
  • 方式1:readelf --debug-dump <object file>
  • 方式2:objdump -g <object file>
.comment版本控制信息。
  • readelf -p .comment <object file>
.shstrtab一个字符串表,其内容包括所有 section 的名称。
  • readelf -p .shstrtab <object file>
.strtab一个字符串表,其内容包括所有符号的名称。
  • readelf -p .strtab <object file> (注:删除目标文件中 .strtab section 的命令为:strip <object file>
Section header table描述目标文件的所有节(sections)的位置和大小等信息。
  • readelf -S <object file>
  • 注:

    • readelf 命令中缩写含义:-h--file-header的缩写,-s--symbols的缩写,-r--relocs的缩写,-p--string-dump的缩写,-S--section-headers的缩写。

    • objdump 命令中缩写含义:-d--disassemble的缩写,-S--source的缩写,-g--debugging的缩写。

1)查看可重定位目标文件的ELF Header

$ readelf -h sum.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:          928 (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:         12
  Section header string table index: 9
  • 注:

    • Magic是 ELF Header 结构体中的第 1 个字段,占用 16 字节。

      前 4 个字节用于标识该文件是 ELF 格式的目标文件,值(十六进制)固定为:7f(0x7f)、45(ASCII 码中大写字母 E 的十六机制表示)、4c(ASCII 码中大写字母 L 的十六机制表示)、46(ASCII 码中大写字母 F 的十六机制表示)。
      Magic[4] 表示目标文件类别,可选项:1(ELFCLASS32,32 位的 ELF 文件)、2(ELFCLASS64,64 位的 ELF 文件)。
      Magic[5] 表示目标文件采用的字节序,可选项:1(ELFDATA2LSB,小端)、2(ELFDATA2MSB,大端)。
      Magic[6] 表示目标文件格式的版本,可选项:1(当前版本)。
      Magic[7] 表示操作系统及 ABI,可选项:0(ELFOSABI_SYSV,System V ABI)、1(ELFOSABI_HPUX,HP-UX operating system)、255(ELFOSABI_STANDALONE,Embedded application)。

    • Type是 ELF Header 结构体中的第 2 个字段,占用 2 字节,表示目标文件类型。常用可选项:0(ET_NONE,没有文件类型)、1(ET_REL,可重定位目标文件)、2(ET_EXEC,可执行目标文件)、3(ET_DYN,可共享目标文件)、4(ET_CORE,core 文件)。

    • Machine是 ELF Header 结构体中的第 3 个字段,占用 2 字节,表示目标机器的体系结构。

    • Version是 ELF Header 结构体中的第 4 个字段,占用 4 字节,表示目标文件格式的版本,可选项:1(当前版本)。

    • Entry point address是 ELF Header 结构体中的第 5 个字段,占用 8 字节,表示程序入口点的虚拟内存地址。0 表示没有程序入口点。

    • Start of program headers是 ELF Header 结构体中的第 6 个字段,占用 8 字节,表示 program header table 的起始位置在目标文件中的偏移量(字节)。

    • Start of section headers是 ELF Header 结构体中的第 7 个字段,占用 8 字节,表示 section header table 的起始位置在目标文件中的偏移量(字节)。

    • Flags是 ELF Header 结构体中的第 8 个字段,占用 4 字节,表示特定处理器中的标识。

    • Size of this header是 ELF Header 结构体中的第 9 个字段,占用 2 字节,表示 ELF Header 的大小(字节)。

    • Size of this program headers是 ELF Header 结构体中的第 10 个字段,占用 2 字节,表示 program header table 中每个条目的大小(字节),每个条目的大小是固定的。

    • Number of program headers是 ELF Header 结构体中的第 11 个字段,占用 2 字节,表示 program header table 中条目的数量。

    • Size of section headers是 ELF Header 结构体中的第 12 个字段,占用 2 字节,表示 section header table 中每个条目的大小(字节),每个条目的大小是固定的。

    • Number of section headers是 ELF Header 结构体中的第 13 个字段,占用 2 字节,表示 section header table 中条目的数量。

    • Section header string table index是 ELF Header 结构体中的第 14 个字段,占用 2 字节,表示 .strtab section 在 section header table 中的索引。0 表示没有 .strtab section。

2)查看可重定位目标文件的Section header table

$ readelf -S sum.o
There are 12 section headers, starting at offset 0x3a0:

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
       0000000000000014  0000000000000000  AX       0     0     1
  [ 2] .data             PROGBITS         0000000000000000  00000054
       000000000000000c  0000000000000000  WA       0     0     4
  [ 3] .bss              NOBITS           0000000000000000  00000060
       0000000000000014  0000000000000000  WA       0     0     4
  [ 4] .rodata           PROGBITS         0000000000000000  00000060
       0000000000000004  0000000000000000   A       0     0     4
  [ 5] .comment          PROGBITS         0000000000000000  00000064
       0000000000000033  0000000000000001  MS       0     0     1
  [ 6] .note.GNU-stack   PROGBITS         0000000000000000  00000097
       0000000000000000  0000000000000000           0     0     1
  [ 7] .eh_frame         PROGBITS         0000000000000000  00000098
       0000000000000038  0000000000000000   A       0     0     8
  [ 8] .rela.eh_frame    RELA             0000000000000000  00000328
       0000000000000018  0000000000000018   I      10     7     8
  [ 9] .shstrtab         STRTAB           0000000000000000  00000340
       000000000000005c  0000000000000000           0     0     1
  [10] .symtab           SYMTAB           0000000000000000  000000d0
       00000000000001c8  0000000000000018          11    15     8
  [11] .strtab           STRTAB           0000000000000000  00000298
       0000000000000090  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)
  • 注:

    • There are 12 section headers, starting at offset 0x3a0表示 section header table 中有 12 个条目,section header table 的起始位置在目标文件中的偏移量为 0x3a0 字节。

    • Name是 Section Header 结构体中的第 1 个字段,占用 4 字节,表示该 section 名称的起始位置在.shstrtabsection 中的偏移量(字节)。

    • Type是 Section Header 结构体中的第 2 个字段,占用 4 字节,表示 section 类型。常用可选项:0(SHT_NULL,表示未使用的 section header)、1(SHT_PROGBITS,表示该 section 包含由程序定义的信息)、2(SHT_SYMTAB,表示链接器符号表)、3(SHT_STRTAB,表示字符串表)、4(SHT_RELA,表示 “Rela” 类型的可重定位条目)、6(SHT_DYNAMIC,表示动态链接表)、8(SHT_NOBITS,表示未初始化的空间,不占用目标文件的任何空间)、9(SHT_REL,表示 “Rel” 类型的可重定位条目)、10(SHT_SHLIB,表示保留)、11(SHT_DYNSYM,表示动态加载的符号表)。

    • Flags是 Section Header 结构体中的第 3 个字段,占用 8 字节,表示 section 的属性。常用可选项:1(SHF_WRITE,简写为 W,表示该 section 中包含可写数据)、2(SHF_ALLOC,简写为 A,表示执行程序时会将该 section 加载到内存中)、4(SHF_EXECINSTR,简写为 X,表示该 section 中包含可执行的指令)。

    • Address是 Section Header 结构体中的第 4 个字段,占用 8 字节,表示该 section 的起始位置所对应的虚拟内存地址。0 表示该 section 不会被加载到内存中。

    • Offset是 Section Header 结构体中的第 5 个字段,占用 8 字节,表示该 section 的起始位置在目标文件中偏移量(字节)。

    • Size是 Section Header 结构体中的第 6 个字段,占用 8 字节,表示该 section 在目标文件中占用的空间大小(字节),除 SHT_NOBITS section 以外。

    • Link是 Section Header 结构体中的第 7 个字段,占用 4 字节,表示该 section 相关 section 的 section 索引。

    • Info是 Section Header 结构体中的第 8 个字段,占用 4 字节,表示该 section 的额外信息。

    • Align是 Section Header 结构体中的第 9 个字段,占用 8 字节,表示该 section 的内存对齐要求。这个字段的值必须是 2 的幂。

    • EntSize是 Section Header 结构体中的第 10 个字段,占用 8 字节,表示该 section 中每个条目的大小(字节)。0 表示该 section 不包含任何条目。

3)查看可重定位目标文件的.commentsection

$ readelf -p .comment sum.o

String dump of section '.comment':
  [     1]  GCC: (Ubuntu 6.5.0-2ubuntu1~16.04) 6.5.0 20181026

注:这里.commentsection 的第一个字节的值为 0x00(\\0),但不代表.commentsection 在其他类型的目标文件中第一个字节的值也为 0x00(\\0)。比如:在可执行目标文件中该 section 的第一个字节的值不为 0x00。

查看可执行目标文件main.commentsection 的内容:

$ readelf -p .comment main

String dump of section '.comment':
  [     0]  GCC: (Ubuntu 6.5.0-2ubuntu1~16.04) 6.5.0 20181026

4)查看可重定位目标文件的.shstrtabsection

$ readelf -p .shstrtab sum.o

String dump of section '.shstrtab':
  [     1]  .symtab
  [     9]  .strtab
  [    11]  .shstrtab
  [    1b]  .text
  [    21]  .data
  [    27]  .bss
  [    2c]  .rodata
  [    34]  .comment
  [    3d]  .note.GNU-stack
  [    4d]  .rela.eh_frame
  • 注:

    • []中的值表示 section 名称的起始位置在.shstrtabsection 中的偏移量(字节)。

    • .shstrtabsection 的第一个字节的值固定为 0,表示一个空的或者不存在的 section 名称。

    • .shstrtabsection 的 section 名称是以\\0(对应的 ASCII 码十六机制为 0x00)结尾的字符串。

注意: 可重定位目标文件sum.o.shstrtabsection 中未显式地包含.eh_framesection 的名称。从表面上看,这不符合“.shstrtabsection 的作用:一个字符串表,其内容包括所有 section 的名称”。实际上,.shstrtabsection 中隐式地包含.eh_framesection 的名称,即复用了.rela.eh_framesection 的后半部分的名称。

验证注释中的三条结论:

$ readelf -x .shstrtab sum.o

Hex dump of section '.shstrtab':
  0x00000000 002e7379 6d746162 002e7374 72746162 ..symtab..strtab
  0x00000010 002e7368 73747274 6162002e 74657874 ..shstrtab..text
  0x00000020 002e6461 7461002e 62737300 2e726f64 ..data..bss..rod
  0x00000030 61746100 2e636f6d 6d656e74 002e6e6f ata..comment..no
  0x00000040 74652e47 4e552d73 7461636b 002e7265 te.GNU-stack..re
  0x00000050 6c612e65 685f6672 616d6500          la.eh_frame.

最左侧一列为地址,后面的为 .shstrtabsection 中的内容。从上面结果中可以看出:

  • .shstrtabsection 的第一个字节的值为 0x00,即\\0。打印结果中用.表示这个字符。

  • 接下来的 8 字节的内容为:2e7379 6d746162 00。0x2e 对应的 ASCII 码为 .、0x73 对应的 ASCII 码为 s、0x79 对应的 ASCII 码为 y、0x6d 对应的 ASCII 码为 m、0x74 对应的 ASCII 码为 t、0x61 对应的 ASCII 码为 a、0x62 对应的 ASCII 码为 b、0x00 对应的 ASCII 码为 NULL。

5)查看可重定位目标文件的.strtabsection

$ readelf -p .strtab sum.o

String dump of section '.strtab':
  [     1]  sum.cpp
  [     9]  _ZZ3sumiiE5val_1
  [    1a]  _ZZ3sumiiE5val_2
  [    2b]  _ZZ3sumiiE5val_3
  [    3c]  _ZZ3sumiiE5val_4
  [    4d]  _ZZ3sumiiE5val_5
  [    5e]  _ZZ3sumiiE5val_6
  [    6f]  g_val_1
  [    77]  g_val_2
  [    7f]  g_val_3
  [    87]  _Z3sumii
  • 注:

    • []中的值表示符号名称的起始位置在.strtabsection 中的偏移量(字节)。

    • .strtabsection 的第一个字节的值固定为 0,表示一个空的或者不存在的符号名称。

    • .strtabsection 的符号名称是以\\0(对应的 ASCII 码十六机制为 0x00)结尾的字符串。

6)查看可重定位目标文件的.symtabsection,见 READELF 程序所打印的 .symtab section 及解释


符号表

每个目标文件(包括可重定位目标文件、可共享目标文件和可执行目标文件)中都有一个符号表(.symtab section),符号表中包含了被该目标文件定义和引用的符号的信息。

符号表(.symtab section)是由汇编器产生的,跟编译时是否添加-g选项无关。

1) 符号表中的符号可以分为三类:

符号类型定义者可见性哪些属于这类符号
内部定义的全局符号(Global Symbols)本目标文件本目标文件和其他目标文件都可见由本目标文件定义的非静态函数和非静态全局变量
引用外部的全局符号(External Symbols)其他目标文件本目标文件和其他目标文件都可见由本目标文件引用的但定义在其他目标文件中的非静态函数和非静态全局变量
内部定义的局部符号(Local Symbols)本目标文件仅本目标文件可见由本目标文件定义的静态函数、静态全局变量和静态局部变量

注:严格地讲,静态局部变量不仅对于其他目标文件不可见,而且对于本目标文件的其他函数也不可见。

2) READELF 程序所打印的.symtabsection 及解释

查看 sum.cpp 的内容:

$ cat sum.cpp 
extern int g_val;
int g_val_1 = 0;
int g_val_2 = 1;

int sum(int a, int b)

  static int val_1;
  static int val_2 = 0;
  static int val_3 = 1;
  static int val_4 = 0;
  static int val_5 = 2;
  const static int val_6 = 0;
  return a + b;

打印 sum.o 的.symtab section:

$ readelf -s sum.o

Symbol table '.symtab' contains 19 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS sum.cpp
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    4 _ZZ3sumiiE5val_1
     6: 0000000000000008     4 OBJECT  LOCAL  DEFAULT    4 _ZZ3sumiiE5val_2
     7: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    3 _ZZ3sumiiE5val_3
     8: 000000000000000c     4 OBJECT  LOCAL  DEFAULT    4 _ZZ3sumiiE5val_4
     9: 0000000000000008     4 OBJECT  LOCAL  DEFAULT    3 _ZZ3sumiiE5val_5
    10: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
    11: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    5 _ZZ3sumiiE5val_6
    12: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
    13: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
    14: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
    15: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 g_val_1
    16: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 g_val_2
    17: 0000000000000000    28 FUNC    GLOBAL DEFAULT    1 _Z3sumii
    18: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND g_val
  • 注:

    • 符号表的第一个条目被保留,并且该条目中的值必须全部为零。符号常量 STN_UNDEF 用于引用这个条目。

    • Num列,表示符号表条目在符号表中的索引,索引从 0 开始。

    • Name是 Symbol Table Entry 结构体中的第 1 个字段,占用 4 字节,表示符号名称的起始位置在.strtab section 中的偏移量(字节)。

    • BindType是 Symbol Table Entry 结构体中的第 2 个字段,占用 1 字节(Bind占用高四位,Type占用低四位)。

      Bind,表示符号所关联对象的的绑定属性(即符号的作用范围)。常见可选项:0(STB_LOCAL,仅定义该符号的目标文件可见)、1(STB_GLOBAL,所有目标文件可见)、2(STB_WEAK,全局作用域,但其优先级低于全局符号)。

      Type,表示符号所关联对象的类型。常见可选项:0(STT_NOTYPE,未指定类型,比如一个绝对符号)、1(STT_OBJECT,数据对象)、2(STT_FUNC,函数入口点)、3(STT_SECTION,符号与 section 相关联)、4(STT_FILE,与目标文件关联的源文件)。

    • Symbol Table Entry 结构体中的第 3 个字段作为保留字段,占用 1 字节。

    • Ndx是 Symbol Table Entry 结构体中的第 4 个字段,占用 2 字节,表示符号所关联对象位于哪个 section 。该值为 section 在 Section header table 中的索引,即readelf -S sum.o输出结果中该 section [Nr] 列的值(同一个 section 在不同的 .o 文件中对应的数字不一定相同)。其他可选项:UNDEF(表示未定义符号)、ABS(表示绝对符号)、COM(表示未初始化的全局变量)。

    • Value是 Symbol Table Entry 结构体中的第 5 个字段,占用 8 字节,表示符号所关联对象的地址。在可重定位目标文件(不包括可共享目标文件)中,该值表示符号所关联对象在其 section 中的偏移量(即符号所关联对象在其 section 中的起始地址)。在可执行目标文件和可共享目标文件中,该值表示符号所关联对象的绝对地址。

    • Size是 Symbol Table Entry 结构体中的第 6 个字段,占用 8 字节,表示符号所关联对象的大小,单位:字节。如果符号没有关联的大小或大小未知,则此字段的值为 0。

a)验证“在可重定位目标文件(不包括可共享目标文件)中,Value 列,表示符号在其 section 中的偏移量”

15: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 g_val_1
 5: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    4 _ZZ3sumiiE5val_1
 6: 0000000000000008     4 OBJECT  LOCAL  DEFAULT    4 _ZZ3sumiiE5val_2
 8: 000000000000000c     4 OBJECT  LOCAL  DEFAULT    4 _ZZ3sumiiE5val_4

表示的含义:在可重定位目标文件 sum.o 中,符号:g_val_1、_ZZ3sumiiE5val_1、_ZZ3sumiiE5val_2、_ZZ3sumiiE5val_4 都位于同一个 setion 中(该 section 在 Section header table 中的索引为 4),符号 g_val_1 所关联的对象在该 section 中占用的空间为 0000000000000000~0000000000000003(即该 section 中第一个 4 字节的值为 g_val_1),符号 _ZZ3sumiiE5val_1 所关联的对象在该 section 中占用的空间为 0000000000000004~0000000000000007(即该 section 中第二个 4 字节的值为 val_1),符号 _ZZ3sumiiE5val_2 所关联的对象在该 section 中占用的空间为 0000000000000008~000000000000000b(即该 section 中第三个 4 字节的值为 val_2),符号 _ZZ3sumiiE5val_4 所关联的对象在该 section 中占用的空间为 000000000000000c~000000000000000f(即该 section 中第四个 4 字节的值为 val_4)。

b)验证“Ndx 列,表示符号所关联对象位于哪个 section”

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

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
       000000000000001c  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000330
       0000000000000018  0000000000000018   I      11     1     8
  [ 3] .data             PROGBITS         0000000000000000  0000005c
       000000000000000c  0000000000000000  WA       0     0     4
  [ 4] .bss              NOBITS           0000000000000000  00000068
       0000000000000010  0000000000000000  WA       0     0     4
  [ 5] .rodata           PROGBITS         0000000000000000  00000068
       0000000000000004  0000000000000000   A       0     0     4
  [ 6] .comment          PROGBITS         0000000000000000  0000006c
       0000000000000033  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  0000009f
       0000000000000000  0000000000000000           0     0     1
  [ 8] .eh_frame         PROGBITS         0000000000000000  000000a0
       0000000000000038  0000000000000000   A       0     0     8
  [ 9] .rela.eh_frame    RELA             0000000000000000  00000348
       0000000000000018  0000000000000018   I      11     8     8
  [10] .shstrtab         STRTAB           0000000000000000  00000360
       0000000000000061  0000000000000000           0     0     1
  [11] .symtab           SYMTAB           0000000000000000  000000d8
       00000000000001c8  0000000000000018          12    15     8
  [12] .strtab           STRTAB           0000000000000000  000002a0
       000000000000008e  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)

全局变量 g_val1,静态变量 val_1、val_2、val_4 要么未初始化,要么初始值为 0。所以,这些变量会存放到.bss section 中。.bss section 在 Section header table 中的索引为 4([Nr] 列),与Ndx列的值相同。从而,验证了“readelf 输出结果中的Ndx列即为上面结果中Nr列的值,表示符号所关联对象所在的 section”。

c)验证“Name 列,表示符号在.strtab section 中的名称”

$ readelf sum.o -p .strtab

String dump of section '.strtab':
  [     1]  sum.cpp
  [     9]  _ZZ3sumiiE5val_1
  [    1a]  _ZZ3sumiiE5val_2
  [    2b]  _ZZ3sumiiE5val_3
  [    3c]  _ZZ3sumiiE5val_4
  [    4d]  _ZZ3sumiiE5val_5
  [    5e]  _ZZ3sumiiE5val_6
  [    6f]  g_val_1
  [    77]  g_val_2
  [    7f]  _Z3sumii
  [    88]  g_val

.strtab section 中可以看到符号名称。


可执行目标文件(ELF-64 格式)

在可执行目标文件和可共享目标文件中,section 被分组成 segment 以供加载。每个 segment 中包含的 sections 是连续的。

典型的 ELF-64 可执行目标文件格式:

组成部分描述如何查看属于哪个 Segment程序执行时是否加载到内存
ELF header描述生成该目标文件的系统的字大小和字节顺序、目标文件类型、机器类型等信息。
  • readelf -h <object file>
Code Segment
Program header table描述可执行目标文件中所有段(Segments)的位置和大小等信息。
  • 方式1:readelf -l <object file>
  • 方式2:objdump -p <object file>
Code Segment
.init该 section 中定义了一个名称为 _init 的函数,会被程序初始化代码调用。objdump -d <object file>Code Segment
.text已编译程序的机器代码。
  • 方式1:objdump -d <object file>(只输出汇编)
  • 方式2:objdump -S <object file>(输出汇编和对应的源码。需要编译时加-g选项,才会打印源码。)
Code Segment
.rodata只读数据,比如:常量字符串、带 const 修饰的全局变量和静态变量等。Code Segment
.data已初始化的且初始值非0的全局变量和静态变量。Data Segment
.bss未初始化的或初始值为0的全局变量和静态变量。Data Segment
.symtab一个符号表,它存放着在目标文件中定义和引用的函数和全局变量、静态变量的信息。
  • readelf -s <object file>(注:删除目标文件中 .symtab section 的命令为:strip <object file>
不属于任何一个 Segment
.debug一个调试符号表,其条目是程序中定义的局部变量和类型定义(typedefs),程序中定义和引用的全局变量,以及原始的 C 源文件。(只有带 -g 编译时才会产生)
  • 方式1:readelf --debug-dump <object file>
  • 方式2:objdump -g <object file>
不属于任何一个 Segment
.comment版本控制信息。
  • readelf -p .comment <object file>
不属于任何一个 Segment
.shstrtab一个字符串表,其内容包括所有 section 的名称。
  • readelf -p .shstrtab <object file>
不属于任何一个 Segment
.strtab一个字符串表,其内容包括所有符号的名称。
  • readelf -p .strtab <object file> (注:删除目标文件中 .strtab section 的命令为:strip <object file>
不属于任何一个 Segment
Section header table描述目标文件的所有节(sections)的位置和大小等信息。
  • readelf -S <object file>
不属于任何一个 Segment
  • 注:

    • readelf 命令中缩写含义:-h--file-header的缩写,-l--program-headers的缩写,-s--symbols的缩写,-p--string-dump的缩写,-S--section-headers的缩写。

    • objdump 命令中缩写含义:-p--private-headers的缩写,-d--disassemble的缩写,-S--source的缩写,-g--debugging的缩写。

1)查看可执行目标文件的Program header table

查看 main.cpp 的内容:

$ cat main.cpp
#include <stdio.h>

int g_val_1;
int g_val_2 = 16;
const int g_val_3 = 2;

int main()

  printf("g_val_1=%x, g_val_2=%x\\n", g_val_1, g_val_2);
  return 0;

查看可执行目标文件mainProgram header table

$ readelf -l main

Elf file type is EXEC (Executable file)
Entry point 0x400430
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000000071c 0x000000000000071c  R E    200000
  LOAD           0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                 0x000000000000022c 0x0000000000000238  RW     200000
  DYNAMIC        0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
                 0x00000000000001d0 0x00000000000001d0  RW     8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x00000000000005f0 0x00000000004005f0 0x00000000004005f0
                 0x0000000000000034 0x0000000000000034  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                 0x00000000000001f0 0x00000000000001f0  R      1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07     
   08     .init_array .fini_array .jcr .dynamic .got 
  • 注:

    • Entry point 0x400430表示程序所执行的第一条指令的虚拟内存地址为 0x400430。

    • There are 9 program headers, starting at offset 64表示 Program Header Table Entry 中有 9 个条目,其起始位置在可执行目标文件中的偏移量为 64 字节(可执行目标文件的前 64 字节被 ELF header 占用)。

    • Type是 Program Header Table Entry 结构体中的第 1 个字段,占用 4 字节,表示 segment 的类型。常用可选项:0(PT_NULL,未使用的条目)、1(PT_LOAD,可加载的 segment)、2(PT_DYNAMIC,动态链接表)、3(PT_INTERP,程序解释器的路径名称)、4(PT_NOTE,Note sections)、5(PT_SHLIB,保留)、6(PT_PHDR,Program header table)。

    • Flags是 Program Header Table Entry 结构体中的第 2 个字段,占用 4 字节,表示 segment 的属性。。常用可选项:0x1(PF_X,可执行权限)、0x2(PF_W,可写权限)、0x4(PF_R,可读权限)。

    • Offset是 Program Header Table Entry 结构体中的第 3 个字段,占用 8 字节,表示该 segment 的起始位置在可执行目标文件中的偏移量(字节)。

    • VirtAddr是 Program Header Table Entry 结构体中的第 4 个字段,占用 8 字节,表示该 segment 的虚拟内存地址。

    • PhysAddr是 Program Header Table Entry 结构体中的第 5 个字段,占用 8 字节,保留字段。

    • FileSiz是 Program Header Table Entry 结构体中的第 6 个字段,占用 8 字节,表示该 segment 在可执行目标文件中的大小(字节)。

    • MemSiz是 Program Header Table Entry 结构体中的第 7 个字段,占用 8 字节,表示该 segment 在内存中的大小(字节)。

    • Align是 Program Header Table Entry 结构体中的第 8 个字段,占用 8 字节,表示该 segment 的内存对齐要求。该字段的值必须是 2 的幂。上面打印结果中该字段的值是十六进制的(可以用objdump -p main的输出结果进行佐证)。

a)程序入口点

查看可执行目标文件main.textsection:

$ objdump -d main
# 省略...
Disassembly of section .text:

0000000000400430 <_start>:
  400430:	31 ed                	xor    %ebp,%ebp
  400432:	49 89 d1             	mov    %rdx,%r9
  400435:	5e                   	pop    %rsi
  400436:	48 89 e2             	mov    %rsp,%rdx
  400439:	48 83 e4 f0          	and    $0xfffffffffffffff0,%rsp
  40043d:	50                   	push   %rax
  40043e:	54                   	push   %rsp
  40043f:	49 c7 c0 c0 05 40 00 	mov    $0x4005c0,%r8
  400446:	48 c7 c1 50 05 40 00 	mov    $0x400550,%rcx
  40044d:	48 c7 c7 26 05 40 00 	mov    $0x400526,%rdi
  400454:	e8 b7 ff ff ff       	callq  400410 <__libc_start_main@plt>
  400459:	f4                   	hlt    
  40045a:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)
# 省略...

从上面结果中可以看出,程序入口点即为.textsection 的第一条指令。

程序执行时,程序入口点的地址仍为 0x400430,如下所示:

$ gdb -q ./main
Reading symbols from ./main...(no debugging symbols found)...done.
(gdb) start
Temporary breakpoint 1 at 0x40052a
Starting program: /home/test/tx/main 

Temporary breakpoint 1, 0x000000000040052a in main ()
(gdb) disas 0x400430
Dump of assembler code for function _start:
   0x0000000000400430 <+0>:	xor    %ebp,%ebp
   0x0000000000400432 <+2>:	mov    %rdx,%r9
   0x0000000000400435 <+5>:	pop    %rsi
   0x0000000000400436 <+6>:	mov    %rsp,%rdx
   0x0000000000400439 <+9>:	and    $0xfffffffffffffff0,%rsp
   0x000000000040043d <+13>:	push   %rax
   0x000000000040043e <+14>:	push   %rsp
   0x000000000040043f <+15>:	mov    $0x4005c0,%r8
   0x0000000000400446 <+22>:	mov    $0x400550,%rcx
   0x000000000040044d <+29>:	mov    $0x400526,%rdi
   0x0000000000400454 <+36>:	callq  0x400410 <__libc_start_main@plt>
   0x0000000000400459 <+41>:	hlt
End of assembler dump.

如果你觉得本文对你有所帮助,欢迎关注公众号,支持一下!

以上是关于计算机系统篇之链接:目标文件的主要内容,如果未能解决你的问题,请参考以下文章

计算机系统篇之链接:动态链接

计算机系统篇之链接(10):.bss.data 和 .rodata section 之间的区别

linux命令每日一篇之ln

计算机系统篇之链接:静态链接(上)

计算机系统篇之链接:静态链接(上)

计算机系统篇之链接:静态链接(中)——符号解析