1.addr2line能够把程序地址转换为文件名和行号,前提是这个可执行文件包括调试符号
1 #include <stdio.h>
2
3 void foo()
4 {
5 printf("The address of foo() is %p\n",foo);
6 }
7 int main()
8 {
9 foo();
10 return 0;
11 }
运行如下命令,得到:
现在,我们可以用这一地址来看看addr2line是如何使用的。在终端中运行如下命令,从命令的运行结果可以看出,addr2line工具正确指出了0x80483c4所对应的程序的具体位置以及所对应的函数名。在调用 addr2line 工具时,要使用 -e 选项来指定可执行映像是 test。通过使用 -f 选项,可以告诉工具输出函数名。
2.nm可以列出目标文件中的符号。用法虽然简单,但是功能很强大。符号是指函数名或变量。
nm所列出的每一行有三部分组成:第一列是指程序运行时的符号所对应的地址,对于函数则地址表示的是函数的开始地址,对于变量则表示变量的存储地址;第二列是指对应符号放在哪一个段;而最后一列则是指符号的名称。在前面我们讲解addr2line时,我们提到addr2line是将程序地址转换成这一地址所对应的具体函数是什么,而nm则是全面的列出这些信息。但是,nm不具备列出符号所在源文件及其行号这一功能,因此,我们说每一个工具有其特定功能。
为了更清楚的理解nm中的符号和我们程序中的关系,我们看一下下列程序其所对应的nm输出结果。
1 #include <stdio.h>
2 int gloable1;
3 int gloable2 = 9;
4
5 static int static_gloable1;
6 static int static_gloable2 = 99;
7
8 void foo()
9 {
10 static int intermal1;
11 static int intermal2 = 999;
12 }
13
14 static void bar()
15 {
16
17 }
18
19 int main()
20 {
21 int local;
22 int local2 = 9999;
23 foo();
24 return 0;
25 }
从nm输出的信息,我们可以看出:
Ø 不论一个静态变量是定义在函数内还是函数外,其在程序段中的分配方式都是一样的。如果这一静态变量是初始化好的,那么被分配在data段中,否则就在bss段中
Ø 非静态的全局变量,其分配的段也是和是否初始化有关。如果被初始化了,分配在data段中,否则就在bss段中。
Ø 函数无论是静态还是非静态的,其总是被分配在text段,但T(t)的大小写代表这一符号所对应的函数是否是静态函数。
Ø 函数内的局部变量并不是分配在data,bss和text段中,其分配在栈上,nm是看不到的。
3. readelf –h test
这条命令查看可执行文件“test”的section的头信息。
每一个头信息都是一个Elf32_Shdr结构,其成员含义如下所述:
typedef struct {
Elf32_Word sh_name; //指定了这个section的名字
Elf32_Word sh_type; //把sections按内容和意义分类
Elf32_Word sh_flags; //sections支持位的标记,用来描述多个属性
Elf32_Addr sh_addr; //该section 在内存中的位置
Elf32_Off sh_offset; //该section的字节偏移量
Elf32_Word sh_size; //该section的字节大小
Elf32_Word sh_link; //该section报头表的索引连接
Elf32_Word sh_info; //保存着额外的信息
Elf32_Word sh_addralign; //地址对齐的约束
Elf32_Word sh_entsize; //保存着一张固定大小入口的表
} Elf32_Shdr;
4.ar用来管理档案文件,在嵌入式系统当中,ar主要用来对静态库进行管理。现在先让我们看看静态库里有些什么。我们采用lib.a为例,对其用ar -t来查看,如下所示:
采用GNU工具集进行开发时,一个静态库其实是将所有的.o文件打成一个档案包。现在,我们就来看看如何使用ar来生成静态库。
foo.c
#include <stdio.h>
void foo()
{
printf("This is foo()\n");
}
bar.c
#include <stdio.h>
void bar()
{
printf("This is bar()\n");
}
我们希望将foo()和bar()函数做成一个库,为此先要将它们编译成.o目标文件。有了目标文件之后,我们采用ar命令来生成libmy.a库,如下所示。其中,ar的c参数表示创建一个档案文件,而r参数表示增加文件到创建的库文件中,s参数是为了生成库索引以提高连接速度。
现在可以在当前目录下看到一个libmy.a文件,这就是我们的静态库。下面验证库是否可用,
#include <stdio.h>
2 int main()
3
4 {
5 foo();
6 bar();
7 return 0;
8 }
编译我们的验证程序,并与libmy.a进行连接,运行程序,从运行的运行结果可以看出,我们的libmy.a是起作用的。
如果想删除档案文件中的文件,我们可以使用d参数。下面是使用d参数删除libmy.a中的foo.o文件。从操作的最后结果看,当执行完d操作后,libmy.a中只存在一个bar.o文件了。
现在总结一下ar的几个参数:采用c参数创建一个档案文件,r参数表示向档案文件增加文件,t参数用于显示档案文件中存在哪些文件,s参数用于指示生成索引以加快查找速度,d参数用于从档案文件中删除文件,最后x参数是用于从档案文件中解压文件。
5.objdump可以显示一个或者更多目标文件的信息,主要用来反汇编。
如下所示(部分未显示):
6.objcopy可以进行目标文件格式转换。
arm-linux-objcopy --gap-fill=0xff -O srec u-boot u-boot.srec
arm-linux-objcopy --gap-fill=0xff -O binary u-boot u-boot.bin
7.size工具很简单,就是列出程序文件中各段的大小。
1 #include <stdio.h>
2 int gloable1;
3 int gloable2 = 9;
4
5 static int static_gloable1;
6 static int static_gloable2 = 99;
7
8 static void foo()
9 {
10 static int intermal1;
11 static int intermal2 = 999;
12 }
13
14 static void bar()
15 {
16 }
17
18 int main()
19 {
20 int local;
21 int local2 = 9999;
22 foo();
23 return 0;
24 }
8.strip用来丢弃目标文件中的全部或者特定符号,减小文件体积。对于嵌入式系统,这个命令必不可少。
9.strings用来打印某个文件的可打印字符串。