《深入理解计算机系统》看到第七章链接那一块用linux的可执行格式elf为例讲解,因为没有看过elf的文件格式所以一开始看的很迷茫,啥~啥~这是啥~
找了一些elf的讲解看了一下,觉得需要自己分析一个简单的elf才能加深理解。
首先ELF的整个结构如下图:
本文以一个带有static 变量的hello.c为例
nt main()
{
static int a = 255;
}
用gcc -c hello.c编译生成hello.o
查看hello.o的二进制内容(hexdump hello.o)为:
0000000 457f 464c 0101 0001 0000 0000 0000 0000
0000010 0001 0003 0001 0000 0000 0000 0000 0000
0000020 00f8 0000 0000 0000 0034 0000 0000 0028
0000030 000b 0008 8955 5de5 00c3 0000 00ff 0000
0000040 4700 4343 203a 5528 7562 746e 2075 2e34
0000050 2e38 2d34 7532 7562 746e 3175 317e 2e34
0000060 3430 332e 2029 2e34 2e38 0034 0014 0000
0000070 0000 0000 7a01 0052 7c01 0108 0c1b 0404
0000080 0188 0000 001c 0000 001c 0000 0000 0000
0000090 0005 0000 4100 080e 0285 0d42 4105 0cc5
00000a0 0404 0000 2e00 7973 746d 6261 2e00 7473
00000b0 7472 6261 2e00 6873 7473 7472 6261 2e00
00000c0 6574 7478 2e00 6164 6174 2e00 7362 0073
00000d0 632e 6d6f 656d 746e 2e00 6f6e 6574 472e
00000e0 554e 732d 6174 6b63 2e00 6572 2e6c 6865
00000f0 665f 6172 656d 0000 0000 0000 0000 0000
0000100 0000 0000 0000 0000 0000 0000 0000 0000
*
0000120 001b 0000 0001 0000 0006 0000 0000 0000
0000130 0034 0000 0005 0000 0000 0000 0000 0000
0000140 0001 0000 0000 0000 0021 0000 0001 0000
0000150 0003 0000 0000 0000 003c 0000 0004 0000
0000160 0000 0000 0000 0000 0004 0000 0000 0000
0000170 0027 0000 0008 0000 0003 0000 0000 0000
0000180 0040 0000 0000 0000 0000 0000 0000 0000
0000190 0001 0000 0000 0000 002c 0000 0001 0000
00001a0 0030 0000 0000 0000 0040 0000 002c 0000
00001b0 0000 0000 0000 0000 0001 0000 0001 0000
00001c0 0035 0000 0001 0000 0000 0000 0000 0000
00001d0 006c 0000 0000 0000 0000 0000 0000 0000
00001e0 0001 0000 0000 0000 0049 0000 0001 0000
00001f0 0002 0000 0000 0000 006c 0000 0038 0000
0000200 0000 0000 0000 0000 0004 0000 0000 0000
0000210 0045 0000 0009 0000 0000 0000 0000 0000
0000220 0368 0000 0008 0000 0009 0000 0006 0000
0000230 0004 0000 0008 0000 0011 0000 0003 0000
0000240 0000 0000 0000 0000 00a4 0000 0053 0000
0000250 0000 0000 0000 0000 0001 0000 0000 0000
0000260 0001 0000 0002 0000 0000 0000 0000 0000
0000270 02b0 0000 00a0 0000 000a 0000 0009 0000
0000280 0004 0000 0010 0000 0009 0000 0003 0000
0000290 0000 0000 0000 0000 0350 0000 0015 0000
00002a0 0000 0000 0000 0000 0001 0000 0000 0000
00002b0 0000 0000 0000 0000 0000 0000 0000 0000
00002c0 0001 0000 0000 0000 0000 0000 0004 fff1
00002d0 0000 0000 0000 0000 0000 0000 0003 0001
00002e0 0000 0000 0000 0000 0000 0000 0003 0002
00002f0 0000 0000 0000 0000 0000 0000 0003 0003
0000300 0009 0000 0000 0000 0004 0000 0001 0002
0000310 0000 0000 0000 0000 0000 0000 0003 0005
0000320 0000 0000 0000 0000 0000 0000 0003 0006
0000330 0000 0000 0000 0000 0000 0000 0003 0004
0000340 0010 0000 0000 0000 0005 0000 0012 0001
0000350 6800 6c65 6f6c 632e 6100 312e 3733 0030
0000360 616d 6e69 0000 0000 0020 0000 0202 0000
0000370
因为我的机器是32的ub,所以用以下格式来解析文件头
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
关于声明类型的定义如下:
大小
对齐
用途
Elf32_Addr
4
4
无符号程序地址
Elf32_Half
2
2
无符号中等大小整数
Elf32_Off
4
4
无符号文件偏移
Elf32_Sword
4
4
有符号大整数
Elf32_Word
4
4
无符号大整数
unsigned char
1
1
无符号小整数
解析结果如下:
magic: 7f45 4c46 0101 0100 0000 0000 0000 0000
type: 0x01
machine: 0x03
version: 0x01
entry: 0
phoff: 0
shoff: 0xf8
flags: 0
ehsize: 0x34
phentsize: 0x0
phnum: 0
shentsize: 0x28
shnum: 0x0b
shstrndx: 0x08
同elfread -h hello.o得到的结果对比
ELF 头:
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 (可重定位文件)
Machine: Intel 80386
Version: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 248 (bytes into file)
标志: 0x0
本头的大小: 52 (字节)
程序头大小: 0 (字节)
Number of program headers: 0
节头大小: 40 (字节)
节头数量: 11
字符串表索引节头: 8
结果是对的,只不过elfread对各个值进一步解析出来了。
看完的header再看后面的节头信息
直接调用elfread -S hello.o查看:
节头:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000005 00 AX 0 0 1
[ 2] .data PROGBITS 00000000 00003c 000004 00 WA 0 0 4
[ 3] .bss NOBITS 00000000 000040 000000 00 WA 0 0 1
[ 4] .comment PROGBITS 00000000 000040 00002c 01 MS 0 0 1
[ 5] .note.GNU-stack PROGBITS 00000000 00006c 000000 00 0 0 1
[ 6] .eh_frame PROGBITS 00000000 00006c 000038 00 A 0 0 4
[ 7] .rel.eh_frame REL 00000000 000368 000008 08 9 6 4
[ 8] .shstrtab STRTAB 00000000 0000a4 000053 00 0 0 1
[ 9] .symtab SYMTAB 00000000 0002b0 0000a0 10 10 9 4
[10] .strtab STRTAB 00000000 000350 000015 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
在这里我们以.text和.symtab以及.strtab为例
根据节头信息,我们找到0x34处长度为5的一段指令:
55 89 e5 5d c3
同objdump -d hello.o得到反汇编对比:
hello.o: 文件格式 elf32-i386
Disassembly of section .text:
00000000 :
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 5d pop %ebp
4: c3 ret
完全一致。
再看符号表symtab,在0x2b0处,长度为0xa0
符号表的表头结构定义如下:
typedef struct {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char type:4,
binding:4;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
一个符号结构体占16个字节, 根据长度可知hello.o共有10个符号,用readelf -s hello.o查看符号表结果如下:
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS hello.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 2
4: 00000000 0 SECTION LOCAL DEFAULT 3
5: 00000000 4 OBJECT LOCAL DEFAULT 2 a.1370
6: 00000000 0 SECTION LOCAL DEFAULT 5
7: 00000000 0 SECTION LOCAL DEFAULT 6
8: 00000000 0 SECTION LOCAL DEFAULT 4
9: 00000000 5 FUNC GLOBAL DEFAULT 1 main
完全正确。
挑出最后一个符号来看,原数据为0x340处,长度为16字节:
0010 0000 0000 0000 0005 0000 0012 0001
根据结构体结构可以知道st_name 为0x10,即在字符表strtab的0x10偏移处。
字符表strtab在0x350处,长度为0x15:
6800 6c65 6f6c 632e 6100 312e 3733 0030 616d 6e69 00
其中"00"为字符串结尾, 所以我们可以读出最后一个字符恰好偏移为0x10:
6d 61 69 6e 00
前四个字节表示的ascii为"m", "a", "i", "n", 即符号表的最后一个符号“main”。
新开公众号“码家村”,欢迎关注