可执行二进制文件里有什么?
ELF 文件格式的基本组成结构可以被划分为 ELF 头、Section 和 Segment 三大主要部分。其中,各个 Section
中包含有按照功能类别划分好的、用于支撑 ELF 功能的各类数据。这些数据共同组成了 ELF 文件的静态视图,以用于支持 ELF
文件的链接过程。而众多的 Segment 则组成了 ELF 文件的动态视图,该视图描述了 ELF
文件在被操作系统加载和执行时,其依赖的相关数据在进程 VAS 内的分布情况。
// elf.c
#include <stdio.h>
int main (void) {
const char* str = "Hello, world!";
printf("%s", str);
return 0;
}
ELF文件格式
使用统一的“头部(header)”来保存可执行文件的基本信息。而其他数据则按照功能被划分在了以 Section 或 Segment 形式组织的一系列单元中。
ELF 格式的可执行文件,64-bit 表示该文件采用的是 64 位地址空间,以及该 ELF 格式的版本,是否采用动态链接,以及使用的动态链接器地址等信息。
$ gcc elf.c -o elf.out
$ file ./elf.out
./elf.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=f594e0790a038413521eebf9ce41d98a69dbcaa9, not stripped
ELF头
[centos71 studyCode]$ readelf -h ./elf.out
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: EXEC (可执行文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x400440
程序头起点: 64 (bytes into file)
Start of section headers: 6480 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 56 (字节)
Number of program headers: 9 //segment数量
节头大小: 64 (字节)
节头数量: 30 //section数量
字符串表索引节头: 29
查看开头64字节的内容
-Ax: 显示地址的时候,用十六进制来表示。如果使用 -Ad,意思就是用十进制来显示地址;
-t -x1: 显示字节码内容的时候,使用十六进制(x),每次显示一个字节(1);
-N 52:只需要读取 52 个字节;
第三行[地址000020],e_phoff值为0x40,表示程序头表。e_shoff值为0x1950,表示段表的偏移地址,保存 section header table 入口对文件头的偏移量,而每个 section 都会对应一个 section header。
第四行,e_ehsize值为0x0040,表示elf文件头大小(正好是64个字节)。e_phentsize表示一个program header表中的入口的长度,值为0x0038。e_phnum的值为0x0009,给出program header表中的入口数目。e_shentsize值为0x0040表示段头大小为64个字节。e_shnum值为0x001e,表示段表入口有30个。e_shstrndx值为0x001d,表示段名串表的在段表中的索引号
$ od -Ax -t x1 -N 64 elf
[liuxl@centos71 studyCode]$ od -Ax -t x1 -N 64 elf
000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
000010 02 00 3e 00 01 00 00 00 40 04 40 00 00 00 00 00
000020 40 00 00 00 00 00 00 00 50 19 00 00 00 00 00 00
000030 00 00 00 00 40 00 38 00 09 00 40 00 1e 00 1d 00
000040
ELF Section 头
查看Sections Header的结构体
vi /usr/include/elf.h
readelf 命令指定 “-S” 参数,查看所有这些 Section 头的具体信息。
readelf -S ./elf.out
objdump命令指定“-s” 参数,我们可以查看某个 Section 的完整内容
[@centos71 studyCode]$ objdump -s ./elf.out --section=.rodata
Section 用于存放可执行文件中按照功能分类好的数据。ELF 将各个 Section 的相关信息都整理在了其各自对应的 Section 头部中,众多连续的 Section 头便组成了 Section 头表。
Section 头表中记录了各个 Section 结构的一些基本信息,例如 Section 的名称、长度、它在可执行文件中的偏移位置,以及具有的读写权限等。而操作系统在实际使用时,便可直接从 ELF 头部中获取到 Section 头表在整个二进制文件内的偏移位置,以及该表的大小。
[@centos71 studyCode]$ readelf -S ./elf.out
共有 30 个节头,从偏移量 0x1950 开始:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 000002b8
0000000000000060 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400318 00000318
000000000000003f 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400358 00000358
0000000000000008 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400360 00000360
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400380 00000380
0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400398 00000398
0000000000000048 0000000000000018 AI 5 23 8
[11] .init PROGBITS 00000000004003e0 000003e0
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000400400 00000400
0000000000000040 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000400440 00000440
0000000000000192 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 00000000004005d4 000005d4
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 00000000004005e0 000005e0
0000000000000021 0000000000000000 A 0 0 8
[16] .eh_frame_hdr PROGBITS 0000000000400604 00000604
0000000000000034 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 0000000000400638 00000638
00000000000000f4 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000600e10 00000e10
0000000000000008 0000000000000008 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000600e18 00000e18
0000000000000008 0000000000000008 WA 0 0 8
[20] .jcr PROGBITS 0000000000600e20 00000e20
0000000000000008 0000000000000000 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000600e28 00000e28
00000000000001d0 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000600ff8 00000ff8
0000000000000008 0000000000000008 WA 0 0 8
[23] .got.plt PROGBITS 0000000000601000 00001000
0000000000000030 0000000000000008 WA 0 0 8
[24] .data PROGBITS 0000000000601030 00001030
0000000000000004 0000000000000000 WA 0 0 1
[25] .bss NOBITS 0000000000601034 00001034
0000000000000004 0000000000000000 WA 0 0 1
[26] .comment PROGBITS 0000000000000000 00001034
000000000000005a 0000000000000001 MS 0 0 1
[27] .symtab SYMTAB 0000000000000000 00001090
00000000000005e8 0000000000000018 28 46 8
[28] .strtab STRTAB 0000000000000000 00001678
00000000000001ca 0000000000000000 0 0 1
[29] .shstrtab STRTAB 0000000000000000 00001842
0000000000000108 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)
C语法形式 | 数据存放位置 | 说明 |
---|---|---|
int x=10; /static int y =10; | .data | 包含有初始化的全局变量或静态变量,本例值x和y |
void foo(){int x 1=0;} | 寄存器、栈 | 局部变量 |
register int y =10; | 寄存器、栈 | 被register修饰的变量 |
calloc(256) | 堆 | 申请的堆内存 |
int x=10; /static int y ; | .bss | 存放初始化为0的全局变量或静态变量,,本例值x和y |
foo(“hello world!”) | .rodata | 用于存放只读(Read-only Data)数据。本例指hello world! |
foo(100) | .text | 存放程序本身的代码 |
查看某个 Section 的完整内容
在 C 代码中使用到的字符串数据 “Hello, world!”,便被放置在了该 Section 距离本section开头偏移 0x10 字节的位置上。[0x10 = 4005f0 =4005e0]
[@centos71 studyCode]$ objdump -s ./elf.out --section=.rodata
./elf.out: 文件格式 elf64-x86-64
Contents of section .rodata:
4005e0 01000200 00000000 00000000 00000000 ................
4005f0 48656c6c 6f2c2077 6f726c64 21002573 Hello, world!.%s
400600 00
ELF Program 头
描述该 Segment 的一些基本信息。包含着各个 Segment 的类型、偏移地址、大小、对齐情况,以及权限等信息。被标注为 “LOAD” 类型的 Segment 将会在程序运行时被真正载入到进程的 VAS 中,而其余 Segment 则主要用于辅助程序的正常运行(比如进行动态链接)。不仅如此,Program 头表的具体偏移位置和大小也被放置在了 ELF 头部中,因此操作系统可以在需要时,随时快速地得到这些信息。
由 Section 组成的静态视图外,众多的 Segment 则组成了描述可执行文件的动态视图。Segment 指定了应用程序在实际运行时,应该如何在进程的 VAS 内部组织数据。
readelf 命令指定 “-l” 参数,来观察程序对应可执行文件的 Segment 情况。
[@centos71 studyCode]$ readelf -l ./elf.out
Elf 文件类型为 EXEC (可执行文件)
入口点 0x400440
共有 9 个程序头,开始于偏移量64
程序头: //描述了各个segment的情况
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
0x000000000000072c 0x000000000000072c R E 200000
LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x0000000000000224 0x0000000000000228 RW 200000
DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x0000000000000604 0x0000000000400604 0x0000000000400604
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:
段节... //列出了section和segment的对应情况
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 .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
第一个 LOAD 类型【对应段节02】的 Segment 便包含有 .text 和 .rodata 在内的多个 Section,具有的权限为 “RE”,可读可执行;
而第二个 LOAD【对应段节03】 类型的 Segment 包含.data。具有的权限为 “RW”,可读可写;
第一个 LOAD Segment 所包含的内容,对应到可执行文件内的偏移(Offset)为 0。这意味着,操作系统在执行该程序时,除了各个 Segment 对应的 Sections 外,它还会将该文件的 ELF 头,连同它的 Program 头表一同加载到内存中。
ELF 编程
参考链接:https://zhuanlan.zhihu.com/p/467232104
https://blog.csdn.net/nirendao/article/details/123883856
https://www.cnblogs.com/20135223heweiqin/p/5554922.html