GNU开发工具
为了有效地进行嵌入式开发,至少需要了解和掌握如下几类工具:
编译开发工具:即能够把一个源程序编译生成一个可执行程序的软件,如gcc等。
调试工具:即能够对执行程序进行源码或汇编级调试的软件,如gdb等。
软件工程工具:用于协助多人开发或大型软件项目的管理的软件,如make、cvs等。
具体来说,我们需要对如下软件有一定了解:
(1)GCC
很多人把GCC看成只是一个C编译器,其实GCC是GNU Compiler Collection的简称,目前,GCC可以支持C、C++、ADA、Object C、JAVA、Fortran、PASCAL等多种高级语言。GCC主要包括如下一些工具。
cpp,GNU预处理器
gcc,符合ISO标准的C编译器
g++,符合ISO标准的C++编译器
gcj,gcj是GCC的java前端,可以生成执行速度更快的二进制本地执行码,而不是java byte code。gcj为把java程序编译成机器代码提供了试验性的支持。要做到这点,用户还需要安装相关的java运行时库。
gnat,是GCC的GNU ADA 95前端,该软件包括开发工具、文档及ADA 95编译器。
(2)binutils
binutils是一组二进制工具程序集,它包括addr2line、ar、as、gprof、ld、nm、objcopy、objdump、ranlib、size、strings、strip等工具,是辅助GCC的主要软件。
as,GNU汇编器(Assembler),用于把汇编代码转换成二进制代码,并存放到一个object文件中。
ld,GNU链接器(Linker),主要用于确定相对地址,把多个object文件、起始代码段、库等链接起来,并最终形成一个可执行文件。
addr2line,把执行程序中的地址映射到源文件中的对应行。
ar,创建归档文件(Archive)、修改/替换库中的object文件,向库中添加/提取object文件。
c++filt,解码C++符号名。
nm,列出object文件中的符号名。
objcopy,复制和转换object文件。
objdump,用来显示对象文件的信息。
ranlib,根据归档文件(Archive)中内容建立索引。
readelf,显示elf格式执行文件中的各种信息。
size,显示object文件和执行文件各节(Section)的大小。
strings,显示可执行文件中字符串常量。
strip,去掉执行文件中多余的信息(如调试信息),可以减少执行文件的大小。
gprof,用来显示调用图表档案数据。
(3)gdb
gdb是GNU调试器,它允许调试用C、C++或其它语言编写的程序。它的基本运行方式是在一个shell环境下用命令行方式调试程序和显示数据。如果加上一些图形前端(如DDD等软件),则可以在此一个更方便的图形环境下调试程序。
(4)make
GNU make是一个用来控制可执行程序的生成过程,从其它的源码程序文件中生成可执行程序的GNU工具。GNU make允许用户生成和安装软件包,而无需了解生成、安装软件包的具体执行过程。
(5)diff/diff3/sdiff
diff/diff3/sdiff是比较文本差异的工具,也可以用来产生补丁。
(6)patch
patch是补丁安装程序,可根据diff生成的补丁来更新程序。
(7)CVS(Concurrent Version System)
CVS是一个版本控制系统。它能够记录文件的修改历史(通常但并不总是包括源码)。CVS只存储版本之间的区别,而不是你创建的文件的每一个版本。CVS还保留一个记录改变者、改变时间以及改变原因的日志。CVS对于管理发行版本和控制在多个作者间同时编辑源码文件很有帮助。CVS为一个层次化的目录提供版本控制,目录由修改控制的文件组成,而不是在一个目录中为一组文件提供版本控制。这些目录和文件可以被合并起来构成一个软件发行版本。
第二节 binutils开发工具
binutils是一组二进制工具程序集,它包括addr2line、ar、as、gprof、ld、nm、objcopy、objdump、ranlib、size、strings、strip等工具,下面分别介绍其中一些常用的软件。
nm工具:
nm软件主要功能是列出目标文件中的符号,这样可以帮助程序员定位和分析执行程序和目标文件中的符号信息和它的属性。如果没有目标文件作为参数被列出,nm假定目标文件为”a.out”,通过下面的命令可以得到nm的一般帮助信息#nm -h。
对于每一个符号,nm将显示下面的内容:
符号的值:用某种基数表示的数值。默认情况下用十六进制表示。
符号的类型:至少要用到下面的类型。也可以用其它类型,这依赖于目标文件的格式。
1. A:符号的值是绝对值,并且不会被将来的链接所改变。
2. B:符号位于未初始化数据部分(被认为是BSS)。
3. C:符号是公共的。公共符号是未初始化的数据。在链接时,多个公共符号可能以相同的名字出现。如果符号在其他地方被定义,该符号会被当作未定义的引用来处理。
4. D:符号位于已初始化数据部分。
5. G:符号位于小型对象的已初始化数据部分,相对于大型的全局array,一些对象文件格式允许对小型数据对象更有效的存取,例如全局的int型变量。
6. I:符号是另一个符号的间接引用。这是对很少用到的a.out目标文件格式的GNU扩展。
7. N:符号是调试符号。
8. R:符号位于只读数据部分。
9. S:符号位于小型对象的未初始化数据部分。
10. T:符号位于文本(代码)部分。
11. U:符号未被定义。
12. W:符号是弱定义符号(Weak Symbol),也称弱符号。当一个弱定义符号和一个已经定义的普通符号链接时,使用该已定义的普通符号不会引起错误。当一个未定义的弱符号被链接且该符号未被定义时,该weak符号的值被无错误的变为0。
13. –:符号是一个a.out目标文件中的stabs符号。这种情况下,接下来要打印的值是stabs other域,stabs desc域,以及stab类型。stabs符号用于保留调试信息。
14. ?:符号类型是未知的,或目标文件格式特殊。
15. o:符号名
nm的命令行参数选项的长格式和短格式是等价的,下面列出了可供选择的命令行参数格式。
1. -A/-o/–print-file-name: 在找到的各个符号的名字前加上输入文件名(或归档文件元素),而不是在此文件中的所有符号前只出现一次输入文件名。
2. -a/–debug-syms:显示所有的调试符号,即使是仅用于调试的符号。通常情况下这些符号是不被列出的。
3. -B:等价于“–format=bsd”(为了兼容MIPS nm)。
4. -C/–demangle:将低级符号名解码成用户级名字。另外去除任何由系统预先生成的初始的下划线,这样可以使C++函数名具有可读性。
5. –no-demangle:不解码低级符号名。这是默认的处理方式。
6. -D/–dynamic:显示动态符号而不是普通符号。该选项仅对动态目标文件有意义,例如特定类型的共享库。
7. -f format/–format = format:使用format格式输出,format可以是bsd、sysv或posix。默认为bsd。仅当format的第一个字符是有意义的,可以是大写或小写。
8. -g/–extern-only:只显示外部符号。
9. -l/–line-numbers:对每个符号,使用调试信息去试图找到文件名和行号。对于已定义的符号,查找符号地址的等号。对于未定义符号,查找指向符号重定位入口的行号。如果可以找到行号信息,在其他符号信息之后显示行号信息。
10. -n/-v/–numeric-sort:按符号对应地址的顺序排序,而不是按符号名的字符顺序。
11. -p/–no-sort:不以任何顺序对符号进行排序,按目标文件中遇到的符号顺序显示。
12. -P/–portablility:使用POSIX.2标准输出格式代替默认的输出格式。等价于“-f posix”。
13. -s/–print-armap:当列出归档文件中成员的符号时,包含索引,即名字和包含该名字定义的模块的映射(通过ar或ranlib保存在归档文件中)。
14. -r/–reverse-sort:反转排序的顺序(按数字或字母顺序)显示。
15. –size-sort:按大小排列符号顺序。该大小是按照一个符号的值与它下一个符号的值进行计算的。
16. -t radix/–radix = radix:使用radix进制显示符号值。radix只能为“d”表示十进制、“o”表示八进制或“x”表示十六进制。
17. –target = bfdname:指定一个目标代码的格式,而非使用系统默认格式。
18. -u/–undefined-only:仅显示没有定义的符号(那些每个目标文件的外部符号)。
19. -defined-only:仅显示每个目标文件中已经定义的符号。
20. -V/–version:显示nm的版本号然后退出。
21. –help:显示nm的所有选项然后退出。
下面介绍一个简单的使用,按照我们刚才介绍ar命令时的例子,执行如下命令:
#nm test.o
输出结果如下:
U Add
00000000 T main
U Minus
U printf
执行命令:
#nm add.o
输出结果如下:
00000000 T Add
执行命令:
#nm minus.o
输出结果如下:
00000000 T Minus
命令nm test.o的输出说明了test.o定义了main函数,但没有定义Add、Minus和printf函数符号。命令nm add.o的输出说明add.o定义了Add函数符号。命令nm minus.o的输出说明了minus.o定义了Minus函数符号。test.o中没有定义但使用了printf函数符号,printf函数实际上定义在libc.a库中。
objdump工具:
objdump显示一个或多个目标文件的信息。由其选项来控制显示哪些特定的信息。这些信息只对那些编写编译工具的程序员有帮助,而对那些只想让自己编写的程序编译和运行起来的程序员来说没有更多意义。但在嵌入式系统级开发时,通过这个软件可以方便地查看执行文件或库文件的信息。如我们可以通过objdump软件反汇编执行程序,看到执行程序的汇编格式。
当目标文件是归档文件时,objdump显示的是归档文件中每个成员文件的信息。选项的长格式和短格式是等价的。下面描述了作为可选择的参数格式(除“-l”之外,至少要给出一个参数选项)。
1. -a/–archive-header:如果任何一个由object-file指定的文件是归档文件,则显示该归档文件的头信息(类似于“ls -l”的格式)。除了“ar -tv”能显示的信息外,“objdump -a”还可以显示每个归档文件成员的目标文件格式。
2. –adjust-vma=offset:当转储信息时,首先给所有的节地址加上一个偏移量offset。如果节地址对应不上符号表时,就可以使用该选项。当节被放在特殊的地址,而采用的是一种不能表示节地址的格式,例如a.out,就会发生节地址对应不上符号表的情况。
3. -b bfdname/–target = bfdname:指定目标文件的目标代码格式为bfdname。该选项可能不是必需的,因为objdump能够识别很多格式。如命令“objdump -b oasys -m vax -h fu.o”执行后,将显示节头中的概要信息。“-b oasys”表示用的是Oasys编译器产生的目标文件格式。“-m vax”表示目标文件是VAX计算机上的目标文件。
4. -C/–demangle:将低级符号名解码成用户级名字。另外去除任何由系统预先生成的初始的下划线,这样可以使得C++函数名具有可读性。
5. –debugging:显示调试信息。该选项试图解析保存在文件中的调试信息并且用C语言风格的语法打印这些信息。仅能对特定类型的调试信息实现这个功能。
6. -d/–disassemble:显示目标文件中的机器指令使用的汇编语言。该选项仅仅反汇编那些应该含有指令机器码的节。
7. -D/–disassemble-all:类似于“-d”,但是反汇编所有节的内容。该选项仅对那些应该含有指令机器码的节有意义。
8. –prefix-addresses:反汇编时,打印每行的完整地址。这是一种更古老的反汇编格式。
9. –disassemble-zeroes:通常情况下,反汇编的输出会跳过大块的零。该选项将指引反汇编器去反汇编那些大块的零,就像处理其他数据一样。
10. -EB/-EL/–endian={big|little}:指定目标文件的字节顺序。该选项只会影响反汇编。当反汇编像S-records这样没有描述字节顺序的文件格式时,该选项是有用的。
11. -f/–file-header:显示每个由object-file指定的所有目标文件的文件头概要信息。
12. -h/–section-header/–header:显示目标文件的节头概要信息。ld使用了“-Ttext”、“-Tdata”或“-Tbss”这些选项时,可能会使得目标文件的各个节被重定向到非标准的地址。这样通过这个选项,可以查出目标文件的各节的起始地址。然而,一些像a.out这样的目标文件格式并没有存储文件段的起始地址。在这些情况下,尽管ld正确地重定位了每个节。但是使用“objdump -h”列出文件节头时,并不能显示其正确地址。
13. –help:显示objdump的所有选项的概要信息然后退出。
14. -i/–info:列表显示所有对“-b”或“-m”可用的体系结构和目标格式。
15. -j name/–section=name:只显示由name指定的节。
16. -l/–line-numbers:用对应于目标代码的文件名和行号来标注要显示的信息(使用调试信息),仅仅和-d、-D或-r一起使用才有效。
17. -m machine/–architecture = machine:当反汇编由object-file指定的目标文件时,标明所使用的体系结构。当反汇编一个像S-records这样本身并没有描述体系结构信息的文件的时候,这个选项是有用的。可以用-i选项列出可用的体系结构。
18. -p/–private-headers:显示目标文件格式的特定信息。要显示的信息依赖于目标文件的格式。对于某些目标文件格式,没有附加的信息可供显示。
19. -r/–reloc:显示文件的重定位入口。如果和“-d”或者“-D”一起使用,重定位部分以反汇编后的格式显示出来。
20. -R/-dynamic-reloc:显示文件的动态重定位入口,仅仅对于动态目标文件有意义,例如特定类型的共享库。
21. -s/–full-contents:显示任何指定节的全部内容。
22. -S/–source:尽可能显示与反汇编混和的源代码。隐含了“-d”参数。
23. –show-raw-insn:反汇编机器指令的时候,用十六进制和符号形式同时显示机器指令码。然而并非所有的目标都能这样正确地出来。
24. –no-show-raw-insn:反汇编机器指令的时候,不显示指令类型。这是指定–prefix-addresses选项时的默认设置。
25. –stabs:显示任何指定节的全部内容。显示ELF格式目标文件中的.stab、.stab.index和.stab.excl节的内容。一般用于Solaris操作系统。在其他大部分执行文件格式中,调试符号表入口与链接符号交织在一起,并在打开了“–yms”参数选项时,在objdump的输出中是可见的。
26. –start-address = address:从指定地址开始显示数据,该选项影响打开-d、-r和-s选项时的输出。
27. –stop-address = address:显示数据直到指定地址为止,该选项影响打开-d、-r和-s选项时的输出。
28. -t/–syms:显示文件中的符号表入口。类似于“nm”程序提供的信息。
29. -T/–dynamic-syms:显示文件中的动态符号表入口。仅仅对于动态目标文件有意义,例如特定类型的共享库。类似于打开了“-D”选项的“nm”程序提供的信息。
30. –version:显示objdump的版本号然后退出。
31. -x/–all-header:显示所有可用的头信息,包括符号表和重定位入口,使用“-x”等价于指定了“-a -f -h -r -t”参数。
32. -w/–wide:对超过80列的输出设备指定一些行的格式。
对于刚才生成的test执行文件,我们执行
$objdump -f test
显示执行文件文件头概要信息。
使用“-d”或“-D”参数反汇编生成的目标代码:
$objdump -d add.o
readelf工具:
readelf软件显示一个或多个ELF格式的目标文件信息。可通过各种参数选项来控制readelf软件显示目标文件中的特定信息。
size工具: List section sizes and total size
size显示一个目标文件或者链接库文件中的目标文件的各个段的大小。
1、输出格式
size有两种输出格式,一种为"sysv",另一种为"berkeley",默认为berkeley的格式。第一种格式可以用"-A"或者"–format=sysv"指定,第二种格式用"-B"或"–format=berkeley"指定
2、数字输出格式
有三种格式,octal, decimal及hex,对应的参数为"-o",
"-d"及"-x",也可以用"–radix=8","–radix=10"及"–radix=16"指定
3、汇总多个文件的各个段合计长度
"-t" 或者"–total",合计值将在最后输出。
ar工具: Create, modify, and extract from archives
ar用来管理一种文档。这种文档中可以包含多个其他任意类别的文件。这些被包含的文件叫做这个文档的成员。ar用来向这种文档中添加、删除、解出成员。成员的原有属性(权限、属主、日期等)不会丢失。
实际上通常只有在开发中的目标连接库是这种格式的,所以尽管不是,我们基本可以认为ar是用来操作这种目标链接库(.a文件)的。
1、创建库文件
我不知道怎么创建一个空的库文件。好在这个功能好像不是很需要。通常人们使用“ar cru liba.a a.o"这样的命令来创建一个库并把a.o添加进去。"c"关键字告诉ar需要创建一个新库文件,如果没有指定这个标志则ar会创建一个文件,同时会给出一个提示信息,"u"用来告诉ar如果a.o比库中的同名成员要新,则用新的a.o替换原来的。但是我发现这个参数也是可有可无的,可能是不同版本的ar行为不一样吧。实际上用"ar -r liba.a a.o"在freebsd5上面始终可以成功。
2、加入新成员
使用"ar -r liba.a b.o"即可以将b.o加入到liba.a中。默认的加入方式为append,即加在库的末尾。"r"关键字可以有三个修饰符"a", "b"和"i"。
"a"表示after,即将新成员加在指定成员之后。例如"ar -ra a.c liba.a b.c"表示将b.c加入liba.a并放在已有成员a.c之后;
"b"表示before,即将新成员加在指定成员之前。例如"ar -rb a.c liba.a b.c";
"i"表示insert,跟"b"作用相同。
3、列出库中已有成员
"ar -t liba.a"即可。如果加上"v"修饰符则会一并列出成员的日期等属性。
4、删除库中成员
"ar -d liba.a a.c"表示从库中删除a.c成员。如果库中没有这个成员ar也不会给出提示。如果需要列出被删除的成员或者成员不存在的信息,就加上"v"修饰符。
5、从库中解出成员
"ar -x liba.a b.c"
6、调整库中成员的顺序
使用"m"关键字。与"r"关键字一样,它也有3个修饰符"a","b", "i"。如果要将b.c移动到a.c之前,则使用"ar -mb a.c liba.a b.c"