GNU开发工具——GNU Binutils快速入门

一、GNU Binutils简介

GNU Binutils(GNU二进制工具集),即GNU Binary Utilities,是一套用于创建、管理和维护二进制目标文件的工具集合,包括addr2line、ar、gprof、nm、objcopy、objdump、ranlib、size、strings、strip。
Binutils官网地址:https://www.gnu.org/software/binutils/

二、GNU Binutils工具使用

1、addr2line

addr2line用于将可执行文件中代码地址转换为文件名和行号,可执行文件必须带调试信息。
addr2line [-a| --addresses ] [-b bfdname | --target=bfdname] [-C | --demangle[=style]] [-e filename | --exe=filename] [-f | --function] [-s | --basename] [-i | --inlines] [-p | --pretty-print] [-j | --section=name] [-H | --help] [-V | --version] [addr addr ...]
-a --addresses:在函数名、文件和行号信息前,显示地址,以十六进制形式。
-b --target=<bfdname>:指定目标文件格式为bfdname。
-e --exe=<executable>:指定需要转换地址的可执行文件名。
-i --inlines: 如果需要转换的地址是内联函数,则输出的信息包括其最近范围内的一个非内联函数的信息。
-j --section=<name>:给出的地址代表指定section的偏移,而非绝对地址。
-p --pretty-print:使得函数输出信息更加人性化:每一个地址的信息占一行。
-s --basenames:仅仅显示每个文件名的基址(即不显示文件的具体路径,只显示文件名)。
-f --functions:在显示文件名、行号输出信息的同时显示函数名信息。
-C --demangle[=style]:将低级别的符号名解码为用户级别的名字。
-h --help:输出帮助信息。
-v --version:输出版本号。
在程序执行过程中出现崩溃时,addr2line可用于快速定位出错位置,进而找出代码的bug。
GNU开发工具——GNU Binutils快速入门
编译:
gcc -g test.c -o test
开启core dump:
ulimit -c unlimited
运行程序,当前目录下生产core文件:
./test
读取core文件,获取IP寄存器值
demsg core.733
GNU开发工具——GNU Binutils快速入门
ip(指令指针寄存器)字段后的数字是test程序出错时程序执行的位置。
定位代码行:
addr2line -a 00000000004004fd -f -p -C -e test
GNU开发工具——GNU Binutils快速入门
定位至test.c文件第7行。

2、ar

ar用于创建、修改静态库文件,从静态库中提取单个模块等。
ar [-]p[mod] [--plugin name] [--target bfdname] [relpos] [count] archive [member...]
操作模式:
d:从库中删除模块。按模块原来的文件名指定要删除的模块。
m:在一个库中移动成员。当库中如果有若干模块有相同的符号定义(如函数定义),则成员的位置顺序很重要。如果没有指定任选项,任何指定的成员将移到库的最后。可以使用'a','b',或'I'操作选项移动到指定的位置。
p:显示库中指定的成员到标准输出。如果指定任选项v,则在输出成员的内容前,将显示成员的名字。如果没有指定成员的名字,所有库中的文件将显示出来。
q:快速追加。增加新模块到库的结尾处,不会检查是否需要替换。
r:在库中插入模块(替换)。如果插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,不会替换 其它同名模块。
t:显示库的模块表清单。
x:从库中提取一个成员。如果不指定要提取的模块,则提取库中所有的模块。
操作选项:
a:在库的一个已经存在的成员后面增加一个新的文件。如果使用选项a,则应该为命令行中membername参数指定一个已经存在的成员名。
b:在库的一个已经存在的成员前面增加一个新的文件。如果使用选项b,则应该为命令行中membername参数指定一个已经存在的成员名。
c:创建一个静态库。不管库是否存在,都将创建。
f:在库中截短指定的名字。缺省情况下,文件名的长度是不受限制的,可以使用f参数将文件名截短,以保证与其它系统的兼容。
i:在库的一个已经存在的成员前面增加一个新的文件。如果使用选项i,则应该为命令行中membername参数指定一个已经存在的成员名。
N:与count参数一起使用,在库中有多个相同的文件名时指定提取或输出的个数。
o:当提取成员时,保留成员的原始数据。如果不指定o选项,则提取出的模块的时间将标为提取出的时间。
P:进行文件名匹配时使用全路径名。
s:写入一个目标文件索引到库中,或者更新一个存在的目标文件索引。
S:不创建目标文件索引,在创建较大的库时能加快时间。
u:插入列出文件中比库中同名文件新的文件,只用于r操作选项。
v:用来显示执行操作选项的附加信息。
V:显示ar的版本.
ar crt libtest.a test.o

3、c++filt

C++编译器通过name mangling机制支持C++函数重载,因此经过C++编译器编译后的函数符号与C++函数名称并不一致。
c++filt是用于查找经过name mangling后的C++函数符号的原本函数名称的工具。
GNU开发工具——GNU Binutils快速入门

4、nm

nm用于列出目标文件中的符号,输出结果包含地址、段、标识符。

nm [-A|-o|--print-file-name] [-a|--debug-syms]
          [-B|--format=bsd] [-C|--demangle[=style]]
          [-D|--dynamic] [-fformat|--format=format]
          [-g|--extern-only] [-h|--help]
          [-l|--line-numbers] [-n|-v|--numeric-sort]
          [-P|--portability] [-p|--no-sort]
          [-r|--reverse-sort] [-S|--print-size]
          [-s|--print-armap] [-t radix|--radix=radix]
          [-u|--undefined-only] [-V|--version]
          [-X 32_64] [--defined-only] [--no-demangle]
          [--plugin name] [--size-sort] [--special-syms]
          [--synthetic] [--target=bfdname]
          [objfile...]

-a或--debug-syms:显示调试符号。
-B:等同于--format=bsd,用来兼容MIPS的nm。
-C或--demangle:将低级符号名解码(demangle)成用户级名字,可以使得C++函数名具有可读性。
-D或--dynamic:显示动态符号。
-f format:使用format格式输出。format可以选取bsd、sysv或posix,该选项在GNU的nm中有用。默认为bsd。
-g或--extern-only:仅显示外部符号。
-n、-v或--numeric-sort:按符号对应地址的顺序排序,而非按符号名的字符顺序。
-p或--no-sort:按目标文件中遇到的符号顺序显示,不排序。
-P或--portability:使用POSIX.2标准输出格式代替默认的输出格式。等同于使用任选项-f posix。
-s或--print-armap:当列出库中成员的符号时,包含索引。索引的内容包含:哪些模块包含哪些名字的映射。
-r或--reverse-sort:反转排序的顺序(例如,升序变为降序)。
--size-sort:按大小排列符号顺序。该大小是按照一个符号的值与它下一个符号的值进行计算的。
-t radix或--radix=radix:使用radix进制显示符号值。radix只能为“d”表示十进制、“o”表示八进制或“x”表示十六进制。
--target=bfdname:指定一个目标代码的格式,而非使用系统的默认格式。
-u或--undefined-only:仅显示没有定义的符号(那些外部符号)。
-l或--line-numbers:对每个符号,使用调试信息来试图找到文件名和行号。对于已定义的符号,查找符号地址的行号。对于未定义符号,查找指向符号重定位入口的行号。如果可以找到行号信息,显示在符号信息之后。
-V或--version:显示nm的版本号。
GNU开发工具——GNU Binutils快速入门
段标识符如下:
A:地址值在链接过程中不会发生改变
B/b:标识符位于未初始化数据段(.bss)
C:未定义存储段的标识符,链接时决定段位置
D/d:标识符位于数据段(.data)
N:调试专用标识符
R/r:标识符位于只读存储区(.rdata)
T/t:标识符位于代码段(.text)
U:未定义标识符

5、objcopy

objcopy用于从源目标文件拷贝内容到目标文件,用于不同格式的二进制文件的转换。
objcopy [option] infile [oufile]
infile:源目标文件
outfile:输出目标文件
-I bfdname,--input-target=bfdname:指定源目标文件格式,如果不指定,objcopy会自己分析源目标文件的格式
-O bfdname,--output-target=bfdname:指定输出目标文件格式
-S|--strip-all:不从源目标文件拷贝重定位和调试信息
-g|--strip-debug:不从源目标文件拷贝调试符号或分区信息
-K symbolname|--keep-symbol=symbolname:保留指定符号
-N symbolname|--strip-symbol=symbolname:移除指定符号
-x,--discard-all:不从源目标文件拷贝非全局符号
-R sectionpattern,--remove-section=sectionpattern:
--gap-fill=val:使用val填充段与段间的空闲区域
objcopy -O binary test test.bin
将文件转换成 rawbinary 格式

6、objdump

objdump用于查看目标文件或者可执行文件的构成。
参数选项如下:
-a,--archive-headers:显示档案库的成员信息。
-b bfdname,--target=bfdname:指定目标码格式。objdump -i可以列出可以指定的目标码格式列表。
-g,--debugging:显示调试信息。
-e,--debugging-tags:使用ctags格式显示调试信息。
-d,--disassemble:反汇编可执行文件section。
-D,--disassemble-all: 反汇编可执行文件所有section。
--prefix-addresses:反汇编的时候,显示每一行的完整地址。
-EB --endian=big:反汇编时指定大端格式。
-EL --endian=little:反汇编时指定小端格式。
-h,--section-headers --headers:显示目标文件各个section的头部摘要信息。
-H,--help:帮助信息。
-i,--info:显示-b或者-m选项可用的架构和目标格式列表。
-j name,--section=name:仅仅显示指定名称为name的section的信息
-m machine,--architecture=machine:指定反汇编目标文件时使用的架构,如果待反汇编文件本身没描述架构信息时需要指定。
-r,--reloc:显示文件的重定位入口。
-R,--dynamic-reloc:显示文件的动态重定位入口。
-s,--full-contents:显示指定section的完整内容。默认所有的非空section都会被显示。
-S,--source:反汇编出源代码。
--show-raw-insn:反汇编时,显示每条汇编指令对应的机器码,如不指定--prefix-addresses,是缺省选项。
--start-address=address:从指定地址开始显示数据。
--stop-address=address:显示数据直到指定地址为止。
-t,--syms:显示文件的符号表入口。
-T,--dynamic-syms:显示文件的动态符号表入口,仅仅对动态目标文件意义,比如某些共享库。它显示的信息类似于 nm -D|--dynamic 显示的信息。
-V,--version:版本信息
-x,--all-headers:显示所可用的头信息,包括符号表、重定位入口。
-z,--disassemble-zeroes:一般反汇编输出将省略大块的零,本选项会将零块也被反汇编。
objdump -j .data -S test
GNU开发工具——GNU Binutils快速入门

7、readelf

readelf用于显示ELF格式(目标)文件的信息。
-a:--all,显示全部信息,等价于 -h -l -S -s -r -d -V -A -I
-h:--file-header,显示elf文件开始的文件头信息
-S:--section-headers,显示节头信息。
-g:--section-groups,显示节组信息(如果有的话)。
-t:--section-details,显示节的详细信息(-S的)。
-s:--symbols,显示符号表段中的项(如果有的话)。
-e:--headers,显示全部头信息,等价于: -h -l -S
-n:--notes,显示note段(内核注释)的信息。
-r:--relocs,显示可重定位段的信息。
-u:--unwind,显示unwind段信息。当前只支持IA64 ELF的unwind段信息。
-d:--dynamic,显示动态段的信息。
-V:--version-info,显示版本段的信息。
-A:--arch-specific,显示CPU构架信息。
-D:--use-dynamic 使用动态段中的符号表显示符号,而不是使用符号段。
-x <number or name>--hex-dump=<number or name> 以16进制方式显示指定段内内容。number指定段表中段的索引,或字符串指定文件中的段名。
-I:--histogram,显示符号的时候,显示bucket list长度的柱状图。
-v:--version,显示readelf的版本信息。
-H:--help,显示readelf所支持的命令行选项。
-W:--wide,宽行输出。

8、size

size显示目标文件或(.a)档案文件中所有section的大小。

size [-A|-B|--format=compatibility]
            [--help]
            [-d|-o|-x|--radix=number]
            [--common]
            [-t|--totals]
            [--target=bfdname] [-V|--version]
            [objfile...]

-A|-B|--format=compatibility
数据输出格式:-A等价于--format=sysv,-B等价于--format=berkeley。
-d|-o|-x|--radix=number
控制section显示的数据进制格式,-d等价于--radix=10,-o等价于--radix=8,-x等价于--radix=16。
GNU开发工具——GNU Binutils快速入门

9、strings

strings用于显示目标文件中的(可打印)所有字符串信息。
-d,--data:只打印目标文件中的data分区中初始化字符串
GNU开发工具——GNU Binutils快速入门

10、strip

strip用于从可执行文件中有选择地除去行号信息、重定位信息、调试段、typchk段、注释段、文件头以及所有或部分符号表,以实现在不影响程序功能的前提下,减少可执行文件的大小,减少程序的空间占用。
通常只在已经调试和测试过的生成模块上使用strip 命令,使用strip 命令减少对象文件所需的存储量开销。
为了方便定位问题(比如定位 core dump问题), 尽量不要strip,除非存储紧张。工程实践中,若需要对动态库.so进行strip操作,减少占地空间,通常使用strip前的库用来调试, strip后的库用于发布, 两者建立对应关系,一旦发布版出问题,可以找对应的未strip库来定位。
-s|--strip-all:删除所有符号信息
-S|-g|-d|--strip-debug:只删除调试符号信息
-K symbolname |--keep-symbol=symbolname:保留符号
-N symbolname |--strip-symbol=symbolname:移除符号
-o:输出文件
strip -K func test.o