计算机系统篇之链接:目标文件
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;
目标文件格式
目标文件格式是指目标文件的组织方式。目标文件格式因系统而异。
系统 | 目标文件格式 |
---|---|
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 | 描述生成该目标文件的系统的字大小和字节顺序、目标文件类型、机器类型等信息。 |
|
.text | 已编译程序的机器代码。 |
|
.rodata | 只读数据,比如:常量字符串、带 const 修饰的全局变量和静态变量等。 | 无 |
.data | 已初始化的且初始值非0的全局变量和静态变量。 | 无 |
.bss | 未初始化的或初始值为0的全局变量和静态变量。 | 无 |
.symtab | 一个符号表,它存放着在目标文件中定义和引用的函数和全局变量、静态变量的信息。 |
|
.rel<name> | <name> section 的可重定位信息。 |
|
.debug | 一个调试符号表,其条目是程序中定义的局部变量和类型定义(typedefs),程序中定义和引用的全局变量,以及原始的 C 源文件。(只有带 -g 编译时才会产生) |
|
.comment | 版本控制信息。 |
|
.shstrtab | 一个字符串表,其内容包括所有 section 的名称。 |
|
.strtab | 一个字符串表,其内容包括所有符号的名称。 |
|
Section header table | 描述目标文件的所有节(sections)的位置和大小等信息。 |
|
-
注:
-
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 | 描述生成该目标文件的系统的字大小和字节顺序、目标文件类型、机器类型等信息。 |
| Code Segment | 是 |
Program header table | 描述可执行目标文件中所有段(Segments)的位置和大小等信息。 |
| Code Segment | 是 |
.init | 该 section 中定义了一个名称为 _init 的函数,会被程序初始化代码调用。 | objdump -d <object file> | Code Segment | 是 |
.text | 已编译程序的机器代码。 |
| Code Segment | 是 |
.rodata | 只读数据,比如:常量字符串、带 const 修饰的全局变量和静态变量等。 | 无 | Code Segment | 是 |
.data | 已初始化的且初始值非0的全局变量和静态变量。 | 无 | Data Segment | 是 |
.bss | 未初始化的或初始值为0的全局变量和静态变量。 | 无 | Data Segment | 是 |
.symtab | 一个符号表,它存放着在目标文件中定义和引用的函数和全局变量、静态变量的信息。 |
| 不属于任何一个 Segment | 否 |
.debug | 一个调试符号表,其条目是程序中定义的局部变量和类型定义(typedefs),程序中定义和引用的全局变量,以及原始的 C 源文件。(只有带 -g 编译时才会产生) |
| 不属于任何一个 Segment | 否 |
.comment | 版本控制信息。 |
| 不属于任何一个 Segment | 否 |
.shstrtab | 一个字符串表,其内容包括所有 section 的名称。 |
| 不属于任何一个 Segment | 否 |
.strtab | 一个字符串表,其内容包括所有符号的名称。 |
| 不属于任何一个 Segment | 否 |
Section header table | 描述目标文件的所有节(sections)的位置和大小等信息。 |
| 不属于任何一个 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.
如果你觉得本文对你有所帮助,欢迎关注公众号,支持一下!
以上是关于计算机系统篇之链接:目标文件的主要内容,如果未能解决你的问题,请参考以下文章