来源:http://www.xxlinux.com/linux/article/development/soft/20071214/13352.html
GNU binutils 是一组二进制工具集。包括: addr2line ar gprof nm objcopy objdump ranlib size strings strip. 本文归纳他们的常用法。
ar
ar 用于建立、修改、提取档案文件 (archive) 。 archive 是一个包含多个被包含文件的单一文件(也称之为库文件),其结构保证了可以从中检索并得到原始的被包含文件(称之为 archive 中的 member )。 member 的原始文件内容、模式(权限)、时间戳、所有着和组等属性都被保存在 a rchive 中。 member 被提取后,他们的属性被恢复到初始状态。
ar 主要用于创建 C 库文件
创建静态库
(1) 生成目标文件:
$ gcc -Wall -c file1.c file2.c file3.c |
不用指定生成 .o 文件名 ( 默认生成 file1.o, file2.o, file3.o) 。
(2) 从 .o 目标文件创建静态连接库:
$ ar rv libNAME.a file1.o file2.o file3.o |
ar 生成了 libNAME.a 库,并列出库中的文件。
r : 将 flie1.o, file2,o, file3.o 插入 archive ,如故原先 archive 中已经存在某文件,则先将该文件删除。
v : 显示 ar 操作的附加信息
创建动态库 ( 利用 gcc ,未用 ar)
(1) 生成目标文件
$ gcc -Wall -c -fpic file1.c file2.c file3.c |
-fpic: 指定生成的 .o 目标文件可被重定址 . pic 是 position idependent code 的缩写 : 位置无关代码 .
(2) 生成动态库文件
$ gcc -shared -o libNAME.so file1.o file2.o file3.o |
一般地 , 连接器使用 main() 函数作为程序入口 . 但在动态共享库中没有这样的入口 . 所以就要指定 -shared 选项来避免编译器显示出错信息 .
实际上 , 上述的两条命令可以合并为下面这条 :
$ gcc -Wall -shared -fpic -o libNAME.so file1.c file2.c file3.c |
此后,将 main 函数所在的程序与 libNAME.so 连接
至此,与动态库连接的函数编译成了一个可执行文件。貌似成功了,但还差最后一步。如果直接运行该程序,会给出这样的错误信息 :
error while loading shared libraries: libhello.so: |
这是因为与动态库连接的程序在运行时,首先将该动态库加载到内存中,而 gcc 默认加载动态库文件所在目录为 /usr/local/lib, /usr/lib 。刚才的程序虽然能编译成功,但如果我们自己建立的动态库没有位于默认目录中,则执行时会应为无法找到它而失败。
解决办法:改变加载路径对应的环境变量,然后再执行。
export LD_LIBRARY_PATH= 动态库所在目录 :$LD_LIBRARY_PATH |
查看 archive 内容
$ ar tv archiveNAME |
t : 显示 archive 中 member 的内容,若不指定 member ,则列出所有。
v : 与 t 结合使用时,显示 member 的详细信息。
要想进了解 ar 的详细选项,参考 ar 的 on-line manual
nm
nm 用来列出目标文件中的符号,可以帮助程序员定位和分析执行程序和目标文件中的符号信息和它的属性。
如果没有目标文件作为参数传递给 nm, nm 假定目标文件为 a.out.
这里用一个简单的示例程序来介绍 nm 的用法 :
main.c:
int main(int argc, char *argv[]) |
hello.c:
void hello(void) |
bye.c:
void bye(void) |
运行下列命令 :
$ gcc -Wall -c main.c hello.c bye.c
gcc 生成 main.o, hello.o, bye.o 三个目标文件 ( 这里没有声明函数原型 , 加了 -Wall,gcc 会给出警告 )
$ nm main.o hello.o bye.o
结果显示如下 :
main.o: |
结合这些输出结果 , 以及程序代码 , 可以知道 :
对于 main.o, bye 和 hello 未被定义 , main 被定义了
对于 hello.o, hello 被定义了 , puts 未被定义
对于 bye.o, bye 被定义了 ,puts 未被定义
几个值得注意的问题 :
(1)" 目标文件 " 指 .o 文件 , 库文件 , 最终的可执行文件
.o : 编译后的目标文件,即含有最终编译出的机器码,但它里面所引用的其他文件中函数的内存位置尚未定义 .
(2) 如果用 nm 查看可执行文件 , 输出会比较多 , 仔细研究输出 , 可以对 nm 用法有更清醒的认识 .
(3) 在上述 hello.c, bye.c 中 , 调用的是 printf(), 而 nm 输出中显示调用的是 puts(), 说明最终程序实际调用的 puts(), 如果令 hello.c 或 bye.c 中的 printf() 使用格式化输出 , 则 nm 显示调用 printf(). ( 如 : printf("%d", 1); )
关于 nm 的参数选项 , 参考 on-line manual
objcopy
objcopy 可以将一种格式的目标文件转化为另外一种格式的目标文件 . 它使用 GNU BFD 库进行读 / 写目标文件 . 使用 BFD, objcopy 就能将原格式的目标文件转化为不同格式的目标文件 .
以我们在 nm 中使用的 hello.o 目标文件和 hello 可执行为例 :
$ file hello.o hello |
file 命令用来判别文件类型 , 输出如下 :
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.0, dynamically linked (uses shared libs), not stripped
现在运行 objcopy 来改变 hello 的文件类型 : 原先它是 ELF 格式的可执行程序 , 现将它转换为 srec 格式 . srec 格式文件是 Motolora S-Record 格式的文件 , 主要用来在主机和目标机之间传输数据 .
$ objcopy -O srec hello hello_srec |
file 命令结果 : hello_srec: Motorola S-Record; binary data in text format
注意 objcopy 的格式 , "-O" 指定输出文件类型 ; 输入文件名和输出文件名位于命令末尾 . 关于 objcopy 命令的详细选项 , 参考 on-line manual
objdump
objdump 用来显示目标文件的信息 . 可以通过选项控制显示那些特定信息 . objdump 一个最大的用处恐怕就是将 C 代码反汇编了 . 在嵌入式软件开发过程中 , 也可以用它查看执行文件或库文件的信息 .
下面我们用上文提到的 hello 可执行文件和 hello_srec 可执行文件为例 , 介绍 objdump 的简单用法 :
$ objdump -f hello hello_srec |
输出如下 :
hello: file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x080482c0
hello_srec: file format srec
architecture: UNKNOWN!, flags 0x00000000:
start address 0x00000000080482c0
- f : 显示目标文件的头文件概要信息 .
生成反汇编代码 :
$ objdump -d hello.o |
显示如下 :
hello.o: file format elf32-i386
Disassembly of section .text:
00000000 <hello>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 08 sub $0x8,%esp
6: 83 ec 0c sub $0xc,%esp
9: 68 00 00 00 00 push $0x0
e: e8 fc ff ff ff call f <hello+0xf>
13: 83 c4 10 add $0x10,%esp
16: c9 leave
17: c3 ret
-d : 显示目标文件中机器指令使用的汇编语言 . 只反汇编那些应该含有指令机器码的节 ( 显示 .text 段 ); 如果用 -D, 则反汇编所有节的内容 .
关于 objcopy 命令的详细选项 , 参考 on-line manual
readelf
readelf 用来显示 ELF 格式目标文件的信息 . 可通过参数选项来控制显示哪些特定信息 .( 注意 : readelf 不支持显示 archive 文档 , 也不支持 64 位的 ELF 文件 ).
下面利用先前的 hello 可执行文件演示 readelf 的简单用法 :
$ readelf -h hello |
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x80482c0
Start of program headers: 52 (bytes into file)
Start of section headers: 3848 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 7
Size of section headers: 40 (bytes)
Number of section headers: 34
Section header string table index: 31
注意 : readelf 只能用于 ELF 格式目标文件 , 且选项中至少要指定一个 ( 除 V, H 外 ) 的选项 !
gprof
gprof 被用来测量程序的性能 . 它记录每个函数被调用的次数以及相应的执行时间 . 这样就能锁定程序执行时花费时间最多的部分 , 对程序的优化就可集中于对它们的优化 .
用一个简单的数值计算程序来掩饰 gprof 的用法 :
collatz.c:
#include <stdio.h> |
先将 collatz.c 编译成目标文件 collatz.o , gcc 通过 -pg 选项来打开 gprof 支持:
$ gcc -Wall -c -pg collatz.c |
$ gcc -Wall -pg -o collatz collatz.o |
注意:两条命令都要加 "-pg" 选项。前一条命令生成 collatz.o 目标文件。后一条命令生成可执行文件,该可执行文件中包含了记录函数执行时间的指令。
生成 collatz 可执行文件后,现执行它,结果与一般程序的执行无疑。但此时在 PWD 目录生成一个名为 "gmon.out" 的文件, gprof 通过它来分析程序的执行。
如果不现执行程序,而直接用 gprof 来分析它,会提示 “gmon.out: No such file or directory” 。
gprof 用法:
$ gprof ./collatz |