計算機系統篇之鏈接(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 | 已編譯程序的機器代碼。 | objdump -d <object file> (只輸出彙編)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 編譯時纔會產生) | readelf --debug-dump <object file> 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 名稱的起始位置在.shstrtab
section 中的偏移量(字節)。 -
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)查看可重定位目標文件的.comment
section
$ readelf -p .comment sum.o
String dump of section '.comment':
[ 1] GCC: (Ubuntu 6.5.0-2ubuntu1~16.04) 6.5.0 20181026
注:這裏.comment
section 的第一個字節的值爲 0x00(\0
),但不代表.comment
section 在其他類型的目標文件中第一個字節的值也爲 0x00(\0
)。比如:在可執行目標文件中該 section 的第一個字節的值不爲 0x00。
查看可執行目標文件main
的.comment
section 的內容:
$ readelf -p .comment main
String dump of section '.comment':
[ 0] GCC: (Ubuntu 6.5.0-2ubuntu1~16.04) 6.5.0 20181026
4)查看可重定位目標文件的.shstrtab
section
$ 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 名稱的起始位置在.shstrtab
section 中的偏移量(字節)。 -
.shstrtab
section 的第一個字節的值固定爲 0,表示一個空的或者不存在的 section 名稱。 -
.shstrtab
section 的 section 名稱是以\0
(對應的 ASCII 碼十六機制爲 0x00)結尾的字符串。
-
注意: 可重定位目標文件sum.o
的.shstrtab
section 中未顯式地包含.eh_frame
section 的名稱。從表面上看,這不符合“.shstrtab
section 的作用:一個字符串表,其內容包括所有 section 的名稱”。實際上,.shstrtab
section 中隱式地包含.eh_frame
section 的名稱,即複用了.rela.eh_frame
section 的後半部分的名稱。
驗證註釋中的三條結論:
$ 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.
最左側一列爲地址,後面的爲 .shstrtab
section 中的內容。從上面結果中可以看出:
-
.shstrtab
section 的第一個字節的值爲 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)查看可重定位目標文件的.strtab
section
$ 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
-
注:
-
[]
中的值表示符號名稱的起始位置在.strtab
section 中的偏移量(字節)。 -
.strtab
section 的第一個字節的值固定爲 0,表示一個空的或者不存在的符號名稱。 -
.strtab
section 的符號名稱是以\0
(對應的 ASCII 碼十六機制爲 0x00)結尾的字符串。
-
6)查看可重定位目標文件的.symtab
section,見 READELF 程序所打印的 .symtab section 及解釋
符號表
每個目標文件(包括可重定位目標文件、可共享目標文件和可執行目標文件)中都有一個符號表(.symtab section),符號表中包含了被該目標文件定義和引用的符號的信息。
符號表(.symtab section)是由彙編器產生的,跟編譯時是否添加-g
選項無關。
1) 符號表中的符號可以分爲三類:
符號類型 | 定義者 | 可見性 | 哪些屬於這類符號 |
---|---|---|---|
內部定義的全局符號(Global Symbols) | 本目標文件 | 本目標文件和其他目標文件都可見 | 由本目標文件定義的非靜態函數和非靜態全局變量 |
引用外部的全局符號(External Symbols) | 其他目標文件 | 本目標文件和其他目標文件都可見 | 由本目標文件引用的但定義在其他目標文件中的非靜態函數和非靜態全局變量 |
內部定義的局部符號(Local Symbols) | 本目標文件 | 僅本目標文件可見 | 由本目標文件定義的靜態函數、靜態全局變量和靜態局部變量 |
注:嚴格地講,靜態局部變量不僅對於其他目標文件不可見,而且對於本目標文件的其他函數也不可見。
2) READELF 程序所打印的.symtab
section 及解釋
查看 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 中的偏移量(字節)。 -
Bind
和Type
是 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)的位置和大小等信息。 | readelf -l <object file> objdump -p <object file> |
Code Segment | 是 |
.init | 該 section 中定義了一個名稱爲 _init 的函數,會被程序初始化代碼調用。 | objdump -d <object file> |
Code Segment | 是 |
.text | 已編譯程序的機器代碼。 | objdump -d <object file> (只輸出彙編)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 編譯時纔會產生) | readelf --debug-dump <object file> 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;
}
查看可執行目標文件main
的Program 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
的.text
section:
$ 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)
# 省略...
從上面結果中可以看出,程序入口點即爲.text
section 的第一條指令。
程序執行時,程序入口點的地址仍爲 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.
如果你覺得本文對你有所幫助,歡迎關注公衆號,支持一下!