0%

ELF分析常用指令

ELF分析常用指令,方便查询。

按ELF段、表分类

十六进制查看

1
2
objdump elffile -s -j section
readelf elffile -x section

ELF文件头

1
readelf elffile -h

section header table

1
2
readelf elffile -S
objdump elffile -h

program header table

1
readelf execfile -l

符号表

1
2
3
4
readelf execfile -s
objdump execfile -t
objdump execfile -T
nm elffile

如果想定位定义符号的文件和行号,可以使用nm -l,需要有debug信息。nm -S打印符号的大小,nm -S --size-sort按大小排序。如果要缩减ELF的大小,这个非常有用。

代码段(反汇编)

1
2
objdump elffile -d -j section_name
gdb elffile -batch -ex 'disassemble function' # 反汇编指定函数

参考:

数据段

1
objdump elffile -s -j section

重定位表

1
2
3
objdump elffile -r
objdump elffile -R
readelf elffile -r

字符串表

1
readelf elffile -p section

字符串表一般是.strtab.shstrtab.dnystr

动态链接表

1
readelf elffile -d

动态链接表指.dynamic段。

按命令分类

nm

打印ELF文件的符号,需要有符号表。如果文件被strip了,则无法获取符号。默认按照bsd格式打印,即对每个符号会打印符号地址符号类型符号名。符号类型及其说明,参考man手册:https://www.man7.org/linux/man-pages/man1/nm.1.html。

命令格式:

1
nm [option] file...

常用选项:

  • -l 打印定义符号的文件和行号。需要有debug信息。
  • -n 符号地址按数字顺序排序,而不是字母顺序。
  • -p / --no-sort 不排序,按照遇到的顺序打印
  • -S 同时打印符号的地址和大小
  • –size-sort 按照符号大小排序
  • -r 反向排序
  • -t radix 指定进制。d十进制,o八进制,x十六进制。默认x

示例如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 默认打印符号地址、符号类型、符号名
$ nm nomain
0000000000601000 B __bss_start
0000000000601000 B _edata
0000000000601008 B _end
0000000000000000 B errno
00000000004001c0 T _exit
00000000004001c0 W _Exit
00000000004003ab r __func__.1800
0000000000400350 T __libc_disable_asynccancel
00000000004002f0 T __libc_enable_asynccancel
0000000000000000 B __libc_errno
0000000000601000 B __libc_multiple_threads
0000000000400220 T __libc_write
0000000000400180 T nomain
0000000000400220 W write
0000000000400220 W __write
00000000004002c0 T __write_nocancel

# 打印符号大小,并按大小排序
$ nm nomain -S --size-sort
0000000000000000 0000000000000004 B errno
0000000000000000 0000000000000004 B __libc_errno
0000000000601000 0000000000000004 B __libc_multiple_threads
00000000004003ab 0000000000000007 r __func__.1800
00000000004002c0 000000000000002c T __write_nocancel
0000000000400180 000000000000003a T nomain
00000000004002f0 0000000000000056 T __libc_enable_asynccancel
00000000004001c0 0000000000000058 T _exit
00000000004001c0 0000000000000058 W _Exit
0000000000400350 0000000000000059 T __libc_disable_asynccancel
0000000000400220 0000000000000099 T __libc_write
0000000000400220 0000000000000099 W write
0000000000400220 0000000000000099 W __write

size

打印ELF文件每个段的大小和总大小。man手册:size.1.html

命令格式:

1
size [option] file...

常用选项如下:

  • -A 按照SysV格式打印
  • -B 按照Berkeley格式打印。默认使用Berkeley格式。
  • -d 按照十进制打印,等同于 --radix=10。默认十进制。
  • -o 按照八进制打印,等同于 --radix=8
  • -x 按照十六进制打印,等同于 --radix=16
  • -t 显示所有文件的总大小。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# SysV 格式
$ size nomain -A
nomain :
section size addr
.note.gnu.build-id 36 4194648
.text 553 4194688
.rodata 9 4195241
.eh_frame 200 4195256
.tbss 4 6295552
.bss 8 6295552
.comment 41 0
.debug_aranges 48 0
.debug_info 146 0
.debug_abbrev 106 0
.debug_line 67 0
.debug_str 146 0
Total 1364

# Berkeley格式
$ size nomain
text data bss dec hex filename
798 0 12 810 32a nomain

addr2line

地址符号+偏移转换为文件名和行号。需要有debug信息。man手册:addr2line.1.html

命令格式:

1
addr2line [option] addr...

常用选项如下:

  • -e filename 指定文件。默认读取a.out文件。
  • -a 在符号名、文件名、行号之前打印地址。
  • -f 同时打印函数名
  • -s / --basename 只打印文件的基本名字,不打印路径
  • -p 输出更加人性化:每个地址只打印一行

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ addr2line -e nomain 400180
/devspace/test-api/src/nomain.c:6

# 打印符号所在的函数
$ addr2line -e nomain 400180 -f
nomain
/devspace/test-api/src/nomain.c:6

# 打印符号地址
$ addr2line -e nomain 400180 -fa
0x0000000000400180
nomain
/devspace/test-api/src/nomain.c:6

# 将所有内容打印在一行
$ addr2line -e nomain 400180 -fap
0x0000000000400180: nomain at /devspace/test-api/src/nomain.c:6

objcopy

复制和转换目标文件。命令格式如下。如果省略outfile,输出的数据会写入到infile

1
objcopy [option] infile [outfile]

objcopy的功能非常强大,可以转换文件格式;可以将普通文件转换为目标文件;可以基于section对文件进行编辑,例如新增、删除、更新内容、修改信息(lma、vma等)、重命名等;可以基于symbol对文件进行编辑,例如本地符号和全局符号互相转换,弱符号和强符号互相转换,新增符号等。更详细的信息参考man手册objcopy.1.html

常用选项:

  • –info 打印支持的架构和文件格式
  • -I bfdname 指定输入文件的格式,而不是自动检测。BFD是Binary format descriptor的缩写。
  • -O bfdname 指定输出文件的格式。
  • -F bfdname 指定输入文件和输出文件的格式。
  • -B bfdarch 指定架构。通常用于将不含架构信息的文件转换为目标文件,其架构为bfdarch。具体参考后面的示例。
  • -j sectionpattern 只复制指定的段。支持通配符,如果第一个字符是!,表示匹配的段不复制。这个选项可以指定多次。
  • -R sectionpattern 将指定的段从输出文件中移除。支持通配符,如果第一个字符是!,表示匹配的段不移除。这个选项可以指定多次。
  • -S --strip-all 不从源文件复制重定位和符号信息,同时也会删除调试信息。
  • -g --strip-debug 删除调试信息

示例如下:

查询支持的架构和文件格式

首先以列表的形式,列出了文件格式支持的架构。最后以表格的形式,汇总了每种格式支持的架构。行表头是文件格式,列表头是架构。示例输出如下,格式elf32-i386仅支持i386架构。交叉工具链xxx-objcopy --info的输出会有所不同,例如arm-linux-gnueabihf-objcopy支持elf32-littlearmelf32-bigarm格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
$ objcopy --info
BFD header file version (GNU Binutils for Ubuntu) 2.30
elf64-x86-64
(header little endian, data little endian)
i386
elf32-i386
(header little endian, data little endian)
i386
......
binary
(header endianness unknown, data endianness unknown)
i386
l1om
k1om
iamcu
plugin
ihex
(header endianness unknown, data endianness unknown)
i386
l1om
k1om
iamcu
plugin

elf64-x86-64 elf32-i386 elf32-iamcu elf32-x86-64 a.out-i386-linux
i386 elf64-x86-64 elf32-i386 ----------- elf32-x86-64 a.out-i386-linux
l1om ------------ ---------- ----------- ------------ ----------------
k1om ------------ ---------- ----------- ------------ ----------------
iamcu ------------ ---------- elf32-iamcu ------------ ----------------
plugin ------------ ---------- ----------- ------------ ----------------

pei-i386 pei-x86-64 elf64-l1om elf64-k1om elf64-little elf64-big
i386 pei-i386 pei-x86-64 ---------- ---------- elf64-little elf64-big
l1om -------- ---------- elf64-l1om ---------- elf64-little elf64-big
k1om -------- ---------- ---------- elf64-k1om elf64-little elf64-big
iamcu -------- ---------- ---------- ---------- elf64-little elf64-big
plugin -------- ---------- ---------- ---------- elf64-little elf64-big

elf32-little elf32-big pe-x86-64 pe-bigobj-x86-64 pe-i386 plugin srec
i386 elf32-little elf32-big pe-x86-64 pe-bigobj-x86-64 pe-i386 ------ srec
l1om elf32-little elf32-big --------- ---------------- ------- ------ srec
k1om elf32-little elf32-big --------- ---------------- ------- ------ srec
iamcu elf32-little elf32-big --------- ---------------- ------- ------ srec
plugin elf32-little elf32-big --------- ---------------- ------- ------ srec

symbolsrec verilog tekhex binary ihex
i386 symbolsrec verilog tekhex binary ihex
l1om symbolsrec verilog tekhex binary ihex
k1om symbolsrec verilog tekhex binary ihex
iamcu symbolsrec verilog tekhex binary ihex
plugin symbolsrec verilog tekhex binary ihex

转换成二进制格式

对于裸机环境,需要将可执行文件转换为单纯的二进制格式,即不包含额外的描述信息,只有指令数据。参考如下命令,可以转换为二进制格式。

1
objcopy -O binary nomain nomain.bin

用二进制对比工具对比nomainnomain.bin,发现nomain.bin的数据,等于nomain偏移量0x158~0x480的数据。查看nomain的段表,这些地址包含.text.rodata.eh_frame段的数据。

.data段的数据也会复制,但是nomain程序没有.data段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Section Headers:
[Nr] Name Type Address Offset Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0
[ 1] .note.gnu.build-i NOTE 0000000000400158 00000158 0000000000000024 0000000000000000 A 0 0 4
[ 2] .text PROGBITS 0000000000400180 00000180 0000000000000229 0000000000000000 AX 0 0 16
[ 3] .rodata PROGBITS 00000000004003a9 000003a9 0000000000000009 0000000000000000 A 0 0 1
[ 4] .eh_frame PROGBITS 00000000004003b8 000003b8 00000000000000c8 0000000000000000 A 0 0 8
[ 5] .tbss NOBITS 0000000000601000 00001000 0000000000000004 0000000000000000 WAT 0 0 4
[ 6] .bss NOBITS 0000000000601000 00001000 0000000000000008 0000000000000000 WA 0 0 4
[ 7] .comment PROGBITS 0000000000000000 00000480 0000000000000029 0000000000000001 MS 0 0 1
[ 8] .debug_aranges PROGBITS 0000000000000000 000004a9 0000000000000030 0000000000000000 0 0 1
[ 9] .debug_info PROGBITS 0000000000000000 000004d9 0000000000000092 0000000000000000 0 0 1
[10] .debug_abbrev PROGBITS 0000000000000000 0000056b 000000000000006a 0000000000000000 0 0 1
[11] .debug_line PROGBITS 0000000000000000 000005d5 0000000000000043 0000000000000000 0 0 1
[12] .debug_str PROGBITS 0000000000000000 00000618 0000000000000092 0000000000000001 MS 0 0 1
[13] .symtab SYMTAB 0000000000000000 000006b0 00000000000002d0 0000000000000018 14 15 8
[14] .strtab STRTAB 0000000000000000 00000980 00000000000000c3 0000000000000000 0 0 1
[15] .shstrtab STRTAB 0000000000000000 00000a43 000000000000009a 0000000000000000 0 0 1

转换成hex格式

烧录器可能需要hex格式,可以使用如下命令转换。hex格式的文件本质上是一个纯文本,更多信息可以参考wiki Intel_HEX

hex文件自带地址信息,所以烧录时不用指定地址。但binary格式不带地址信息,所以需要指定起始地址。

1
objcopy -O ihex nomain nomain.hex

转换成目标文件

在不带文件系统的环境,但又需要读取某个文件的数据,可以考虑将文件直接嵌入到程序中。先将文件转换为目标文件,最终和代码链接成一个文件。参考如下命令,将任意文件转换为目标文件。输入格式一定要设置为binary,输出格式根据运行环境选择。

1
objcopy -I binary -O elf32-i386 test.mp3 test.mp3.o

创建的目标文件自带三个符号,可以在代码中使用extern关键字声明并使用。

1
2
3
4
5
6
7
8
9
$ readelf -s test.mp3.o

Symbol table '.symtab' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 SECTION LOCAL DEFAULT 1
2: 00000000 0 NOTYPE GLOBAL DEFAULT 1 _binary_test_mp3_start
3: 000004d9 0 NOTYPE GLOBAL DEFAULT 1 _binary_test_mp3_end
4: 000004d9 0 NOTYPE GLOBAL DEFAULT ABS _binary_test_mp3_size