图 1    基于 kgdb  的 l i nux  核心调试 表 1    G DB  调试命令
基于 arm 平台的 KGDB 内核调试技术应用
周永福, 匡华
( 河源职业技术学院 , 广东 河源 517000)
摘要: 文章简要分析了 kgdb 的原理和它的实现过程, 并在目前使用比较广泛的 arm 板上对 kgdb 进行了移植, 主要包括串口驱动, 陷 阱处理和调试命令的移植三个部分。
关键词: 嵌入式 linux ;  内核源代码级调试器; 陷阱处理;移植,  arm  中图分类号: TP 302 文献标识码: A 文章编号: 1009- 3044(2007)14- 30437- 03
The KGDB Kernel- debug Technology Based on Arm
ZHOU Yong- fu , KUANG Hua
(Heyuan Polytechnique College , Heyuan 517000,China)
Abstract : Paper Analyzed kgdb' s theory and how it implement; and transplanted  kgdb to the arm  b
orad which is popular nowdays,  in- cluding three portions: the uart driver 、trap dispose and the transplant of the debug commands.
Key words : embedded linux;kernel- debug;stub;trap dispose;transplant
1 引言
Kgdb(内核源代码级调试器)是一种插桩式内核调试工具, 能 很方便地在源码级对内核进行调试。 目前, kgdb 发布支持 i386、
x86_64、32- bitPPC 、S PARC 等几种体系结构的调试器, 而 arm 体
系结构的 kgdb 补丁并不多见。本文以 Xscale  pxa255 开发板为例, 将 适 合 于 i386 体 系 结 构 的 linux 系 统 的 kgdb 移 植 到 Xscale px- a255 开发板上, 采用串口通信, 研究 kgdb 在 arm 平台上的移植技 术, 实现在 arm 平台上基于 kgdb 对 linux 内核的调试。
2  基于 x86 的 kgdb 的简要分析
x86架构和arm架构区别2.1  kgdb 的调试原理
kgdb 提供了一种使用 gdb 调试 linux 内核的机制, 需要两台 机器进行远程调试, 一台宿主机( host ) 和一台目标机( target , 也可
以是要调试的目标板) , 通过串口( 或网口) 相连。调试时在待调试 的内核插入 kgdb 补丁, 重新编译使其运行于目标板。 此时, 在主 机运行的 gdb 就能通过串口( 或网口) 与运行于目标板 上 的 内 核 的 kgdb 功能模块建立连接并开始通信[3][4]。 用户运行 gdb 调试 命令, 通过 kgdb 功能模块对内核进行控制, 从而达到远程调试 linux 内核的目的。基于 kgdb 的 linux 核心调试如图 1 所示。
2.2  kgdb 的具体实现
2.2.1 通信协议
为支持远程调试, kgdb 与 gdb 之间的通信遵循约定的通信 协
议。远程通信协议定义读写数据的信息, 控制被调试程序, 并报 告运行程序的状态。通信协议的数据包以“$”开始, “#”结束, 再加 上校验码, 格式统一为: $<;信息>#<;校验码>。kgdb 功能模块响应 主机
gdb 命令的方式有两种: 若数据包正确, 则回送响应数据(“+”
表示接收到正确的数据,  “- ”表示接收出错,  要求重新发送数据)或 OK ; 否则, 返回由目标平台自己定义的错误码, 并由 gdb 控制台向 用户报告[5]。
kgdb 与 gdb 的连接一般要使用一个端口: 串口或网口, 选定 端口后,  kgdb  首先要做的就是对端口进行初始化。
2.2.2 陷阱处理
Kgdb 功能模块要全面完整的调试内核, 必须对目标机上所 发生的所有异常进行统一管理。 为了能够捕获这些异常, kgdb 在
CPU 的
每 个 异 常 处 理 函 数 中 都 嵌 入 kgdb 的 核 心 处 理 函 数 han- dle_exception ()。当发生异常时, 能通过 CPU 的处理函数进入到
kgdb 的处理函数 handle_exception()中。
此时 handle_exception()会 将发生的异常以信号 “S x x ”的 方式通知主机上的 gdb , 并等待主机 上的 gdb 发送过来的调试命令, 从而进行调试。 完成调试退出函 数 handle_exception()后, CPU 重新回到原来的处理函数中, 继续对
异常进行处理。
Kgdb 采取了在内核中设置陷阱的方式, 使被调试的内核产生 异常进入到 kgdb 调试状态。int $3 指令( 0xCC ) 是 x86 中断指令, kgdb 对 int 3 异常处理函数的修改和对其他异常处理函数不同, 它是用函数 handle_exception()完全代替原来的处理。Kgdb 定义宏 BREAKPOINT 为 int $3, 被 breakpoint()函数调用。Kgdb 在 linux 的 启 动 函 数 start_kernel()中 嵌 入 breakpoint()函 数 。 当 核 心 启 动 时 ,
trap_init()完 成 对 各 种 异 常 的 初 始 化 后 , 执 行 breakpoint()函 数 , 产
生 int 3 异常, 进入 kgdb 的核心处理函数 handle_exception (), 将
CPU 的控制权交给 kgdb , 从而调试内核。
每当进入 kgdb 调试状 态, 要在内核的其他地方设置断点时, kgdb 通过将指定位置的原 指令与 int  $3 指令进行替换实现。
2.2.3 调试命令
当要调试的内核与 gdb 建立连接进入到调试状态以后, kgdb  接收 gdb 发送过来的调试命令, 然后根据不同的调试命令在目标
系统上完成相应的调试操作: 设置断点, 单步执行, 读取寄存器的 值, 设置变量的值等等, 就像在本机上调试程序一样, 这些主要的 调试命令格式如下表 1 所示。
这 些 命 令 都 是 由 函 数 handle_exception()实 现 , handle_excep-
tion()通过对 gdb 发送过来的数据包进行解析再执行, 然后生成应 答数据包, 完成与 gdb 的通信, 从而达到利用 gdb 调试 linux 内核
收稿日期: 2007- 06- 02
作者简介: 匡华( 1982- ) , 男, 江西吉安人, 助教, 研究方向为计算机数据处理与数值计算、数学教育; 周永福( 1979- ) , 男, 江西贵溪人, 助教, 华 中科
技大学硕士研究生,  研究方向为智能控制嵌入式系统、计算机网络。
开发研究与设计技术
的目的。
3  kgdb  在arm 平台上的移植实现
3.1  串口驱动模块的移植
不同的体系结构中, 串口驱动实现不一样。Kgdb 需要根据入口参数( kgdb 将要用到的串口号ttyS 和串口的传输数率baud) 和目标板本身的硬件资料, 实现串口的三个接口: 串口的读写和初始化。
在pxa255 开发板上, 对串口进行读写时, 只需要直接对接收寄存器FFRBR 和发送寄存器FFTHR 进行读写, 串口的初始化实现如下:
set_GPIO_mode(GPIO34_FFRXD_MD);
set_GPIO_mode(GPIO39_FFTXD_MD); //设置正确的GPIO;
CKEN |= CKEN6_FFUART;    //时钟使能;
FFDLL = 0x00000008;    //波特率为115200;
FFDLH = 0x00000000;
FFLCR = 0x00000003;
// 8 个数据位, 1 个停止位, 无奇偶校验位;
FFFCR = 0x00000006; // FIFO 复位;
FFFCR = 0x00000000; //屏蔽FIFO;
FFMCR = 0x00000008;  //使能中断;
FFIER = 0x00000055;    //使能串口;
3.2  陷阱处理的移植
对于arm 平台, 在armv5 及其以上版中有一个断点指令BKPT, 而在低版本的指令集中, 没有该指令。在将kgdb 移植到pxa255 开发板上时, 使用了另一种方法: 利用非定义指令来代替断点指令, 使其嵌入到被调试程序中(非定义指令就是arm 指令集中没有的指令, 它会产生一个非定义指令异常, 从而达到断点异常的效果) :
#define BREAKPOINT() asm (".word 0xe7ffdeff")
( 其中0xe7ffdeff 就是一条非定义指令, 只要是arm 指令集中没有的指令, 都可以用作非定义指令; kgdb 
定义了两条非定义指令: 0xe7ffdeff 用来代替断点指令和0xe7ffdefe 用来代替下一条指令)当内核执行到breakpoint()函数时, 会调用BREAKPOINT 产生非定义指令异常。CPU 从trap_init()初始化好的异常入口地址进入到非定义指令异常的处理段und_svc, 在und_svc 处理段中对非定义指令进行比较(为了防止系统中有其他的非定义指令), 如果产生该异常的指令不是kgdb 定义好的非定义指令( 0xe7ffd- eff 或0xe7ffdefe) , 则按正常的CPU 非定义指令异常处理; 否则, 跳过一些不必要的操作, 直接进入到非定义指令异常处理函数do_undefinstr (), 在这里将控制权交给kgdb, 使内核顺利进入到kgdb 调试状态。
具体实现如下:
stmia  r4, {r5  - r9}
#ifdef CONFIG_KGDB
adr r10, kgdb_brk_instr
ldr r10, [r10]
ldr r9, [sp, #S_PC]
sub r9, r9, #4
mask_pc r9, r9
ldr r9, [r9]
teq r10, r9
//判断是否为代替下一条指令的非定义指令kgdb_brk_instr
beq 2f
adr r10, kgdb_trap_instr
ldr r10, [r10]
teq r10, r9 //判断是否为代替断点指令的非定义指令
kgdb_trap_instr
beq 2f
#endif
adrsvc al, r9, 1f
bl call_fpe
#ifdef CONFIG_KGDB
2://是则直接进入到do_undefinstr()函数中
本栏目责任编辑: 谢媛媛#endif
mov    r0, r5 @ unsigned long pc
mov    r1, sp@ struct pt_regs *regs
bl SYMBOL_NAME(do_undefinstr)
在全面接管系统异常时, arm 的做法和i386 一样, 需要在CPU 的异常处理函数中嵌入kgdb 的核心处理函数handle_excep- tion()。
3.3  调试命令的移植
'  g'    、' m'    、's'    等是基本的gdb 调试命令, 用户所使用的gdb 命令(如' break' 、'set' 、'next' 、'continue' 等) 都可以通过这些组合实现, 如gdb 命令' set' 通过gdb 传送给kgdb 实际就是调试命令' m' , 等等。当kgdb 移植到arm 板上时, 这些调试命令的实现与体系结构相关。
命令‘g’/‘G’:是读写寄存器的两个调试命令, 需要用到中间变量gdb_regs 实现对CPU 寄存器的读写操作。读寄存器时, 先将寄存器的值读入到gdb_regs, 再转化格式发送出去; 写寄存器时操作相反, 中间变量gdb_regs 必须定义成他们各自的CPU 寄存器数组, 并在对寄存器操作时将gdb_regs 的各个寄存器和CPU 各个寄存器对应起来, 否则就会出错(如i386: gdb_regs[_EAX] = regs- > eax 或regs- >eax = gdb_regs  [_EAX]; arm: gdb_regs  [0]    = regs-  > ARM_r0 或regs- >ARM_r0 = gdb_regs[0]) 。
命令‘m’/‘M’:是读写内存的两个调试命令, 实现这个命令时, 只需对接收到的地址进行读写就可以了。对内存的读写有可能会发生读写内存异常, 如对一个非法地址进行读写。当发生读写异常时,kgdb 用函数kgdb_setjmp()和kgdb_longjmp()提供异常处理机制, 将进程返回到调用前的状态。这两个函数需要将进程运行的当前状态保存下来, 出错时再根据保存下来的状态进行恢复, 在arm 中, 这两个函数实现如下:
ENTRY (kgdb_setjmp)
stmia  r0, {r0- r14}
str lr,[r0, #60]
mrs r1,cpsr
str r1,[r0,#64]
ldr r1,[r0,#4]  //保存当前进程的状态;
mov    r0, #0
mov    pc,lr
ENTRY (kgdb_longjmp)
str r1,[r0]
ldmia  r0,{r0- pc}^ //将保存好的状态进行恢复;
命令‘s’/‘S’:是单步执行调试命令, 但它只是指令级的单步而不是源码级的单步。在x86 中, 这条调试命令的处理方法是将产生异常的进程标志寄存器的陷阱标志位TF 置1( linux_regs- >e- flags |= 0x100) , 然后函数handle_exception()结束返回到断点继续执行。由于TF 标志位位1, 所以程序执行一条指令后
又进入到handle_exception()。这种方法在arm 体系结构中并不适用, 在实现这个命令时, 也使用了非定义指令的方法: 定义非定义指令KGDB_BREAKINS T, 将KGDB_BREAKINS T 与下一条指令进行替换。当handle_exception()结束返回到断点继续执行完一条指令之后, 由于下一条指令是KGDB_BREAKINS T, 系统产生异常又进入到handle_exception(),  同样达到了目的。
其具体实现如下:
#define KGDB_BREAKINS T 0xe7ffdefe
定义两个全局变量: step_addr  和step_instr;
step_addr = (unsigned int *) get_next_pc(&kgdb_regs);
//函数get_nextpc()用来计算出下一条指令的地址
step_instr = *step_addr;
*step_addr = KGDB_BREAKINS T;
由于将下一条指令取出, 所以当下一次进入到函数han-dle_exception()时,首先要做的事就是将暂时存放在step_instr 中的指令放回到原来的地址中去:
if (step_addr !  = NULL) {
*step_addr = step_instr;
438 电脑知识与技术
step_addr = NULL;
step_instr = 0;}
4  结束语
kgdb 是一个采用插桩方式, 纯软件的内核调试工具, 不需要片上调试。且它可以对内核进行全面的调试, 在调试过程中对内核运行进行全面控制, 甚至可以调试内核的中断处理程序, 而且其操作方便, 其调试命令几乎和普通用户程序调试一样。另外, kgdb 还可以调试模块, 如果使用图形化的开发工具, kgdb 对内核的调试更加方便[6]。但kgdb 调试环境的构建比较复杂, 且因为采用的是中断的方式调试内核, 所以kgdb 不能调试内核的引导启动过程, 只能在内核进入start_kernel()以后开始kgdb 的调试。在arm 中kgdb 所能设置断点进行调试的最初位置, 是在执行完set- up_arch()、console_init()和trap_init()函数之后[7][8]。尽管如此, kgdb 还是一个很不错的内核调试工具。
参考文献:
[1]Linux 内核调试器内幕[EB/OL].www- 900.ibm/de-veloperWorks/cn/linux/l- kdbug/index.shtml.
[2]李红卫,李翠萍.kgdb  调试Linux  内核的剖析与改进[J].微型机与应用,2004,10:7- 10.
[3]张磊,王学慧. Linux 内核调试技术[J].计算机工程,2003,10: 81- 83.
[4]郭胜超,吕强,杨季文,钱培德.  GDB  远程调试及其在嵌入式Linux  系统中的应用[J].计算机工程与科学.
[5]毛德操,胡希明.Linux 内核源代码情景分析[M].杭州:浙江大学出版社,2002.
[6]KGDB:Linux Kernel Source Level Debugger [EB/OL]. www.g com.
(上接第427 页)
2  DCCM 设计分析
数据交换组件( DCCM)是由数据访问方法包装器、数据访问持久器、数据访问仲裁器、W e b服务接口器和数据传输器等组成, 它一般驻留在服务器端, 且属于服务提供程序组成部分。如图  3 所示。数据和
交换数据相关属性和方法, 以及序列化方法, 其通过数据传输对象(DTO) 来实现, 且直接与Web 服务接口器相关联, 其命名空间定义为: Dccm.datatransfer.dto.*。传输过程如图  4  所示。
3  DCCM 实现
图  4  数据传输过程
建议在Windows2003+VS.2003  (C#)+SQLServer2000+( I IS6.0)的环境下实现DCCM,并定义一组中间件配置方法: <dccm> </ dccm>。然后编译成一组DLL, 命名为: dccm.dll, 在VS.2003 中引用dccm.dll 即可实现基于.NET 的异构体数据交换, 其实直接在C# 运行环境中编写组成一组一组*.CS 类, 并相互创建对象调用, 当实现完DCCM 相关功能后, 编译即可。如图5 所示是DCCM 运行应用结构图。
图3    DCCM 运行时序概图
数据访问方法包装器: 包装.NET 下MDAC 所有的数据访问、
处理提供程序, 其采用的方法是: 首先建立一个数据访问处理接
口, 然后按所提供的数据库程序的特性建立一个类, 该类中包含
相关的访问处理的属性和方法, 以及持久性和仲裁相关的方法,
并继承该接口。同时公开该接口, 便于开发自定义数据库访问驱
动程序,  其命名空间定义为: Dccm.daowrap.*。  4 结语
图5  突出DCCM 运行应用结构图
数据访问持久器: 为X ML 数据交换提供一种映射匹配机制和一组持久化访问对象, 然后通过数据访问方法包装器和数据访问仲裁器提供地方法进行数据交换, 如包装的ADO、ADO.N ET 方法等, 其命名空间定义为: Dccm.dao  persistent.*。
数据访问仲裁器: 控制协调数据交换对象, 由一组并发、调度相关的对象组成, 具有判断、识别能力, 是整个DCCM 控制枢导中心, 在实现时可以建立线程池的方式来完成, 同时定义一组数据交换优先级和控制操作数,  其命名空间定义为:  l.* Web 服务接口器: 连接服务需求者与服务提供者访问交换数据的方法, 可以是一组基于X ML 配置文件, 其包括请求\ 提供连接适配器、XML 数据总线定义方法、数据传输对象接口等组成, 其命名空间定义为: Dccm.webservice.interface.*
数据传输器: 封装S OAP 协议(请求/响应)以XML 流为载体与HTTP、F TP、S MTP 等以及相关的RPC、WS DL 等进行绑定传输
根据目前数据直接访问出现的弱点,  在.NET  框架下通过Web 服务建立了一种专门的数据访问组件。
从技术角度, 有效分离了业务逻辑与数据访问逻辑, 降低了耦合度, 从应用角度, 减轻了开发人员繁琐编码操作, 提高了工作效率, 开发人员只需调用相关的API 就可方便实现数据交换; 从而提高该类应用系统的可扩展性、移植性、可重用性等。
参考文献:
[1]Dino Esposito.Real- World X ML Manipulate X ML Data Easily with Integrated Readers and Writers in the .NET Framework [J]. MS DN M agazine.May 2003.
[2]蔡飞,  贝佳,  潘金贵.一种简单高效的X ML 与关系数据库信息交换的方法[J].计算机科学.2004.31(12):72-  75.
[3]智永锋,  张骏.基于X ML  的数据库访问中间件设计与实现[J].计算机应用研究.2005.11: 87- 88( 91) .
439

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。