ELF文件
ELF 是Executable and Linking Format的缩写,即可执行和可链接的格式,是Unix/Linux系统ABI (Application Binary Interface)规范的一部分。Unix/Linux下的可执行二进制文件、目标代码文件、共享库文件和core dump文件都属于ELF文件。
可以利用file命令来判断一个文件是不是ELF格式,比如我以可执行文件uxplay为例子

ELF格式
常见的ELF格式如下图所示,左边为链接视图,右边为执行视图。

- 左边是
ELF的链接视图,可以理解为是目标代码文件的内容布局。右边是ELF的执行视图,可以理解为可执行文件的内容布局。 segments与sections是包含的关系,一个segment包含若干个section。当ELF文件被操作系统加载到内存中后(加载到内存中也就是说这个elf要运行),系统会将多个具有相同权限(flg值)section合并成一个segment(优化空间利用),减少内存碎片。
区分
- 节(section)
- 平常我们说
.text,.bss,.data这些都是section层面上的,比如.text,告诉汇编器后面的代码放入.text section中。 .text:保存程序代码,权限只读.data:保存已经初始化的全局变量和局部静态变量.bss: 保存未初始化的全局变量和局部静态变量- 目标代码文件中的
section和section header table中的条目是一一对应的。section的信息用于链接器对代码重定位。
- 平常我们说
- 段(segment)
- 平常说的代码段,数据段则是
segment层面上的 - 目标代码中的
section会被链接器组织到可执行文件的各个segment中。.text section的内容会组装到代码段中,.data,.bss等节的内容会包含在数据段中。 - 而文件载入内存执行时,是以
segment组织的,每个segment对应ELF文件中program header table中的一个条目,用来建立可执行文件的进程映像。
- 平常说的代码段,数据段则是
现在以我的test.c编译生成的目标文件test.o跟可执行文件test为例,用readelf命令来初步展现下链接视图跟执行视图的些许不同(代码在文章后面有)
先介绍下readelf的用法

readelf -h:显示elf文件的头部readelf -l:显示elf文件的segment头readelf -S:显示elf文件的section头
readelf -l test.o 结果为目标文件中没有程序头,也就是没有段

而readelf -l test 则都打印出来

copyright@copyright-Vostro-3559:~/code_elf$ readelf -l test
Elf 文件类型为 DYN (共享目标文件)
Entry point 0x1060
There are 11 program headers, starting at offset 64
程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x0000000000000268 0x0000000000000268 R 0x8
INTERP 0x00000000000002a8 0x00000000000002a8 0x00000000000002a8
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000000005b8 0x00000000000005b8 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
0x000000000000020d 0x000000000000020d R E 0x1000
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
0x0000000000000170 0x0000000000000170 R 0x1000
LOAD 0x0000000000002db0 0x0000000000003db0 0x0000000000003db0
0x0000000000000270 0x0000000000000278 RW 0x1000
DYNAMIC 0x0000000000002dc0 0x0000000000003dc0 0x0000000000003dc0
0x00000000000001f0 0x00000000000001f0 RW 0x8
NOTE 0x00000000000002c4 0x00000000000002c4 0x00000000000002c4
0x0000000000000044 0x0000000000000044 R 0x4
GNU_EH_FRAME 0x0000000000002028 0x0000000000002028 0x0000000000002028
0x000000000000003c 0x000000000000003c R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002db0 0x0000000000003db0 0x0000000000003db0
0x0000000000000250 0x0000000000000250 R 0x1
Section to Segment mapping:
段节...
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
03 .init .plt .plt.got .text .fini
04 .rodata .eh_frame_hdr .eh_frame
05 .init_array .fini_array .dynamic .got .data .bss
06 .dynamic
07 .note.ABI-tag .note.gnu.build-id
08 .eh_frame_hdr
09
10 .init_array .fini_array .dynamic .got
readelf -S test.o
copyright@copyright-Vostro-3559:~/code_elf$ readelf -S test.o
There are 15 section headers, starting at offset 0x448:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000004e 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000002f8
00000000000000a8 0000000000000018 I 12 1 8
[ 3] .data PROGBITS 0000000000000000 00000090
0000000000000004 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 00000094
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 00000094
0000000000000023 0000000000000000 A 0 0 1
[ 6] .data.rel.local PROGBITS 0000000000000000 000000b8
0000000000000008 0000000000000000 WA 0 0 8
[ 7] .rela.data.rel.lo RELA 0000000000000000 000003a0
0000000000000018 0000000000000018 I 12 6 8
[ 8] .comment PROGBITS 0000000000000000 000000c0
000000000000002c 0000000000000001 MS 0 0 1
[ 9] .note.GNU-stack PROGBITS 0000000000000000 000000ec
0000000000000000 0000000000000000 0 0 1
[10] .eh_frame PROGBITS 0000000000000000 000000f0
0000000000000038 0000000000000000 A 0 0 8
[11] .rela.eh_frame RELA 0000000000000000 000003b8
0000000000000018 0000000000000018 I 12 10 8
[12] .symtab SYMTAB 0000000000000000 00000128
0000000000000198 0000000000000018 13 10 8
[13] .strtab STRTAB 0000000000000000 000002c0
0000000000000035 0000000000000000 0 0 1
[14] .shstrtab STRTAB 0000000000000000 000003d0
0000000000000076 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)
但是readelf -S test还是会显示section的信息,但是section headers的数量要比上面的多
copyright@copyright-Vostro-3559:~/code_elf$ readelf -S test
There are 29 section headers, starting at offset 0x39b0:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 00000000000002a8 000002a8
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 00000000000002c4 000002c4
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 00000000000002e4 000002e4
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000308 00000308
0000000000000024 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000000330 00000330
00000000000000c0 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 00000000000003f0 000003f0
0000000000000089 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 000000000000047a 0000047a
0000000000000010 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000000490 00000490
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 00000000000004b0 000004b0
00000000000000d8 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000000588 00000588
0000000000000030 0000000000000018 AI 5 22 8
[11] .init PROGBITS 0000000000001000 00001000
0000000000000017 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000001020 00001020
0000000000000030 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 0000000000001050 00001050
0000000000000008 0000000000000008 AX 0 0 8
[14] .text PROGBITS 0000000000001060 00001060
00000000000001a1 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 0000000000001204 00001204
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 0000000000002000 00002000
0000000000000027 0000000000000000 A 0 0 4
[17] .eh_frame_hdr PROGBITS 0000000000002028 00002028
000000000000003c 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000002068 00002068
0000000000000108 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000003db0 00002db0
0000000000000008 0000000000000008 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000003db8 00002db8
0000000000000008 0000000000000008 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000003dc0 00002dc0
00000000000001f0 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000003fb0 00002fb0
0000000000000050 0000000000000008 WA 0 0 8
[23] .data PROGBITS 0000000000004000 00003000
0000000000000020 0000000000000000 WA 0 0 8
[24] .bss NOBITS 0000000000004020 00003020
0000000000000008 0000000000000000 WA 0 0 4
[25] .comment PROGBITS 0000000000000000 00003020
000000000000002b 0000000000000001 MS 0 0 1
[26] .symtab SYMTAB 0000000000000000 00003050
0000000000000648 0000000000000018 27 44 8
[27] .strtab STRTAB 0000000000000000 00003698
000000000000021a 0000000000000000 0 0 1
[28] .shstrtab STRTAB 0000000000000000 000038b2
00000000000000fe 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)
解析ELF文件(一):ELF Header
ELF的结构声明位于系统头文件 elf.h 中,在/usr/include目录下,ELF格式分为32位与64位两种,下面以64位为例。
ELF Header
结构体Elf64_Ehdr代表elf的头部,头部可以提供的信息如下所示。

readelf -h也可查看头部信息

e_ident
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
包含了Maigc Number和其它信息,共16字节。16个字节在这个头文件中都有定义,而且实际上readelf -h也把这16个包含的信息显示出来了。
0~3:前4字节为Magic Number,固定为ELFMAG ,也即下图的\177ELF。刚好对应readelf读出来的7f 45 4c 46,刚好0x45 0x4c 0x46的十进制为 69 76 70 ,刚好ASCII码对应的就是ELF。

4(EI_CLASS):ELFCLASS32代表是32位ELF,ELFCLASS64 代表64位ELF。readelf读出来的是0x02,代表64位。

5(EI_DATA):ELFDATA2LSB代表小端,ELFDATA2MSB代表大端。readelf读出来的是0x01,代表小端。

6(EI_VERSION):固定为EV_CURRENT(1)。readelf读出来的是0x01。


7(EI_OSABI):操作系统ABI标识。readelf读出来的是0x00,而且readelf同时也打印出来了。


8(EI_ABIVERSION):ABI版本(实际未使用)。
9~15:对齐填充,无实际意义。都是0x00。

e_type
Elf64_Half e_type; /* Object file type */
代表文件类型

ET_REL:可重定位文 件(如目标文件)ET_EXEC:可执行文件(可直接执行的文件)ET_DYN:共享目标文件(如SO库)ET_CORE:Core文件(吐核文件)
注意:GCC使用编译选项 -pie 编译的可执行文件实际也是DT_DYN类型。


e_machine
Elf64_Half e_machine; /* Architecture */
代表架构的信息,头文件里面定义了200多种类型。

test.o跟test利用readelf -h读出来的都是Advanced Micro Devices X86-64

e_verison
Elf64_Word e_version; /* Object file version */
文件版本,目前常见的ELF 文件版本均为EV_CURRENT(1)。

e_entry
Elf64_Addr e_entry; /* Entry point virtual address */
入口虚拟地址
readelf -h test.o
入口点地址: 0x0
readelf -h test
入口点地址: 0x1060
e_phoff
Elf64_Off e_phoff; /* Program header table file offset */
段表文件偏移
` readelf -l test`
Elf 文件类型为 DYN (共享目标文件)
Entry point 0x1060
There are 11 program headers, starting at offset 64
e_shoff
Elf64_Off e_shoff; /* Section header table file offset */
节表文件偏移
readelf -S test.o
copyright@copyright-Vostro-3559:~/code_elf$ readelf -S test.o
There are 15 section headers, starting at offset 0x448:
readelf -S test
copyright@copyright-Vostro-3559:~/code_elf$ readelf -S test
There are 29 section headers, starting at offset 0x39b0:
e_flags
Elf64_Word e_flags; /* Processor-specific flags */
处理器特定的标志,一般为0。
e_ehsize
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf_Header的大小(字节),64位则为64,如果是32位则为52。正如前面的这张图所示。
32位程序


64位程序

e_phentsize
Elf64_Half e_phentsize; /* Program header table entry size */
段头(Program Header)的大小(字节)
readelf -h test.o

readelf -h test

e_phnum
Elf64_Half e_phnum; /* Program header table entry count */
段的数量。
readelf -l test
copyright@copyright-Vostro-3559:~/code_elf$ readelf -l test
Elf 文件类型为 DYN (共享目标文件)
Entry point 0x1060
There are 11 program headers, starting at offset 64
readelf -h test
copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test
.........
Number of program headers: 11
.........
e_shentsize
Elf64_Half e_shentsize; /* Section header table entry size */
节头(Section Header)的大小(字节)。因为test也是由test.o而来的,所以section的大小应该是一样的,但是section的数量不一定是一样的。
个人看法,因为,当ELF文件被操作系统加载到内存中后(加载到内存中也就是说这个elf要运行),系统会将多个具有相同权限(flg值)section合并成一个segment(优化空间利用),在这个过程中section的数量可能会发生改变。
readelf -h test.o
copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test.o
.............
节头大小: 64 (字节)
.............
readelf -h test
copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test
.............
节头大小: 64 (字节)
.............
e_shnum
Elf64_Half e_shnum; /* Section header table entry count */
节头数量
readelf -h test.o
copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test.o
.............
节头数量: 15
.............
readelf -h test
copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test
.............
节头数量: 29
.............
e_shstrndx
Elf64_Half e_shstrndx; /* Section header string table index */
节字符串表的节索引。
readelf -h test.o
copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test.o
.............
字符串表索引节头: 14
.............
readelf -h test
copyright@copyright-Vostro-3559:~/code_elf$ readelf -h test
.............
字符串表索引节头: 28
.............
代码
编译指令
gcc -c test.c -o test.o
test.c
#include <stdio.h>
int a = 1;
int b;
char *p = "123";
int main(void)
{
int c = 3;
b = 2;
printf("hello\n");
printf("a is %d,b is %d,c is %d\n",a,b,c);
return 0;
}
参考链接
其他想法
本来想直接写段代码读出来elf二进制的内容,照着二进制的内容去分析,会更加具体一点,但是自己的代码总是有问题,后来怕耽误时间就没去做,以后有空再做。