初识链接与readelf
在《csapp》一书中,我的直观感觉后半本相较于前半本是更深更难的,以至于上课时常无法跟上老师,总得思索一会才得以明白(我的这门课的崩盘就是从捡了一次笔开始的- -)。既然没搞懂,就来跑一跑看他到底是个什么东西~
ELF文件类型
Linux生成的目标文件是标准的ELF文件格式,使用objdump工具和readelf工具可以查看分析elf文件的格式。
而目标文件又有三种形式:
- 可重定位目标文件。包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。如*.a文件
- 可执行目标文件。包含二进制代码和数据,其形式可以被直接复制到内存并执行。如a.out,*.o文件
- 共享目标文件。一种特殊类型的可重定位目标文件,可以在加载或者运行的时候被动态地加载进内存并连接。如*.so文件
ELF文件组成部分
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且他们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。
readelf
- -h
h即head,-h可以显示ELF头中包含的所有信息。
root@lijunting-virtual-machine:/mnt/hgfs/share_vmware# readelf -h sum.o
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
类型: REL (可重定位文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 576 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 0 (字节)
Number of program headers: 0
节头大小: 64 (字节)
节头数量: 11
字符串表索引节头: 10
其中
- 类别:ELF64指这里是64位的ELF格式
- 数据:该参数指文件中数据是二进制存储的小端排序
- OS/ABI : 生成该文件的操作系统类型,ABI(Application Binary Interface)
- 类型:当前文件的类型,EXEC(可执行文件), REL(可重定位文件),DYN(可被共享文件)
- 入口点地址: 程序的虚拟地址入口点,可执行文件之外的类型该值为0x0
- Start of section headers : 段头的开始处,从程序入口地址偏移地址开始
- -s
显示符号表的信息,包含静态符号表和动态符号表,符号表中的信息只包括全局变量和函数名。
/* sum.c */
/* $begin sum */
int sum(int *a, int n)
{
int i, s = 0;
for (i = 0; i < n; i++) {
s += a[i];
}
return s;
}
/* $end sum */
root@lijunting-virtual-machine:/mnt/hgfs/share_vmware# readelf -s sum.o
Symbol table '.symtab' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS sum.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 4
8: 0000000000000000 69 FUNC GLOBAL DEFAULT 1 sum
可以看出,函数名会出现在符号表中,而局部变量不会。
- -S
在课上我只了解到了-h和-s,但在实验时忘记了,误打误撞输入了大写的S(才不是笔记写的潦草看成了S),发现-S与-s的功能有所不同,查找资料后知道,-S是显示节区表内的所有信息,包括每个节的属性。
root@lijunting-virtual-machine:/mnt/hgfs/share_vmware# readelf -S sum.o
There are 11 section headers, starting at offset 0x240:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000045 0000000000000000 AX 0 0 1
[ 2] .data PROGBITS 0000000000000000 00000085
0000000000000000 0000000000000000 WA 0 0 1
[ 3] .bss NOBITS 0000000000000000 00000085
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .comment PROGBITS 0000000000000000 00000085
000000000000002b 0000000000000001 MS 0 0 1
[ 5] .note.GNU-stack PROGBITS 0000000000000000 000000b0
0000000000000000 0000000000000000 0 0 1
[ 6] .eh_frame PROGBITS 0000000000000000 000000b0
0000000000000038 0000000000000000 A 0 0 8
[ 7] .rela.eh_frame RELA 0000000000000000 000001d0
0000000000000018 0000000000000018 I 8 6 8
[ 8] .symtab SYMTAB 0000000000000000 000000e8
00000000000000d8 0000000000000018 9 8 8
[ 9] .strtab STRTAB 0000000000000000 000001c0
000000000000000b 0000000000000000 0 0 1
[10] .shstrtab STRTAB 0000000000000000 000001e8
0000000000000054 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)
- .text:已编译程序的机器代码。
- .data:已初始化的全局C变量。局部C变量在运行时被保存在栈中,既不出现在.data中,也不出现在.bss节中。
- .bss:未初始化的全局C变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。目标文件格式区分初始化和未初始化变量是为了空间效率在:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。
- .symtab:一个符号表(symbol table),它存放在程序中被定义和引用的函数和全局变量的信息。一些程序员错误地认为必须通过-g选项来编译一个程序,得到符号表信息。实际上,每个可重定位目标文件在.symtab中都有一张符号表。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的表目。
- .strtab:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以null结尾的字符串序列。
readelf中还有许多可以使用的命令符
用法:readelf <选项> elf-文件
显示关于 ELF 格式文件内容的信息
Options are:
-a --all Equivalent to: -h -l -S -s -r -d -V -A -I
-h --file-header Display the ELF file header
-l --program-headers Display the program headers
--segments An alias for --program-headers
-S --section-headers Display the sections' header
--sections An alias for --section-headers
-g --section-groups Display the section groups
-t --section-details Display the section details
-e --headers Equivalent to: -h -l -S
-s --syms Display the symbol table
--symbols An alias for --syms
--dyn-syms Display the dynamic symbol table
-n --notes Display the core notes (if present)
-r --relocs Display the relocations (if present)
-u --unwind Display the unwind info (if present)
-d --dynamic Display the dynamic section (if present)
-V --version-info Display the version sections (if present)
-A --arch-specific Display architecture specific information (if any)
-c --archive-index Display the symbol/file index in an archive
-D --use-dynamic Use the dynamic section info when displaying symbols
-x --hex-dump=<number|name>
Dump the contents of section <number|name> as bytes
-p --string-dump=<number|name>
Dump the contents of section <number|name> as strings
-R --relocated-dump=<number|name>
Dump the contents of section <number|name> as relocated bytes
-z --decompress Decompress section before dumping it
-w[lLiaprmfFsoRtUuTgAckK] or
--debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
=frames-interp,=str,=loc,=Ranges,=pubtypes,
=gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
=addr,=cu_index,=links,=follow-links]
Display the contents of DWARF debug sections
--dwarf-depth=N Do not display DIEs at depth N or greater
--dwarf-start=N Display DIEs starting with N, at the same depth
or deeper
-I --histogram Display histogram of bucket list lengths
-W --wide Allow output width to exceed 80 characters
@<file> Read options from <file>
-H --help Display this information
-v --version Display the version number of readelf