解析ELF符号
1.elf基本概念
ELF 是 Executable and Linking Format 的缩写,其类型定义在Elf64_Ehdr中,字段名为e_type,主要有3种类型
- 可重定位文件(ET_REL) 即所谓的目标文件,在这些文件中,如果引用到其它目标文件或库文件中定义的符号(变量或者函数)的话,只是给出一个名字,这里还并不知道这个符号在哪里,其 具体的地址是什么。需要在连接的过程中,把对这些外部符号的引用重新定位到其 真正定义的位置上,所以称目标文件为“可重定位”或者“待重定位”的。
- 共享目标文件(ET_DYN) 即动态连接库文件。它在以下两种情况下 被使用:第一,在连接过程中与其它动态链接库或可重定位文件一起构建新的目标文件;第二,在可执行文件被加载的过程中,被动态链接到新的进程中,成为运行代码的一部分。
- 可执行文件(ET_EXEC) 即正常的可执行文件。
不同视角下的elf
ELF的作用有两个,一是用于构建程序,构建动态链接库或都可执行程序,主要体现在连接的过程;二是用于运行程序。在这两种情况下,我们可以从不同的视角来看待同一个目标文件。对于同一个目标文件,当它分别被用于连接和用于执行的时候,其特性是不一样的,我们所关注的内容也不一样。在Elf64_Ehdr的定义中,e_phoff和e_shoff分别指定了段头表和节头表的偏移量。
1.连接视图
这个视图主要用于构建程序、构建动态链接库或者可执行文件,这时候关注的是**节(section)**,主要关心的时符号的解析。
2.运行视图
这个视图下是准备执行时使用,加载器只需要按照段头表描述的那样,将对应的段(segment)加载进内存,并按照要求设置好段的权限并最终跳转到e_entry所指定的位置执行即可。
ELF文件头定义
Elf64_Ehdr定义在elf.h文件中,e_ident到e_version都是一些元信息,文件头包含的比较重要的信息就是程序头表(段头表)和节头表的偏移和对应表项的大小。对于节头表,有一个比较重要的信息就是字符串节表在整个节表中的索引,字符串里面存储了节名称的字符串,而每个节的定义里有一个sh_name 指向这个字符串表里的一个字符串,每个字符串以NULL结尾,对于解析ELF,找到字符串表是定位指定节的关键。
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
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;
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;
2.解析函数符号的地址
按照elf的基本概念,要解析函数符号的地址,就要从连接视图来看ELF文件,即要分析ELF的节表(section。
//这里假设ELF文件的已经被mmap到内存中,且其起始地址为h
Elf64_Ehdr *h;
// 这里获取节头表的偏移并使Shdr指向具体的地址
void *Shdr = (char *)h + h->e_shoff;
// 这里获取节的字符串表的具体地址,e_shstrndx为索引,所以先把Shdr转成Elf64_Shdr*
char * sectionStringTable = (char *)h + ((Elf64_Shdr *) Shdr + h-> e_shstrndx)->sh_offset;
在ELF的规范中,名字为**.strtab**的节存储了符号表的字符串,接下来需要遍历节表找到这个节
char * symtabStringTable = NULL;
// 遍历
for (int i = 0; i < h->e_shnum; i++)
{
Elf64_Shdr *s = (Elf64_Shdr *)(Shdr+i*h->e_shentsize);
// .strtab节
if(strcmp(sectionStringTable+s->sh_name,".strtab")==0){
// 记录字符串表的地址
symtabStringTable = (char *)h + s->sh_offset;
}
}
在ELF的规范中,名字为**.symtab为的节(Elf64_Shdr的sh_type为SHT_SYMTAB)**存储了符号信息,接下来需要遍历节表找到这个节,遍历即可
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;//符号表的表项定义
for (int i = 0; i < h->e_shnum; i++)
{
Elf64_Shdr *s = (Elf64_Shdr *)(Shdr+i*h->e_shentsize);
//找到.symtab节
if(s->sh_type == SHT_SYMTAB){
printf("seciton[%d] : %s \n",i,sectionStringTable+s->sh_name);
//获取symtable的首地址
Elf64_Sym * symtable = (Elf64_Sym*)((char *)h + s->sh_offset);
//遍历symtable表
for (int i = 0; i < s->sh_size/s->sh_entsize; i++,symtable++)
{
//符号类型是函数
if(ELF64_ST_TYPE(symtable->st_info) == STT_FUNC)
{
//st_value即是函数在ELF文件中的偏移量,st_name为函数名在.strtab的字符串表中的索引
printf("fun 0x%07lx \t %s\n",symtable->st_value, symtabStringTable+symtable->st_name);
}
}
}
}
完整的实现代码
#include <sys/mman.h>
#include <elf.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
void dumpSymbols(Elf64_Ehdr *h){
void *Shdr = (char *)h + h->e_shoff;
char * sectionStringTable = (char *)h + ((Elf64_Shdr *) Shdr + h-> e_shstrndx)->sh_offset;
char * symtabStringTable = NULL;
char * dynsymStringTable = NULL;
// 遍历section表
for (int i = 0; i < h->e_shnum; i++)
{
Elf64_Shdr *s = (Elf64_Shdr *)(Shdr+i*h->e_shentsize);
if(s->sh_type == SHT_STRTAB){
if(strcmp(sectionStringTable+s->sh_name,".strtab")==0){
symtabStringTable = (char *)h + s->sh_offset;
}
if(strcmp(sectionStringTable+s->sh_name,".dynstr")==0){
dynsymStringTable = (char *)h + s->sh_offset;
}
}
}
for (int i = 0; i < h->e_shnum; i++)
{
/* code */
Elf64_Shdr *s = (Elf64_Shdr *)(Shdr+i*h->e_shentsize);
if(s->sh_type == SHT_SYMTAB){
printf("seciton[%d] : %s \n",i,sectionStringTable+s->sh_name);
Elf64_Sym * symtable = (Elf64_Sym*)((char *)h + s->sh_offset);
for (int i = 0; i < s->sh_size/s->sh_entsize; i++,symtable++)
{
if(ELF64_ST_TYPE(symtable->st_info) == STT_FUNC)
printf("fun 0x%07lx \t %s\n",symtable->st_value, symtabStringTable+symtable->st_name);
}
}
}
}
int main(int argc, char const *argv[])
{
if (argc < 2)
{
fprintf(stderr, "Usage: %s file\n", argv[0]);
exit(1);
}
int fd = open(argv[1],O_RDONLY);
assert(fd > 0);
// 把elf mmap进来
Elf64_Ehdr *h = mmap(NULL, 40960, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
dumpSymbols(h);
return 0;
}
运行结果,和readelf的对应的结果一致
# f19 @ F19999 in ~/jyy/elf-load [16:14:03]
$ gcc myreadelf.c -o read
# f19 @ F19999 in ~/jyy/elf-load [16:14:05]
$ ./read read
seciton[28] : .symtab
fun 0x0001170 deregister_tm_clones
fun 0x00011a0 register_tm_clones
fun 0x00011e0 __do_global_dtors_aux
fun 0x0001220 frame_dummy
fun 0x0001000 _init
fun 0x0001580 __libc_csu_fini
fun 0x0001229 dumpSymbols
fun 0x0001588 _fini
fun 0x0000000 mmap@@GLIBC_2.2.5
fun 0x0000000 printf@@GLIBC_2.2.5
fun 0x0000000 __assert_fail@@GLIBC_2.2.5
fun 0x0000000 close@@GLIBC_2.2.5
fun 0x0000000 __libc_start_main@@GLIBC_2.2.5
fun 0x0000000 strcmp@@GLIBC_2.2.5
fun 0x0000000 fprintf@@GLIBC_2.2.5
fun 0x0001510 __libc_csu_init
fun 0x0001140 _start
fun 0x000143b main
fun 0x0000000 open@@GLIBC_2.2.5
fun 0x0000000 exit@@GLIBC_2.2.5
fun 0x0000000 __cxa_finalize@@GLIBC_2.2.5
# f19 @ F19999 in ~/jyy/elf-load [16:14:15]
$
# f19 @ F19999 in ~/jyy/elf-load [16:15:16] C:1
$ readelf -s read
Symbol table '.dynsym' contains 15 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mmap@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __assert_fail@GLIBC_2.2.5 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND close@GLIBC_2.2.5 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strcmp@GLIBC_2.2.5 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fprintf@GLIBC_2.2.5 (2)
9: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND open@GLIBC_2.2.5 (2)
11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.2.5 (2)
12: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
13: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2)
14: 0000000000004020 8 OBJECT GLOBAL DEFAULT 26 stderr@GLIBC_2.2.5 (2)
Symbol table '.symtab' contains 75 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000318 0 SECTION LOCAL DEFAULT 1
2: 0000000000000338 0 SECTION LOCAL DEFAULT 2
3: 0000000000000358 0 SECTION LOCAL DEFAULT 3
4: 000000000000037c 0 SECTION LOCAL DEFAULT 4
5: 00000000000003a0 0 SECTION LOCAL DEFAULT 5
6: 00000000000003c8 0 SECTION LOCAL DEFAULT 6
7: 0000000000000530 0 SECTION LOCAL DEFAULT 7
8: 00000000000005e6 0 SECTION LOCAL DEFAULT 8
9: 0000000000000608 0 SECTION LOCAL DEFAULT 9
10: 0000000000000628 0 SECTION LOCAL DEFAULT 10
11: 0000000000000700 0 SECTION LOCAL DEFAULT 11
12: 0000000000001000 0 SECTION LOCAL DEFAULT 12
13: 0000000000001020 0 SECTION LOCAL DEFAULT 13
14: 00000000000010b0 0 SECTION LOCAL DEFAULT 14
15: 00000000000010c0 0 SECTION LOCAL DEFAULT 15
16: 0000000000001140 0 SECTION LOCAL DEFAULT 16
17: 0000000000001588 0 SECTION LOCAL DEFAULT 17
18: 0000000000002000 0 SECTION LOCAL DEFAULT 18
19: 0000000000002064 0 SECTION LOCAL DEFAULT 19
20: 00000000000020b0 0 SECTION LOCAL DEFAULT 20
21: 0000000000003d80 0 SECTION LOCAL DEFAULT 21
22: 0000000000003d88 0 SECTION LOCAL DEFAULT 22
23: 0000000000003d90 0 SECTION LOCAL DEFAULT 23
24: 0000000000003f80 0 SECTION LOCAL DEFAULT 24
25: 0000000000004000 0 SECTION LOCAL DEFAULT 25
26: 0000000000004020 0 SECTION LOCAL DEFAULT 26
27: 0000000000000000 0 SECTION LOCAL DEFAULT 27
28: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
29: 0000000000001170 0 FUNC LOCAL DEFAULT 16 deregister_tm_clones
30: 00000000000011a0 0 FUNC LOCAL DEFAULT 16 register_tm_clones
31: 00000000000011e0 0 FUNC LOCAL DEFAULT 16 __do_global_dtors_aux
32: 0000000000004028 1 OBJECT LOCAL DEFAULT 26 completed.8061
33: 0000000000003d88 0 OBJECT LOCAL DEFAULT 22 __do_global_dtors_aux_fin
34: 0000000000001220 0 FUNC LOCAL DEFAULT 16 frame_dummy
35: 0000000000003d80 0 OBJECT LOCAL DEFAULT 21 __frame_dummy_init_array_
36: 0000000000000000 0 FILE LOCAL DEFAULT ABS myreadelf.c
37: 000000000000205c 5 OBJECT LOCAL DEFAULT 18 __PRETTY_FUNCTION__.4084
38: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
39: 00000000000021d4 0 OBJECT LOCAL DEFAULT 20 __FRAME_END__
40: 0000000000000000 0 FILE LOCAL DEFAULT ABS
41: 0000000000003d88 0 NOTYPE LOCAL DEFAULT 21 __init_array_end
42: 0000000000003d90 0 OBJECT LOCAL DEFAULT 23 _DYNAMIC
43: 0000000000003d80 0 NOTYPE LOCAL DEFAULT 21 __init_array_start
44: 0000000000002064 0 NOTYPE LOCAL DEFAULT 19 __GNU_EH_FRAME_HDR
45: 0000000000003f80 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_
46: 0000000000001000 0 FUNC LOCAL DEFAULT 12 _init
47: 0000000000001580 5 FUNC GLOBAL DEFAULT 16 __libc_csu_fini
48: 0000000000001229 530 FUNC GLOBAL DEFAULT 16 dumpSymbols
49: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
50: 0000000000004000 0 NOTYPE WEAK DEFAULT 25 data_start
51: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 25 _edata
52: 0000000000001588 0 FUNC GLOBAL HIDDEN 17 _fini
53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mmap@@GLIBC_2.2.5
54: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
55: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __assert_fail@@GLIBC_2.2.
56: 0000000000000000 0 FUNC GLOBAL DEFAULT UND close@@GLIBC_2.2.5
57: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
58: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start
59: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strcmp@@GLIBC_2.2.5
60: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fprintf@@GLIBC_2.2.5
61: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
62: 0000000000004008 0 OBJECT GLOBAL HIDDEN 25 __dso_handle
63: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used
64: 0000000000001510 101 FUNC GLOBAL DEFAULT 16 __libc_csu_init
65: 0000000000004030 0 NOTYPE GLOBAL DEFAULT 26 _end
66: 0000000000001140 47 FUNC GLOBAL DEFAULT 16 _start
67: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 26 __bss_start
68: 000000000000143b 208 FUNC GLOBAL DEFAULT 16 main
69: 0000000000000000 0 FUNC GLOBAL DEFAULT UND open@@GLIBC_2.2.5
70: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@@GLIBC_2.2.5
71: 0000000000004010 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__
72: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
73: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@@GLIBC_2.2
74: 0000000000004020 8 OBJECT GLOBAL DEFAULT 26 stderr@@GLIBC_2.2.5