远程调试环境由宿主机GDB和目标机调试stub共同构成,两者通过串口或TCP连接。使用GDB标准远程串行协议协同工作,实现对目标机上的系统内核和 上 层应用的监控和调试功能。调试stub是嵌入式系统中的一段代码,作为宿主机GDB和目标机调试程序间的一个媒介而存在。
就目前而言,嵌入式 Linux系统中,主要有三种远程调试方法,分别适用于不同场合的调试工作:用ROM Monitor调试目标机程序、用KGDB调试系统内核和用gdbserver调试用户空间程序。这三种调试方法的区别主要在于,目标机远程调试stub 的存在形式的不同,而其设计思路和实现方法则是大致相同的。
而我们最常用的是调试应用程序。就是采用gdb+gdbserver的方式进行调试。在很多情况下,用户需要对一个应用程序进行反复调试,特别是复杂的程 序。采用GDB方法调试,由于嵌入式系统资源有限性,一般不能直接在目标系统上进行调试,通常采用gdb+gdbserver的方式进行调试。 Gdbserver在目标系统中运行,gdb则在宿主机上运行。
要进行GDB调试,目标系统必须包括gdbserver程序,宿主机也必须安装gdb程序。一般linux发行版中都有一个可以运行的gdb,但开发人员 不能直接使用该发行版中的gdb来做远程调试,而要获取gdb的源代码包,针对arm平台作一个简单配置,重新编译得到相应gdb。
下载gdb:如果放到了/home/cby目录:
#cd /home/cby
#tar zxvf gdb-6.6.tar.gz
#cd gdb-6.6
#./configure --target=arm-linux --prefix=/home/cby/arm-gdb
#make
#make install
然后建立gdbserver:
#mkdir /home/cby/gdbserver
#cd ../gdbserver
#chmod +x /home/cby/gdb-6.6/gdb/gdbserver/configure
#CC=arm-linux-gcc ../gdb-6.6/gdb/gdbserver/configure /
--host=arm-linux --prefix=/home/cby/gdbserver
#make
#make install
#arm-linux-strip gdbserver
#cp gdbserver /tftpboot
二、调试步骤
1、交叉编译,带参数-gstabs 或 -g 加入调试信息。
假设要调试的程序为hello.c。
#arm-linux-gcc -g hello.c -o hello
2、在Target Board开启gdbserver
#gdbserver
gdbserver开始监听2345端口(你也可以设其他的值),然后启动hello,你会看到“Process test created:pid=88”
3、回到Host端
#export PATH=$PATH:/home/cby/arm-gdb/bin(arm-linux-gdb的路径)
#arm-linux-gdb hello
最后一行显示:This GDB was configured as “--host=i686-pc-linux-gnu,--target=arm-linux”...
说明此gdb在X86的Host上运行,但是调试目标是ARM代码。
(gdb)target remote
注意:你的端口号必须与gdbserver开启的端口号一致,这样才能进行通信。
建立链接后,就可以进行调试了。调试在Host端,跟gdb调试方法相同。注意的是要用“c”来执行命令,不能用“r”。因为程序已经在Target Board上面由gdbserver启动了。结果输出是在Target Board端,用超级终端查看。
GDB调试器提供了两种不同的调试代理用于支持远程调试,即gdbserver方式和stub(插桩)方式。
这两种远程调试方式是有区别的。gdbserver本身的体积很小,能够在具有很少存储容量的目标系统上独立运行,因而非常适合于嵌入式环境;而stub 方式则需要通过链接器把调试代理和要调试的程序链接成一个可执行的应用程序文件,如果程序运行在没有操作系统的机器上,那么stub需要提供异常和中断处 理序,以及串口驱动程序,如果程序运行在有操作系统的嵌入式平台上,那么stub需要修改串口驱动程序和操作系统异常处理。显然,对于在有嵌入式操作系统 支持下的开发而言,gdbserver比stub程序更容易使用。这里使用的是GDB+gdbserver的方式,建立远程调试的环境。
gdbserver是一个可以独立运行的控制程序,它可以运行在类UNIX操作系统上,当然,也可以运行在Linux的诸多变种。gdbserver允许远程GDB调试器通过target remote命令与运行在目标板上的程序建立连接。
GDB和gdbserver之间可以通过串口线或TCP/IP网络连接通信,采用的通信协议是标准的GDB远程串行协议( Remote Serial Protocol RSP)。
使用gdbserver调试方式时,在目标机端需要一份要调试的程序的拷贝,这通常是通过ftp或NFS下载到目标机上的,宿主机端也需要这信一份拷贝。 由于gdbserver不处理程序符号表,所以如果有必要,可以用strip工具将要复制到目标机上的程序中的符号表去掉以节省空间。符号表是由运行在主 机端的GDB调试器处理的,不能将主机端的程序中的符号表去掉。
虽然大部分的Linux发行版都已经安装了GDB,但是那都是基于PC的平台的,我们要使用的是在ARM平台上,所以要重新下载gdb的源码,并修改以 适应自己的目标平台。可以从http://www.gnu.org/software/gdb/download,获得。这里使用的是GDB的最新的版本 7.1。首先将下载到的gdb-7.1.tar.bz2复制到/home/zfz/gdb目录下。在控制台下输入下面的解包命令
|
解包之后会生成gdb-7.1/目录,所有的源码都在这个目录下,在gdb目录下新建立一个目录arm-linux-gdb,把生成的可执行文件都放在这个目录下,
在gdb-7.1目录下,输入下面的命令,配置GDB源码:
|
其中 --target=arm-linux选项,表示针对的目标平台是运行linux内核的ARM体系结构的平台,后面的选项--prefix=/home /frank/gdb 则是指定编译完成后的安装目录,最后一个选项--program-prefix=arm-
linux-用来指定在编译生成的二进制可执行文件名之前加上前缀,这里加上的前缀是arm-linux-。这样就可以和宿主机上的调试文件相区别。
|
把源文件编译成相应的目标文件,并连接成可执行的二进制文件 。然后,再执行下面的命令,
|
执行完该命令后,就会在我们刚才建立的arm-linux-gdb目录下生成bin/,lib/,share/这几个子目录,其中在bin下有三个可执行 文件分别为:arm-linux-gdb. arm-linux-run ,arm-linux-gdbui.arm-linux-gdb,就是我们需要的调试器。
编译完arm-linux-gdb之后,接下来就需要编译在目标板上运行的gdbserver了,在gdb/gdb7.1/gdb下有一个gdbserver的子目录,这个目录包括了编译gdbserver所需要的所有的东西。
首先,进行gdbserver目录
|
此时,如果要直接进行编译的话,会出现错误,
|
|
|
这里我们只需要把conifg.h文件中 的#define HAVE_SYS_REG_H 1注释掉就OK了。由于gdbserver是运行在ARM-linux平台上的,因此需要使用交叉编译器arm-linux-gcc,这可以通过给 make加上CC参数来实现这里我使用的是默认的,你也可以使用一个绝对的路径来指定一个arm-linux-gcc.
所有的工作都已经完成了,只要把生成的gdbserver下载到目标板上,或者是通过NFS挂载到目标板上就可能进行远程的调试了,如果就是足够幸运的 话,编译中没有出现什么错误的话,这样完全可以了,不过在我的系统上却不是那么幸运。我一直使用的是友善之臂的arm-linux-gcc-4.3.2交 叉编译器,这个版本的编译器中,已经自带了arm-linux-gdb.
在终端中输入arm-linux-gdb -v 可以看到下面的信息
|
GNU gdb (Sourcery G++ Lite 2008q3-72) 6.8.50.20080821-cvs |
这里可以看出arm-linux-gcc-4.3.2自带的arm-linux-gdb的版本是6.8版的,而我们一直编译的是7.1版本的。一开始我并 没有注意arm-linux-gdb的版本信息,不过在使用过程中,我把用生成的gdbserver的7.1版本用NFS加载到目标板上,而在宿主机上用 的arm-linux-gcc 6.8版本,一直出现下面的错误:
先在目标机上运行下面的gdbserver,注意这里的IP地址是宿主机上的IP地址。
|
然后在宿主机上运行
|
输入c命令时说是程序没有运行,可是程序已经在目标板上运行了,不用输入run命令,当输入 target remote 10.27.10.23:9000结束后,在minicom中可以看到目标板上的输出信息有了变化.
|
在这里捣鼓了一整天没有弄明白错误在那里,看到了arm-linux-gdb 和gdbserver的版本不匹配,所以就变换arm-linux-gdb,这里要把arm-linux-gdb加入到.profile文件中,或者environment文件中,
|
当时一直在,arm-linux-gdb/bin目录下执行,其实执行的也都是arm-linux-gcc-4.3.2中的arm-linux-gdb, 如果这样你看到的版本信息还是6.8版本的话,也可以直接在arm-linux-gcc-4.3.2中把arm-linux-gdb文件删除掉。
这样再一次查看arm-linux-gdb的信息。
|
使用GDB进行调试,首先,我的宿主机的IP 10.27.10.48,开发板上的IP 10.27.10.23在开发板上运行gdbserver 10.27.10.48:9000 test,这里是通过NFS,把要调试的程序加载到目标版上面的。这样就可从在宿主机上运行。
|
在目标板上的输出:
|
补充:前面还有库文件错误的问题,这里可以通过下面这样的方法的来解决。需要说明的是,远程调试,可能程序动态库,我们可以这样设置:
|
调试找不到源代码,如下设置:
|
下面是调试的情况:
(gdb) set solib-absolute-prefix /home/kernel/fs/root_nfs
(gdb) set solib-search-path /home/kernel/fs/root_nfs/lib
(gdb) target remote 10.27.10.23:9000
Remote debugging using 10.27.10.23:9000
Reading symbols from /home/kernel/fs/root_nfs/lib/ld-linux.so.3...(no debugging symbols
found)...done.
Loaded symbols for /home/kernel/fs/root_nfs/lib/ld-linux.so.3
0x400007b0 in ?? () from /home/kernel/fs/root_nfs/lib/ld-linux.so.3
(gdb) l
3 {
4 int i=2;
5 int x, y;
6
7 x=(++i);
8 printf(" %d %d\n", i,x);
9 x+=(++i);
10 printf(" %d %d\n", i,x);
11 x+=(++i);
12 printf(" %d %d\n", i,x);
(gdb) l
13 i=2;
14 y=(i++)+(i++)+(i++);
15 printf(" %d %d\n", i,y);
16
17 return 0;
18 }
19
(gdb) b 8
Note: breakpoint 1 also set at pc 0x83a8.
Breakpoint 2 at 0x83a8: file test.c, line 8.
(gdb) c
Continuing.
Breakpoint 1, main () at test.c:8
8 printf(" %d %d\n", i,x);
(gdb) n
9 x+=(++i);
(gdb) b 12
Breakpoint 3 at 0x8400: file test.c, line 12.
(gdb) n
10 printf(" %d %d\n", i,x);
(gdb) n
11 x+=(++i);
(gdb) n
Breakpoint 3, main () at test.c:12
12 printf(" %d %d\n", i,x);
(gdb) n
13 i=2;
(gdb) n
14 y=(i++)+(i++)+(i++);
(gdb) n
15 printf(" %d %d\n", i,y);
(gdb) n
17 return 0;
(gdb) n
18 }
(gdb) n
0x4003b004 in __libc_start_main ()
from /home/kernel/fs/root_nfs/lib/libc.so.6
(gdb)