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
二进制的内容,照着二进制的内容去分析,会更加具体一点,但是自己的代码总是有问题,后来怕耽误时间就没去做,以后有空再做。