計算機系統篇之鏈接(2):目標文件

計算機系統篇之鏈接(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;
}

目標文件格式

目標文件格式是指目標文件的組織方式。目標文件格式因系統而異。

系統 目標文件格式
Windows PE(Portable Executable)
Mac OS-X Mach-O(Mach Object)
X86-64 Linux 和 Unix ELF-64(Executable and Linkable Format)
aarch64 Linux 和 Unix ELF-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.
    

    如果你覺得本文對你有所幫助,歡迎關注公衆號,支持一下!

    在這裏插入圖片描述

    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章