[toc]
网址
深入理解程序构造
LMA与VMA总结
ELF文件解析和加载(附代码)
Linux及安全实践四——ELF文件格式分析
程序员的自我修养:(1)目标文件
ELF文件格式
ELF文件解析和加载(附代码)
ELF中与动态链接相关的段
strtab symtab shstrtab
ELF Format 笔记(十一)—— 程序头结构
说明
即Executable and Linkable Format
,可执行和可链接格式。
本文使用的cpp源码
// test.cpp
int fang() {
return 1;
}
int main() {
fang();
return 0;
}
elf文件标准的文件类型
可执行文件(ET_EXEC
)
1. linux 下 的/bin/bash
2. windows 下的 .exe
可重定位文件(ET_REL
)
类型标记为:relocatable
。
主要包含数据和代码,
多个(包含一个)可重定位文件可用来链接成可执行文件、共享库,然后其机器码中的地址就会重定位。
1. 静态库(.a
)
这个有点区别,它基本上就是把许多目标文件捆绑在一起打包,类似tar命令, 再加上一些索引。
2. 目标文件(.o
)
在linux中是常见的中间文件,其格式与可执行文件相似。
3. windows
的.obj
共享库(.so
,又称共享目标文件,ET_DYN
)
类型标记为:dynamic
。
该文件被标记为了一个动态的可链接的目标文件,也称为共享库。这类共享库会在程序运行时被装载链接到程序的进程镜像中。
主要包含代码和数据。
用途
- 第一种用途可以与其它文件链接生成可重定位或者共享目标文件
- 再者直接链接到可执行文件,作为进程映象的一部分动态执行。
1. Linux下的.so
2. Windows下的.dll
核心转储格式文件(Core dump
,ET_CORE
)
又称核心文件
。
这个格式调试bug时很有用,进程意外终止时产生的,保留程序终止时进程的信息,Linux下的Core dump
。
载程序崩溃或者进程传递了一个SIGSEGV
信号(分段违规)时,会在核心文件中记录整个进程的镜像信息。可以使用GDB读取这类文件来辅助调试并查找程序崩溃的原因。
未知类型(ET_NONE
)
组成
1. ELF header
2. Program header table (程序头表)
3. Section (节、段)
4. Section header table (节头表、段头表、段表)
除了ELF header
位置是固定的,其他部分的位置、大小由ELF header
中的值决定。
ELF header
ELF header 结构定义(/usr/include/elf.h
中有)
typedef uint16_t Elf64_Half; // 16位,16位值的类型
typedef uint32_t Elf64_Word; // 32位,用于有符号和无符号的32位值的类型
typedef uint64_t Elf64_Addr; // 地址类型,64位
typedef uint64_t Elf64_Off; // 偏移的类型,64位
/* The ELF file header. This appears at the start of every ELF file. */
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ // 16字节
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr; // 大小总共64个字节,即在ELF文件的 前 0x40 字节。 Ehdr 即为 ELF header
命令行查看 ELF header
[root@localhost src]# readelf -h test.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: 1648 (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: 20
Section header string table index: 19
用hexdump -C test.o
查看16进制,下面提取出 ELF header
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 01 00 3e 00 01 00 00 00 00 00 00 00 00 00 00 00 |..>.............|
00000020 00 00 00 00 00 00 00 00 70 06 00 00 00 00 00 00 |........p.......|
00000030 00 00 00 00 40 00 00 00 00 00 40 00 14 00 13 00 |....@.....@.....|
identification 即 e_ident
- 第一部分
7f 45 4c 46 -> .ELF
:4字节,表示是一个ELF对象 - 第二部分
02
:1字节,02
表示是64位对象 - 第三部分
01
:1字节,01
表示小端 - 第四部分
0.
:1字节,01
表示头文件版本 - 其余默认
0
information
e_type
两个字节,01 00
表示是一个重定位文件,02 00
表示可执行文件,即上面的elf文件标准的文件类型
。
e_machine
两个字节,3e 00
表示是intel80386
处理器体系结构。
e_version
四个字节,01 00 00 00
表示是当前版本。
e_entry
八个字节,00 00 00 00 00 00 00 00
表示当前程序没有入口点。
e_phoff
八个字节,00 00 00 00 00 00 00 00
表示没有程序头表。
e_shoff
八个字节,70 06 00 00 00 00 00 00
表示段表的偏移地址在00 00 00 00 00 00 06 70
处,这里可以看出是小端。
e_flags
四个字节,00 00 00 00
表示未知处理器特定标志#define EF_SH_UNKNOWN 0x0
。
e_ehsize
两个字节,40 00
表示elf文件头大小为00 40(64个字节)
。
e_phentsize
两个字节,00 00
表示重定位文件没有程序头表。
e_phnum
两个字节,00 00
表示重定位文件没有程序头表。和上面重复了
e_ehentsize
两个字节,40 00
表示段头大小为00 40(64字节
),section header table
中每个元素(即header
)的大小
e_shnum
两个字节,14 00
表示段表入口有20个,即段表有20段。
e_shstrndx
两个字节,13 00
表示段表字符串在段表中的索引号,.shstrab
段的段表索引号为00 13
,即19
。
段头表(Section header table
)相关字段
- 段头表偏移:
e_shoff
- 段头表中各元素长度:
e_ehentsize
- 段头表个数:
e_shnum
通过ELF header
找到各section
[root@localhost src]# readelf -S test.o
There are 20 section headers, starting at offset 0x670:
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
000000000000001b 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000430
0000000000000018 0000000000000018 I 17 1 8
[ 3] .data PROGBITS 0000000000000000 0000005b
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 0000005b
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .debug_info PROGBITS 0000000000000000 0000005b
0000000000000073 0000000000000000 0 0 1
[ 6] .rela.debug_info RELA 0000000000000000 00000448
0000000000000108 0000000000000018 I 17 5 8
[ 7] .debug_abbrev PROGBITS 0000000000000000 000000ce
0000000000000051 0000000000000000 0 0 1
[ 8] .debug_aranges PROGBITS 0000000000000000 0000011f
0000000000000030 0000000000000000 0 0 1
[ 9] .rela.debug_arang RELA 0000000000000000 00000550
0000000000000030 0000000000000018 I 17 8 8
[10] .debug_line PROGBITS 0000000000000000 0000014f
0000000000000041 0000000000000000 0 0 1
[11] .rela.debug_line RELA 0000000000000000 00000580
0000000000000018 0000000000000018 I 17 10 8
[12] .debug_str PROGBITS 0000000000000000 00000190
000000000000008d 0000000000000001 MS 0 0 1
[13] .comment PROGBITS 0000000000000000 0000021d
000000000000002e 0000000000000001 MS 0 0 1
[14] .note.GNU-stack PROGBITS 0000000000000000 0000024b
0000000000000000 0000000000000000 0 0 1
[15] .eh_frame PROGBITS 0000000000000000 00000250
0000000000000058 0000000000000000 A 0 0 8
[16] .rela.eh_frame RELA 0000000000000000 00000598
0000000000000030 0000000000000018 I 17 15 8
[17] .symtab SYMTAB 0000000000000000 000002a8
0000000000000168 0000000000000018 18 13 8
[18] .strtab STRTAB 0000000000000000 00000410
0000000000000019 0000000000000000 0 0 1
[19] .shstrtab STRTAB 0000000000000000 000005c8
00000000000000a8 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
找到段表
文件头的e_shoff
是段表偏移00 00 00 00 00 00 06 70
,即相对于文件首地址的偏移。
e_shnum
*e_ehentsize
+e_shoff
=00000b70
,即为段表尾部(不包含)。
// 第一段:全为0,不表示任何段
00000670 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
// 第二段
// 段名:.text
// 类型:PROGBITS
// 标志:表示alloc和execute
// 相对于文件头偏移:0x40
// 大小:0x1b
000006b0 20 00 00 00 01 00 00 00 06 00 00 00 00 00 00 00 | ...............|
000006c0 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
000006d0 1b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000006e0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
// 第三段
// 段名:.rela.text
// 类型:RELA
// 标志:info
// 相对于文件头偏移:0x0430
// 大小:0x18
000006f0 1b 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
00000700 00 00 00 00 00 00 00 00 30 04 00 00 00 00 00 00 |........0.......|
00000710 18 00 00 00 00 00 00 00 11 00 00 00 01 00 00 00 |................|
00000720 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00 |................|
// 第四段
// 段名:.data
// 类型:PROGBITS
// 标志:write、alloc
// 相对于文件头偏移:0x5b
// 大小:0
00000730 26 00 00 00 01 00 00 00 03 00 00 00 00 00 00 00 |&...............|
00000740 00 00 00 00 00 00 00 00 5b 00 00 00 00 00 00 00 |........[.......|
00000750 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000760 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
// 第五段
// 段名:.bss
// 类型:NOBITS
// 标志:write、alloc
// 相对于文件头偏移:0x5b
// 大小:0
00000770 2c 00 00 00 08 00 00 00 03 00 00 00 00 00 00 00 |,...............|
00000780 00 00 00 00 00 00 00 00 5b 00 00 00 00 00 00 00 |........[.......|
00000790 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000007a0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
// 第六段
// 段名:.debug_info
// 类型:PROGBITS
// 标志:无
// 相对于文件头偏移:0x5b
// 大小:0x73
000007b0 36 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 |6...............|
000007c0 00 00 00 00 00 00 00 00 5b 00 00 00 00 00 00 00 |........[.......|
000007d0 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |s...............|
000007e0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
// 第七段
// 段名:.rela.debug_info
// 类型:RELA
// 标志:info
// 相对于文件头偏移:0x0448
// 大小:0x0108
000007f0 31 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00 |1.......@.......|
00000800 00 00 00 00 00 00 00 00 48 04 00 00 00 00 00 00 |........H.......|
00000810 08 01 00 00 00 00 00 00 11 00 00 00 05 00 00 00 |................|
00000820 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00 |................|
// 第八段
// 段名:.debug_abbrev
// 类型:PROGBITS
// 标志:无
// 相对于文件头偏移:0xce
// 大小:0x51
00000830 42 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 |B...............|
00000840 00 00 00 00 00 00 00 00 ce 00 00 00 00 00 00 00 |................|
00000850 51 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |Q...............|
00000860 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
// 第九段
// 段名:.debug_aranges
// 类型:PROGBITS
// 标志:无
// 相对于文件头偏移:0x011f
// 大小:0x30
00000870 55 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 |U...............|
00000880 00 00 00 00 00 00 00 00 1f 01 00 00 00 00 00 00 |................|
00000890 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |0...............|
000008a0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
// 第十段
// 段名:.rela.debug_aranges
// 类型:RELA
// 标志:info
// 相对于文件头偏移:0x0550
// 大小:0x30
000008b0 50 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00 |P.......@.......|
000008c0 00 00 00 00 00 00 00 00 50 05 00 00 00 00 00 00 |........P.......|
000008d0 30 00 00 00 00 00 00 00 11 00 00 00 08 00 00 00 |0...............|
000008e0 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00 |................|
// 第十一段
// 段名:.debug_line
// 类型:PROGBITS
// 标志:无
// 相对于文件头偏移:0x014f
// 大小:0x41
000008f0 69 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 |i...............|
00000900 00 00 00 00 00 00 00 00 4f 01 00 00 00 00 00 00 |........O.......|
00000910 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |A...............|
00000920 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
// 第十二段
// 段名:.rela.debug_line
// 类型:RELA
// 标志:info
// 相对于文件头偏移:0x0580
// 大小:0x18
00000930 64 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00 |d.......@.......|
00000940 00 00 00 00 00 00 00 00 80 05 00 00 00 00 00 00 |................|
00000950 18 00 00 00 00 00 00 00 11 00 00 00 0a 00 00 00 |................|
00000960 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00 |................|
// 第十三段
// 段名:.debug_str
// 类型:PROGBITS
// 标志:merge、strings
// 相对于文件头偏移:0x0190
// 大小:0x8d
00000970 75 00 00 00 01 00 00 00 30 00 00 00 00 00 00 00 |u.......0.......|
00000980 00 00 00 00 00 00 00 00 90 01 00 00 00 00 00 00 |................|
00000990 8d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000009a0 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
// 第十四段
// 段名:.comment
// 类型:PROGBITS
// 标志:merge、strings
// 相对于文件头偏移:0x021d
// 大小:0x2e
000009b0 80 00 00 00 01 00 00 00 30 00 00 00 00 00 00 00 |........0.......|
000009c0 00 00 00 00 00 00 00 00 1d 02 00 00 00 00 00 00 |................|
000009d0 2e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000009e0 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
// 第十五段
// 段名:.note.GNU-stack
// 类型:PROGBITS
// 标志:无
// 相对于文件头偏移:0x024b
// 大小:0
000009f0 89 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 |................|
00000a00 00 00 00 00 00 00 00 00 4b 02 00 00 00 00 00 00 |........K.......|
00000a10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000a20 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
// 第十六段
// 段名:.eh_frame
// 类型:PROGBITS
// 标志:alloc
// 相对于文件头偏移:0x0250
// 大小:0x58
00000a30 9e 00 00 00 01 00 00 00 02 00 00 00 00 00 00 00 |................|
00000a40 00 00 00 00 00 00 00 00 50 02 00 00 00 00 00 00 |........P.......|
00000a50 58 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |X...............|
00000a60 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
// 第十七段
// 段名:.rela.eh_frame
// 类型:RELA
// 标志:info
// 相对于文件头偏移:0x0598
// 大小:0x30
00000a70 99 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
00000a80 00 00 00 00 00 00 00 00 98 05 00 00 00 00 00 00 |................|
00000a90 30 00 00 00 00 00 00 00 11 00 00 00 0f 00 00 00 |0...............|
00000aa0 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00 |................|
// 第十八段
// 段名:.symtab
// 类型:STRTAB
// 标志:无
// 相对于文件头偏移:0x02a8
// 大小:0x0168
00000ab0 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 |................|
00000ac0 00 00 00 00 00 00 00 00 a8 02 00 00 00 00 00 00 |................|
00000ad0 68 01 00 00 00 00 00 00 12 00 00 00 0d 00 00 00 |h...............|
00000ae0 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00 |................|
// 第十九段
// 段名:.strtab
// 类型:STRTAB
// 标志:无
// 相对于文件头偏移:0x0410
// 大小:0x19
00000af0 09 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 |................|
00000b00 00 00 00 00 00 00 00 00 10 04 00 00 00 00 00 00 |................|
00000b10 19 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000b20 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
// 第二十段
// 段名:.shstrtab
// 类型:STRTAB
// 标志:无
// 相对于文件头偏移:0x05c8
// 大小:0xa8
00000b30 11 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 |................|
00000b40 00 00 00 00 00 00 00 00 c8 05 00 00 00 00 00 00 |................|
00000b50 a8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000b60 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000b70
Section header table
Section header table
结构定义(/usr/include/elf.h
中有)
typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
分析段表
第一段:全为0,不表示任何段
00000670 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
注意这里*
是省略了3行0值
,这个从下一个地址000006b0
可以看出。
第二段
000006b0 20 00 00 00 01 00 00 00 06 00 00 00 00 00 00 00 | ...............|
000006c0 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
000006d0 1b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000006e0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
sh_name
四个字节,20 00 00 00
表示该段名称在.shstrtab
中偏移量,是字符串,以\0
结尾,这里为.text
节。
sh_type
四个字节,01 00 00 00
表示这个段拥有程序所定义的信息,其格式和含义完全由该程序确定,这里表示PROGBITS
。
02 00 00 00
表示STRTAB
,即字符串表。
sh_flags
八个字节,06 00 00 00 00 00 00 00
表示alloc
和execute
。
sh_addr
八个字节,00 00 00 00 00 00 00 00
表示是section
在内存中的虚拟地址,.o
文件不需要执行,这里都是0。
sh_offset
八个字节,40 00 00 00 00 00 00 00
表示是section
与文件头之间的偏移,即00 00 00 00 00 00 00 40
。由于ELF header
占0x40
字节,因此它的下一个地址就是本section header table
的header
指向的section
。
sh_size
八个字节,1b 00 00 00 00 00 00 00
表示文件里面section
占用的大小,即00 00 00 00 00 00 00 1b
sh_link
四个字节,00 00 00 00
表示没有链接信息。
sh_info
四个字节,00 00 00 00
表示没有辅助信息。
sh_addralign
八个字节,01 00 00 00 00 00 00 00
表示字节对齐长度。
sh_entsize
八个字节,00 00 00 00 00 00 00 00
表示没有入口。
其他...
section
可用objdump -s -d test.o
把各段信息提取出来
.text
:本节中是可执行指令的集合
通过section header table
中的信息,可以找到指令:
00000040 55 48 89 e5 b8 01 00 00 00 5d c3 55 48 89 e5 e8 |UH.......].UH...|
00000050 00 00 00 00 b8 00 00 00 00 5d c3
通过反汇编查看:
[root@localhost src]# objdump -d test.o
test.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <_Z4fangv>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: b8 01 00 00 00 mov $0x1,%eax
9: 5d pop %rbp
a: c3 retq
000000000000000b :
b: 55 push %rbp
c: 48 89 e5 mov %rsp,%rbp
f: e8 00 00 00 00 callq 14
14: b8 00 00 00 00 mov $0x0,%eax
19: 5d pop %rbp
1a: c3 retq
.comment
:本节用来存放编译器版本信息
00000210 00 47 43 | .GC|
00000220 43 3a 20 28 47 4e 55 29 20 34 2e 38 2e 35 20 32 |C: (GNU) 4.8.5 2|
00000230 30 31 35 30 36 32 33 20 28 52 65 64 20 48 61 74 |0150623 (Red Hat|
00000240 20 34 2e 38 2e 35 2d 33 36 29 00 | 4.8.5-36). |
.symtab
:符号表
Symbol Table
,本节存放所有section
中定义的符号名字,一般是变量、函数
000002a0 00 00 00 00 00 00 00 00 | ........|
000002b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000002c0 01 00 00 00 04 00 f1 ff 00 00 00 00 00 00 00 00 |................|
000002d0 00 00 00 00 00 00 00 00 00 00 00 00 03 00 01 00 |................|
000002e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000002f0 00 00 00 00 03 00 03 00 00 00 00 00 00 00 00 00 |................|
00000300 00 00 00 00 00 00 00 00 00 00 00 00 03 00 04 00 |................|
00000310 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000320 00 00 00 00 03 00 05 00 00 00 00 00 00 00 00 00 |................|
00000330 00 00 00 00 00 00 00 00 00 00 00 00 03 00 07 00 |................|
00000340 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000350 00 00 00 00 03 00 08 00 00 00 00 00 00 00 00 00 |................|
00000360 00 00 00 00 00 00 00 00 00 00 00 00 03 00 0a 00 |................|
00000370 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000380 00 00 00 00 03 00 0c 00 00 00 00 00 00 00 00 00 |................|
00000390 00 00 00 00 00 00 00 00 00 00 00 00 03 00 0e 00 |................|
000003a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003b0 00 00 00 00 03 00 0f 00 00 00 00 00 00 00 00 00 |................|
000003c0 00 00 00 00 00 00 00 00 00 00 00 00 03 00 0d 00 |................|
000003d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003e0 0b 00 00 00 12 00 01 00 00 00 00 00 00 00 00 00 |................|
000003f0 0b 00 00 00 00 00 00 00 14 00 00 00 12 00 01 00 |................|
00000400 0b 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 |................|
.shstrtab
:本节存放 sh_name
Section Header String Table
,段表字符串表,针对段表的sh_name
。
000005c0 00 2e 73 79 6d 74 61 62 | ..symtab|
000005d0 00 2e 73 74 72 74 61 62 00 2e 73 68 73 74 72 74 |..strtab..shstrt|
000005e0 61 62 00 2e 72 65 6c 61 2e 74 65 78 74 00 2e 64 |ab..rela.text..d|
000005f0 61 74 61 00 2e 62 73 73 00 2e 72 65 6c 61 2e 64 |ata..bss..rela.d|
00000600 65 62 75 67 5f 69 6e 66 6f 00 2e 64 65 62 75 67 |ebug_info..debug|
00000610 5f 61 62 62 72 65 76 00 2e 72 65 6c 61 2e 64 65 |_abbrev..rela.de|
00000620 62 75 67 5f 61 72 61 6e 67 65 73 00 2e 72 65 6c |bug_aranges..rel|
00000630 61 2e 64 65 62 75 67 5f 6c 69 6e 65 00 2e 64 65 |a.debug_line..de|
00000640 62 75 67 5f 73 74 72 00 2e 63 6f 6d 6d 65 6e 74 |bug_str..comment|
00000650 00 2e 6e 6f 74 65 2e 47 4e 55 2d 73 74 61 63 6b |..note.GNU-stack|
00000660 00 2e 72 65 6c 61 2e 65 68 5f 66 72 61 6d 65 00 |..rela.eh_frame.|
.strtab
:本节是段表的字符串表
String Table
,字符串表,用于存储ELF文件中用到的各种字符串。
shstrtab
及symtab
经常引用strtab
中的字符串。
00000410 00 74 65 73 74 62 2e 63 70 70 00 5f 5a 34 66 61 |.testb.cpp._Z4fa|
00000420 6e 67 76 00 6d 61 69 6e 00 |ngv.main. |
用0
分割出3部分,为testb.cpp
、_Z4fangv
、main
。
为特殊符号
ld
链接脚本中将会定义很多特殊的符号,这些符号并没有在你的程序中定义,但是你可以直接声明并应用,我们称之为特殊符号
。
对于这些特殊符号,我们不必定义它,只需声明引用即可使用。链接器会在将程序最终链接成可执行文件的时候将其解析成正确的值,只有
在使用ld
链接生成最终可执行文件的时候这些符号才会存在。
查看链接默认脚本
ld -verbose
.rodata
:只读数据段
保存常量,比如调用printf
时会用一个字符串常量"%d\n"用来定义格式化输出,它是一种只读数据,所以保存在.rodata
段,
只读数据类型
- 在只读变量
- 字符串常量
单独设置.rodata
段的好处
支持了C里面的关键字const
, 而且操作系统加载程序时自动将只读变量加载到只读存储区,或者映射成只读,这样任何修改操作都会被认为非法操作,保证了程序的安全性。
.data
:数据段
保存的是那些已经初始化的全局静态变量和局部静态变量。
.bss
存放:
- 未初始化的全局变量
- 局部静态变量
- 初始化为
0
的变量
这些变量可读可写。
未初始化的全局变量和局部静态变量的处理方式
与不同的语言和不同的编译器实现有关
- 放入
.bss
段 - 使用未定义的
COMMON
符号,直到链接成可执行文件时才在.bss
段分配空间。
与动态链接相关的段
在Linux 下,动态链接器 ld.so
是一个共享对象,操作系统同样通过映射的方式将其加载到进程的地址空间中。操作系统在加载完动态链接后,将控制权交给动态链接器的入口地址,动态链接器执行一系列自身的初始化操作,而后根据当前的环境参数,对可执行文件进行动态链接工作,完成后将操作系统的控制权交给可执行文件的入口地址。
.interp
动态链接器在操作系统中的位置不是由系统配置决定,也不是由环境参数指定,而是由 ELF 文件中的 .interp
段指定。
该段里保存的是一个字符串,这个字符串就是可执行文件所需要的动态链接器的位置,常位于 /lib/ld-linux.so.2。(通常是软链接)
.dynamic
该段中保存了动态链接器所需要的基本信息,是一个结构数组,可以看做动态链接下 ELF 文件的“文件头”
。存储了动态链接会用到的各个表的位置等信息。
.dynsym
该段与 .symtab
段类似,但只保存了与动态链接相关的符号,很多时候,ELF文件同时拥有 .symtab
与 .synsym
段,其中 .symtab
将包含 .synsym
中的符号。
该符号表中记录了动态链接符号在动态符号字符串表中的偏移,与.symtab
中记录对应。
.dynstr
该段是 .dynsym
段的辅助段,.dynstr
与 .dynsym
的关系,类比与 .symtab
与 .strtab
的关系
.hash
在动态链接下,需要在程序运行时查找符号,为了加快符号查找过程,增加了辅助的符号哈希表,功能与 .dynstr
类似
.rel.dyn
对数据引用的修正,其所修正的位置位于 .got
以及数据段(类似重定位段 .rel.data
)
.rel.plt
对函数引用的修正,其所修正的位置位于 .got.plt
。
其他常量段
常用的段名 | 说明 |
---|---|
.rodata1 | Read Only Data,这种段里存放的是只读数据,比如字符串常量,全局const变量,和".rodata" 一样 |
.debug | 调试信息 |
.hash | 符号哈希表 |
.line | 调试时的行号表,即源代码行号和编译后指令的对应表 |
.note | 额外的编译器信息。比如程序的公司名,发布版本号 |
,plt .got | 动态链接的跳转表和全局入口表 |
.init .finit | 程序初始化与终结代码段 |
Program header table (程序头表)
在ELF
中把权限相同、又连在一起的段(section
)叫做segment
,操作系统正是按照segment
来映射可执行文件的。
描述这些segment
的结构叫做程序头,它描述了elf文件该如何被操作系统映射到内存空间中。
Program header table 结构定义
typedef struct
{
Elf64_Word p_type; /* Segment type */
Elf64_Word p_flags; /* Segment flags */
Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment */
} Elf64_Phdr;
p_type
段类型,如:PT_LOAD (可加载段)、PT_DYNAMIC (动态段)。
p_offset
段数据的第一个字节相对于文件开头的偏移量。
p_vaddr
段数据的第一个字节在内存中的虚拟地址 (只是一个偏移)。
p_paddr
暂时不用关心。
p_filesz
段数据在文件中的的字节大小,可以是 0。
p_memsz
段数据在内存映像中的字节大小,可以是 0。
p_flags
段内存映像的访问权限:PF_X (可执行)、PF_W (可写)、PF_R (可读)。
p_align
如果为 0 或 1,表示不需要对齐。否则,p_align 应该是 2 的正整数幂,p_vaddr 和 p_offset 在对 p_align 取模后应相等。
5种常见程序头类型
1. PT_LOAD
一个可执行文件只是一个PT_LOAD
类型的段。
这类程序头描述的是可装载的段。
一般一个需要动态链接的ELF可执行文件通常由两个可装载的段:
- 存放程序代码的text段
- 存放全局变量和动态链接的data段。
上面两个段会被映射到内存,根据p_align
中存放的值在内存中对齐。
程序头主要描述程序执行时在内存中的布局。
2. PT_DYNAMIC
动态段是动态链接可执行文件持有的,包含动态链接器所必有的一些信息,包含了一些标记值和指针,比如运行是需要链接的共享库列表,全局偏移表,重定位条目的相关信息。
3. PT_NOTE
存了与特定供应商或者系统相关的附加信息。
4. PT_INTERP
将信息二号位置存放在一个NULL为终止符的字符串中,对程序解释器位置的描述。
5. PT_LOAD
段保存了程序头标本身的位置和大小。Phdr
表保存了所有的Phdr
对文件(以及内存镜像)中段的描述信息。
readelf
获取程序头表(目标文件没有程序头表
)
[root@localhost src]# readelf -l test
Elf file type is EXEC (Executable file)
Entry point 0x4005c0
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
0x000000000000096c 0x000000000000096c R E 200000
LOAD 0x0000000000000e38 0x0000000000600e38 0x0000000000600e38
0x000000000000020c 0x0000000000000220 RW 200000
DYNAMIC 0x0000000000000e50 0x0000000000600e50 0x0000000000600e50
0x00000000000001b0 0x00000000000001b0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x00000000000007d8 0x00000000004007d8 0x00000000004007d8
0x000000000000004c 0x000000000000004c R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000000e38 0x0000000000600e38 0x0000000000600e38
0x00000000000001c8 0x00000000000001c8 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.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .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
只有LOAD
类型的segment
需要被映射。
我们需要做的是找到这些segment
在文件中的位置,并将其加载到对应的内存空间,对于memsiz
大于filesiz
的部分全部填充为0,加载完之后让程序跳转到入口地址。
节 (section) 和段 (segment)
节 (section
) 和段 (segment
) 分别是从链接视图和执行视图两个不同的角度的来划分的,它们有一个对应关系 (每个段“包含”一个或者多个节):
[root@localhost src]# readelf -l test
...
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.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .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