嵌入式Linux系统的GDB远程调试实践

                                                        嵌入式Linux系统的GDB远程调试实践
摘要嵌入式Linux系统的研究和应用越来越热针对如何完成系统调试工作的问题文章介绍了GDB远程调试功能及其工作机制重点描述了使用GDB远程调试技术在嵌入式Linux系统中调试各类程序的实践示例
1 引言
信息技术迅猛发展个人数字助理掌上电脑机顶盒等嵌入式产品成为市场热点 Linux继在桌面系统取得巨大成功之后又以其开放源码容易定制和扩展多硬件平台支持和内置网络功能等优良秉性逐渐成为嵌入式系统的研究热点和广泛使用的系统平台在嵌入式Linux系统中使用GDB(GNU Debugger)的强大调试能力开发人员可以避免使用的价格昂贵的仿真器工具来跟踪和调试程序由于嵌入式系统的软硬件资源有限一般不可能在系统本地建立GDB调试环境而是使用远程调试这一GDB高级功能在宿主机上灵活地对运行在目标平台上的程序进行跟踪调试
2 GDB的远程调试功能
GDB是GNU免费提供的调试除错工具可以用于C C++ Pascal和 Fortran等程序的跟踪调试在嵌入式Linux系统中开发人员能够在宿主机上使用GDB方便地以远程调试的方式单步执行目标平台上的程序代码设置断点查看内存并同目标平台交换信息
使用GDB进行远程调试时运行在宿主机上的GDB通过串口或TCP连接与运行在目标机上的调试插桩stub以GDB标准远程串行协议协同工作从而实现对目标机上的系统内核和上层应用的监控和调试功能调试stub是嵌入式系统中的一段代码作为宿主机GDB和目标机调试程序间的一个媒介而存在GDB远程调试结构如图1所示 stub操作系统目标机上层应用宿主机GDB操作系统标准远程串行协议
图1 GDB远程调试结构
为了监控和调试程序主机GDB通过串行协议使用内存读写命令无损害地将目标程序原指令用一个Trap指令代替完成断点设置动作当目标系统执行该Trap指令时stub可以顺利获得控制权此时主机GDB就可以通过stub来跟踪和调试目标机程序了调试stub会将当前场景传送给主机GDB然后接收其控制命令stub按照命令在目标系统上进行相应动作从而实现单步执行读写内存查看寄存器内容显示变量值等调试功能
GDB远程串行协议是一种基于消息的ASCII码协议它定义读写数据的信息控制被调试的程序并报告运行程序的状态这些功能在目标机方由stub的相关功能函数实现而宿主机方则由GDB源文件remote.c中的功能代码完成协议交换数据的格式如下
$#[chksum]
其中是ASCII码十六进制字符串[chksum]是一个字节的十六进制校验和当发送信息时接收方响应如下
+ 接收数据校验正确且接收方准备好接收下一个数据包
- 接收数据校验不正确发送方必须重发数据包
调试stub响应主机GDB数据的方式有两种若消息传送正确则回送响应数据或OK否则返回由目标平台自己定义的错误码并由GDB控制台向用户报告通信数据中的包括了非常丰富的远程调试的操作指令和状态信息完整的定义信息可参考
GDB参考文档的info-14中GDB Remote Serial Protocol 部分
3 远程调试实践
远程调试环境由宿主机GDB和目标机调试stub共同构成两者通过串口或TCP连接使用GDB标准远程串行协议协同实现远程调试功能宿主机环境的设置比较简单只要有一个可以运行GDB的系统环境一般选择一个较好的Linux发行版即可但要注意开发人员不能直接使用该发行版中的GDB来做远程调试而要获取GDB的源文件包针对特定目标平台做一个简单配置重新编链得到相应GDB
host> cd /tmp/gdb-5.2.1
host> ./configure --target=……
host> make
host> cp /tmp/gdb-5.2.1/gdb/gdb /usr/bin
其中configure命令的target参数就指定了目标机的类型开发人员可根据所用目标平台从GDB说明文档获取该参数值这样宿主机远程调试环境就建立好了剩下就是目标机stub的实现具体环境中目标stub的实现和使用方式则与硬件平台和应用场合相关就目前而言嵌入式Linux系统中主要有三种远程调试方法分别适用于不同场合的调试工作用ROM Monitor调试目标机程序用KGDB调试系统内核和用GDBserver调试用户空间程序这三种调试方法的区别主要在于目标机远程调试stub的存在形式的不同而其设计思路和实现方法则是大致相同的
3.1用ROM Monitor调试目标机程序
嵌入式Linux系统在内核运行前的状态中程序的装载运行和调试一般都由ROM Monitor实现系统一加电包含了远程调试stub的ROM Monitor首先获得系统控制权对CPU内存中断串口网络等重要资源和外设进行初始化然后就可以下载运行和监控目标代码包括内核的装载和引导也由它完成基于ARM平台的嵌入式Linux系统中最常用的RedBootRed Hat Embedded Debug and Bootstrap是一款功能相当强大的系统引导调试和管理工具其中包含了GDB远程调试stub的完整实现串口和以太口的驱动TCP/IP相关协议的实现目标系统的启动和Flash设备的管理等功能内核运行之前用RedBoot下载并调试目标机程序armboot的一个实例会话如下
目标板加电RedBoot运行在提示符下按’$’键进入调试stub状态
ARM eCos // 部分RedBoot启动信息
RedBoot(tm) debug environment - built 15:49:04, Mar 27 2002
Copyright (C) 2000, Red Hat, Inc. ……
RedBoot> // 按’$’键进入调试stub状态
RedBoot> Entering debug mode using GDB and stubs
宿主机运行GDB实现程序的下载和调试
[root@Host armboot-1.1.0]# gdb armboot
GNU gdb 5.2.1 // 部分GDB启动信息
Copyright 2002 Free Software Foundation, Inc.
Configured as "--host=i686-pc-linux-gnu --target=arm-pxa-linux-gnu"...
(gdb) set remotebaud 115200 // 设置串口波特率
(gdb) target remote /dev/ttyS0 // 通过串口1连接目标机
Remote debugging using /dev/ttyS0
0x0000e2b8 in ?? ()
(gdb) load // 下载目标代码至目标机内存
Loading section .text, size 0xbef8 lma 0xa3000000
Loading section .rodata, size 0x2f84 lma 0xa300bef8
Loading section .data, size 0xd24 lma 0xa300ee7c
Start address 0xa3000000, load size 64416
Transfer rate: 85888 bits/sec, 298 bytes/write.
(gdb) list serial.c:75 // 查看源代码
70 */
71 void serial_init(bd_t *bd)
72 {
73 const char *baudrate;
74
75 if ((baudrate = getenv(bd, "baudrate")) != 0)
76 bd->bi_baudrate = simple_strtoul(baudrate, NULL, 10);
77
78 serial_setbrg(bd, bd->bi_baudrate);
79 }
(gdb) break 75 // 设置断点
Breakpoint 1 at 0xa300b964: file serial.c, line 75.
(gdb) continue // 运行目标机程序至断点处
Continuing.
Breakpoint 1, serial_init (bd=0xa30312e4) at serial.c:75
75 if ((baudrate = getenv(bd, "baudrate")) != 0)
(gdb) display baudrate // 显示变量baudrate当前值
1: baudrate = 0xa30312e4 ""
(gdb) next // 跟踪baudrate变量值
76 bd->bi_baudrate = simple_strtoul(baudrate, NULL, 10);
1: baudrate = 0xa300f392 "115200"
(gdb)
开发人员可以依靠GDB提供的强大调试和监控功能对运行在目标板上的程序进行实时跟踪清晰地查看程序所使用的目标板资源的状态如CPU寄存器内存值等等
3.2 用KGDB调试系统内核
系统内核与硬件体系关系密切因而其调试stub的实现也会因具体目标平台的差异而存在一些不同嵌入式Linux的开源社区和开发团体针对大多数流行的目标平台对Linux内核远程调试stub给予了实现并以源码补丁形式发布开发人员只需正确编链打好补丁的内核就可对内核代码进行灵活的调试PC平台Linux内核开发人员所熟知的KGDBRemote Kernel Debugger就是这种实现形式该方法也同样用在嵌入式Linux系统中如MontaVista的Deepak Saxena主持开发的内核调试补丁就能够很好地完成IOP3xxADI和IXP系列的目标平台上Linux内核的调试一个通过串口以远程方式跟踪do_fork()内核函数的大致场景如下
(gdb) set remotebaud 115200 // 设置串口波特率
(gdb) target remote /dev/ttyS0 // 通过串口1连接远程目标
Remote debugging using /dev/ttyS0
……
(gdb) continue // 连接成功开始内核的启动
Continuing.
Memory clock: 99.53MHz (*27) // 内核启动信息
Run Mode clock: 99.53MHz (*1)
……
VFS: Mounted root (nfs filesystem).
Freeing init memory: 76K
Program received signal SIGTRAP, // 中断内核的运行以便设置断点
Trace/breakpoint trap.
……
(gdb) list fork.c:622 // 查看内核源代码
617 p->did_exec = 0;
618 p->swappable = 0;
619 p->state = TASK_UNINTERRUPTIBLE;
620
621 copy_flags(clone_flags, p);
622 p->pid = get_pid(clone_flags);
623
624 p->run_list.next = NULL;
625 p->run_list.prev = NULL;
626
(gdb) break 622 // 设置断点
Breakpoint 1 at 0xc0116d50: file fork.c, line 622.
(gdb) continue // 恢复内核的运行直至断点
Continuing.
Breakpoint 1, do_fork (clone_flags=17, stack_start=3221223548, regs=0xc7153fc4, stack_size=0) at fork.c:622
622 p->pid = get_pid(clone_flags);
(gdb) display p->pid // 显示变量p->pid当前值
1: p->pid = 1152
(gdb) next 3 // 跟踪get_pid()函数返回后变量p->pid的变化
624 p->run_list.next = NULL;
1: p->pid = 1181
(gdb) delete 1 // 删除断点
(gdb) continue // 恢复内核的运行
Continuing.
GDB的调试功能非常丰富和强大上例所示只是GDB远程调试在系统内核调试中的一个最简单的应用开发人员可以使用GDB提供的众多功能对内核进行强有力的跟踪调试
3.3 用GDBserver调试用户空间程序
在Linux内核已经正常运行的基础上使用GDBserver作为远程调试stub的实现开发人员可以在宿主机上用GDB方便地监控目标机用户空间程序的运行GDBserver是GDB自带的针对用户程序的远程调试stub它具有良好的可移植性可交叉编译到多种目标平台上运行因为有操作系统的支持它的实现要比一般的调试stub简单很多但作为以远程方式调试用户程序的目标方正是它的用武之处下面给出主机gdb和目标机gdbserver以TCP方式远程调试目标平台MiniGUI HelloWorld程序的会话过程其中192.195.150.140是宿主机192.195.150.164为目标机
目标机
[root@l2x target]# gdbserver 192.195.150.140:2345 mginit
Process mginit created: pid=72 // 等待宿主机连接
宿主机
[root@l2x host]# gdb mginit
…… // GDB版本配置信息
(gdb) target remote 192.195.150.164:2345 // TCP方式连接目标机
Remote debugging using 192.195.150.164:2345
0x40002a90 in ?? () // 连接成功
目标机
Remote debugging from host 192.195.150.140 // 宿主机连接成功
宿主机
(gdb) break mginit.c:73 //设置断点
Breakpoint 1 at 0x8bfc: file mginit.c, line 73.
(gdb) continue // 运行程序至设定断点处
Continuing.
Breakpoint 1, MiniGUIMain (args=1, arg=0xbffffe14) at mginit.c:73
73 hMainWnd = CreateMainWindow (&CreateInfo);
(gdb) display CreateInfo // 查看变量
1: CreateInfo = {dwStyle = 671088640, spCaption = 0x8cd8 "Hello, world!",… }
(gdb) c // 运行至程序结束
Continuing.

Program exited normally. // 程序正常退出
目标机
Child exited with retcode = 0 // 程序运行返回
Child exited with status = 14 // 程序退出状态
GDBserver exiting. // 联调结束
在开发嵌入式系统上层应用时由于宿主机和目标机的软硬件环境存在或多或少的差异所以开发人员在宿主机上模拟调通的程序往往并不能很好地运行于目标平台上运用上述远程调试方法则可以减少对模拟调试的依赖在主机上以远程方式像调试本地程序一样十分方便地直接对运行在目标平台的程序进行监控调试从而提高开发质量和效率缩短嵌入式系统应用的开发周期特别值得一提的是这种方式对于嵌入式GUI系统的调试是非常方便的宿主机运行调试器目标机运行待调试的GUI系统两者在各自的控制台处理输入输出互不干扰以串口或TCP方式进行通讯共同完成对GUI系统的调试这种调试工具和图形系统与开发人员的同步交互在单机调试环境中实现起来是比较麻烦的不仅需要虚拟图形设备以支持图形系统的输入输出如Qt图形库中的QVFB就是一个虚拟的FrameBuffer而且调试工具与图形系统间的交互则要通过Xterm等终端工具实现调试环境的搭建和调试的具体实施较为繁琐但是效果却不如远程调试好
4 结束语
在嵌入式Linux系统开发中GNU工具链是一个很好的选择不但提供了对各种流行嵌入式处理器的良好支持而且开发人员可免费使用使得系统开发成本大大降低GNU工具链中的GDB更是以其远程的调试方式适应了嵌入系统的特殊调试要求在实际开发中得到了最广泛的应用我们在一款ARM系列实验开发板上做嵌入式Linux系统时就使用文章所述的GDB远程调试方法很好的完成了内核运行前的代码内核代码内核基础上用户空间代码的调试工作系统开发的工作效率得到了质的提高

你可能感兴趣的:(嵌入式Linux系统的GDB远程调试实践)