ELF分析常用指令,方便查询。
按ELF段、表分类
十六进制查看
1 2 objdump elffile -s -j section readelf elffile -x section
ELF文件头
1 2 readelf elffile -S objdump elffile -h
符号表
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。
动态链接表
动态链接表指.dynamic段。
按命令分类
nm
打印ELF文件的符号,需要有符号表。如果文件被strip了,则无法获取符号。默认按照bsd格式打印,即对每个符号会打印符号地址、符号类型、符号名。符号类型及其说明,参考man手册:https://www.man7.org/linux/man-pages/man1/nm.1.html。
命令格式:
常用选项:
-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 。
命令格式:
常用选项如下:
-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-littlearm和elf32-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
用二进制对比工具对比nomain和nomain.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