📄 7.htm
字号:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>CTerm非常精华下载</title>
</head>
<body bgcolor="#FFFFFF">
<table border="0" width="100%" cellspacing="0" cellpadding="0" height="577">
<tr><td width="32%" rowspan="3" height="123"><img src="DDl_back.jpg" width="300" height="129" alt="DDl_back.jpg"></td><td width="30%" background="DDl_back2.jpg" height="35"><p align="center"><a href="http://bbs.fudan.edu.cn"><font face="黑体"><big><big>日月光华</big></big></font></a></td></tr>
<tr>
<td width="68%" background="DDl_back2.jpg" height="44"><big><big><font face="黑体"><p align="center"> 基于linux的嵌入式系统研究 </font></big></big></td></tr>
<tr>
<td width="68%" height="44" bgcolor="#000000"><font face="黑体"><big><big><p align="center"></big></big><a href="http://cterm.163.net"><img src="banner.gif" width="400" height="60" alt="banner.gif"border="0"></a></font></td>
</tr>
<tr><td width="100%" colspan="2" height="100" align="center" valign="top"><br><p align="center">[<a href="基于linux的嵌入式系统研究.htm">回到开始</a>][<a href="基于linux的嵌入式系统研究.htm">上一层</a>][<a href="8.htm">下一篇</a>]
<hr><p align="left"><small>发信人: smilecrying (越龙), 信区: Embedded <br>
标 题: 基于linux的嵌入式系统研究(7) <br>
发信站: 日月光华 (2003年05月25日16:37:24 星期天) <br>
<br>
<br>
第六章 调试环境的搭建和定制 <br>
正如我在4.6所说的那样,利用Linux现有的内核改造成嵌入式操作系统,最根本的需求是 <br>
能够自己完全控制Linux内核,并且可以随时根据自己的需求,改造Linux内核成自己需要 <br>
的内核,尤其是在内核的体积和功能的比例上。如果是自己不需要的功能,就不应该出现 <br>
在内核中。因为嵌入式系统的资源有限性,提出定制自己的内核的要求是理所当然的。因 <br>
此,需要根据Linux建立起自己的一个内核调试环境,在自己修改内核的情况下,可以深入 <br>
到内核的运行过程内部,了解内核的运行动态,从而才能达到修改内核的目的。 <br>
在实时的Linux内核开发过程中,使用最多的调试内核的方法是使用printk函数,这个函数 <br>
根据loglevel的级别,将内核的信息打印出来。这种方法简单使用,但是不适用于对整个 <br>
内核的修改工作中,只是对一些内核模块的编程工作或者内核做一些小变化的时候才有用 <br>
。我们考虑别的方法来搭建内核的调试环境。 <br>
调试Linux内核需要有一个运行内核的环境,一个调试的环境。我们其实可以有下面两种思 <br>
路: <br>
1)自行设计和编写调试命令解释器和一个简易内核用来支持命令解释器。这样做的好处是 <br>
可以在一台机器上进行内核的调试。将调试命令解释器和简易内核嵌入到内核的代码中, <br>
并且提供随时中断内核的运行,而调试器开始运行,监控内核的每一步动作。 <br>
这样做的好处是,使用一台机器就可以达到调试的目的,并且可以在任何自己想停下来进 <br>
行调试的时候,将整个内核的运行停止,并且一步步监控内核的运行。 <br>
这样做的缺憾是,自行编写命令解释器的复杂度太高,而且提供的功能功能太少;调试的 <br>
时候只能进行汇编代码的调试,不直观;缺乏图形界面的调试方式。 <br>
2)利用GDB的远程调试功能。远程调试功能是在操作系统开发中使用最多的方法。远程调 <br>
试的时候分两台机器,一台是远程被调试主机,一台是本地主机。两台主机通过串口线协 <br>
议或者TCP/IP协议连接起来。远程主机上运行被GDB规范断点改造过后的内核,需要进行调 <br>
试的时候,将断点激活,然后等待本地主机的连接信息,一旦连接成功,本地主机可以向 <br>
远程主机发送一系列的调试命令,指使远程主机内核运行。所有调试的符号表存放在本地 <br>
主机上,因此在调试的时候可以使用源代码调试的方式。并且GDB有X Window调试窗口环境 <br>
,界面友好。 <br>
比较这两种调试方法,我认为采用第二种调试方法对嵌入式Linux系统的开发工作很有意义 <br>
。因此下面将详细介绍如何建立这样的调试环境。 <br>
6.1 远程调试环境的搭建原理 <br>
6.1.1 GDB介绍 <br>
GDB是GNU C自带的调试工具。它可以使得程序的开发者了解到程序在运行时的详细细节 <br>
<br>
从而能够很好的除去程序的错误,达到调试的目的。英文debug的愿意就是”除虫”,而g <br>
db的全称就是Gnu DeBugger。目前GDB支持的可以调试的语言有C,C++,Modula-2等几种语 <br>
言,现在还可能可以支持Fortran语言的调试。 <br>
使用GDB可以完成下面这些任务: <br>
 运行程序,可以给程序加上你所需要的任何条件; <br>
 在你规定的条件下让程序停止; <br>
 检查在你的程序停止的时候程序的状态; <br>
 在你的程序中改变一些数据,使得你可以更好的改正程序的错误。 <br>
GDB提供了大量的命令,用来完成程序的调试;在GDB的基础上还实现有 X-Window下的调试 <br>
界面,调试界面友好。 <br>
6.1.2 GDB远程调试功能介绍 <br>
在GDB里面有一个调试目标(Target)的概念。调试目标就是你的程序所获得的执行环境。 <br>
一般的情况下,你要调试的程序和你当前所在的环境是完全一样的,那么你用file或者co <br>
re命令可以指定你的调试目标。(file命令用来指定用来调试的可执行文件,core命令用 <br>
来指定调试的时候需要导入的core dump文件)。另外一种情况就是这里需要介绍的。如果 <br>
需要调试的程序和GDB所运行的环境不同,或者说你需要调试的环境上根本无法运行起GDB <br>
,那么就没有办法使用file或者core命令来指定调试目标了。这里,就需要使用远程调试 <br>
功能,通过一台可以使用GDB的机器,通过串口的通讯协议和被调试的程序所在的机器连接 <br>
,从而调试程序。这种情况是很多的,比如说你要调试一个独立的系统,或者说是实时系 <br>
统,都需要和这个系统建立起连接才能完成整个调试过程。在GDB里面就是使用target命令 <br>
完成这项工作的。 <br>
指定你需要调试的远程机器的方法是使用target remote命令,后面紧接着需要和远程机器 <br>
连接的设备,如/dev/ttyS0(串口1)等等。在GDB里面内嵌有串口的通信协议,并且规范 <br>
了和远程机器的调试命令的一些数据传输包的格式;在远程机器上,需要实现一个stub文 <br>
件,在这个文件里面需要提供串口连接的协议,和传送数据信息的方法。可以这样说,st <br>
ub文件代替了在本地主机上GDB串口协议的位置,从而实现两台机器的连接。注意,所有的 <br>
符号表数据都放在本地主机上,在调试之前将符号表导入,这样调试的时候就可以对着源 <br>
代码进行调试了。GDB远程调试的原理可以参看图10: <br>
<br>
<br>
在6.1.3里面我将详细介绍这三个内容:stub中要实现的串口协议;双方进行通信的数据包 <br>
格式和调试环境运行的步骤。 <br>
6.1.3 GDB远程调试建立的条件 <br>
利用GDB进行远程调试并不像在本机上调试一个可执行程序那么简单,因为需要在两台机器 <br>
的连接的基础上进行调试, <br>
6.1.3.1 远程主机上stub要实现的函数接口 <br>
一般的做法是把需要调试的程序在本地主机上进行编译,注意要加上-g调试选项,然后将 <br>
程序的一份拷贝放在远程机器上,本机上保留该程序的符号表,用于调试的时候读入符号 <br>
和程序代码。然后就需要把两台机器连接起来,这里介绍的是通过串口线的方式。在本地 <br>
主机上输入target remote /dev/ttyS0命令,本地主机就通过串口1和远程主机里面的stu <br>
b程序相连接。当然,对于不同的体系结构的系统,需要编写不同的stub程序,在GDB的发 <br>
布套件里面提供了缺省stub文件,如针对Sparc机器的sparc-stub.c文件,针对m68000的m <br>
68k-stub.c和intel 386的i386-stub.c文件。 <br>
在这个stub文件里面实现的函数接口有如下这些: <br>
1)串口驱动支持: <br>
 int getDebugChar() <br>
从串口设备读取一个字节的数据; <br>
 void putDebugChar() <br>
向串口设备写一个字节的数据; <br>
2)连接之后控制程序的运行和中止: <br>
 set_debug_traps() <br>
用于在你调试的程序停止的时候挂在中断上,如果有调试的中断到达,就进入handle_exc <br>
eption()函数。那么在你需要调试的程序的开始一定要加上handle_exception()函数的调 <br>
用。 <br>
 handle_exception() <br>
是中断处理的整个过程。可以说,调试的大部分内容都是在这里完成的。要知道的是,程 <br>
序并不会显式的调用它,而是通过set_debug_traps()函数里面给中断处理函数指针初始化 <br>
的时候把它写进去的。在你的程序停止运行(比如说,出现了断点)的时候,通过这个函数 <br>
内部的操作和主机的GDB进行通信,那么还可以说,就是在这里实现和串口通信,从而完成 <br>
调试的。 <br>
实际上,我们可以认为handle_exception()函数完成的就是GDB在主机里面完成的工作。它 <br>
首先是发送一些主机的状态信息,比如说是寄存器的值一类的,然后继续运行,检索和发 <br>
送GDB需要的数据信息,直到你的GDB要求程序继续运行,这个时候handle_exception()把 <br>
控制权交回给机器。 <br>
<br>
 break_point() <br>
这个函数使得你的程序里面包含有一个断点.在某些特殊的情况下,这可能是你的GDB获得控 <br>
制权的唯一办法. <br>
<br>
6.1.3.2 调试双方数据包的传送格式 <br>
在本地主机上的GDB里面的remote.c文件负责向远程主机上的stub.c发送数据包。所有的调 <br>
试信息数据包都是使用调试信息+校验码组成并进行传送的,在调试信息的开始都是用’$ <br>
’符号作为标记,而在调试信息的结尾都用’#’符号作为标记,如下所示: <br>
$<调试信息>#<校验码> <br>
校验码的值是调试信息里面每个字符的ASCII码和模操作256的结果。 <br>
在接收到数据包之后,用’+’的回答作为接收到正确的数据,用’-‘表示接收出错,要 <br>
求重新发送数据。 <br>
另外,从主机的GDB可以发送一些命令消息数据,具体描述如下: <br>
g: CPU寄存器的值 <br>
G: 设置CPU寄存器的值 <br>
maddr,count: 在addr位置读取count个字节的数据 <br>
Maddr,count: 在addr位置写count个字节的数据 <br>
c/caddr: 在当前位置,重新开始执行或者是从addr的位置开始 <br>
s/saddr: 单步执行当前的指令,或者执行到指定的addr位置. <br>
k: 杀掉target进程 <br>
?: 打印出最近的信号(signal) <br>
如果这些命令消息都能实现,在使用GDB的时候,就可以像在本机调试程序一样。因为GDB <br>
调试程序只需要向应用程序发出这些控制信号就足够了。 <br>
6.1.3.3 调试步骤的介绍 <br>
需要远程调试程序时,首先要对你需要进行远程调试的程序做一些改造,在程序的开始插 <br>
入set_debug_traps()和break_point()函数,然后重新编译,并且将GDB Stub,你的程序 <br>
,还有串口驱动程序等能一起连接在一起成为新的可执行程序,将它的一份拷贝到远程主 <br>
机上。 <br>
然后按照如下的步骤: <br>
 将两台机器用串口线连接起来 <br>
 将需要调试的程序拷贝到远程主机 <br>
 在本地主机启动GDB,读入需要调试的程序的符号表和程序代码 <br>
 使用target remote命令建立和远程主机的连接 <br>
 然后就像和使用一般的GDB一样进行程序的调试了。 <br>
6.2 Linux的内核调试环境的搭建 <br>
Linux内核调试环境,需要进行调试的程序就是远程的Linux内核,就像前面所说的,就是 <br>
需要在Linux内核里面做一些修改,并且提供一个stub文件。然后把stub,串口驱动程序和 <br>
Linux内核编译连接在一起。利用这个核心启动的系统,在需要进行调试的时候,激活程序 <br>
的断点,等待本地主机的连接,然后,就可以进行内核的调试了。 <br>
下面针对KGDB-2.3.35(KGDB是Kernel Debugger的缩写,用来调试Linux内核的工具,)的 <br>
体系结构,介绍如何对Linux内核进行修改,从而搭建起Linux的内核调试环境;下一节将 <br>
介绍如何通过对RTLinux的内核修改,从而调试RTLinux的实时内核。 <br>
6.2.1 串口驱动程序模块和数据包传送函数 <br>
 struct serial_state* gdb_serial_setup(int ttyS, int baud); <br>
用来初始化串口。 <br>
定义在drivers/char/serial.c中 <br>
入口参数为:ttyS——串口号 <br>
baud——传输波特率 <br>
出口参数为该串口的状态 <br>
在KGDB里面该串口的初始化是在gdb_hook()里面通过调用gdb_serial_setup()来完成 <br>
的。 <br>
 int getDebugChar(); <br>
调用read_char()从串口获得一个字节的数据,并且返回它 <br>
 void putDebugChar(int chr); <br>
调用write_char()向串口写一个字节的数据, <br>
 char* getpacket(); <br>
利用getDebugChar()函数,分析从调试主机传过来的数据包,将调试信息和校验信息区 <br>
分开来,并且检查校验信息时候正确。正确的数据,将调试信息作为返回值返回。 <br>
 void putpacket(char* buffer); <br>
利用putDebugChar()将入口参数加上校验信息,并且发送出去。 <br>
 static int read_data_bfr(void); <br>
直接从硬件端口读取一个字节的数据,并且返回它 <br>
 static int read_char(void); <br>
如果在缓冲区里面存在没有被读取的数据,那么读取它;否则调用read_data_bfr()从硬 <br>
件端口读取。 <br>
缓冲区由中断服务程序gdb_interrupt()生成 <br>
 static void write_char(int chr); <br>
直接向串口端口写一个字节的数据 <br>
6.2.2 stub程序的函数接口 <br>
 void set_debug_traps(void); <br>
如前面所述,在需要进行远程调试的程序中需要加入这个函数的调用。在这个函数中,向 <br>
系统注册调试过程中的处理函数:handle_exception()。在启动进行调试的最开始的时 <br>
候,通过运行这个函数,向系统注册handle_exception(),并且回应给本地主机第一个 <br>
数据信息,表示双方系统已经通过串口相连接。 <br>
 void break_point(void); <br>
进入中断,运行中断处理程序。这里的中断处理过程是gdb_interrupt(),在gdb_hook( <br>
)中注册。 <br>
 int handle_exception(int exceptionVector, int signo, int err_code, s <br>
<br>
ruct pt_regs* linux_regs); <br>
该函数是最主要的函数,在6.1.3.1里已经做了详细介绍。在这个函数中一边响应本地主机 <br>
上的GDB发送过来的控制信号,一边控制远程主机上Linux内核的运行状况。根据发送过来 <br>
的控制信号(6.1.3.2中介绍的命令数据信息),对本地的内和运行状况做出修改或者返回 <br>
需要的数据信息。 <br>
举个例子说,如果本地主机发送过来了g命令信息,说明GDB需要得到远程主机上的所有寄 <br>
存器的信息,那么在函数的流程中有如下一段代码: <br>
______________________________________________________________ <br>
583 case ‘g’: <br>
584 regs_to_gdb_regs(gdb_regs, ®s); <br>
585 mem2hex((char*)gdb_regs, remcomOutBuffer, NUMREGBYTES, 0); <br>
586 break; <br>
______________________________________________________________ <br>
第583行进入g的case过程,第584行将系统的寄存器转换成gdb_regs,KGDB定义的寄存器格 <br>
式,然后在第585行将gdb_regs转换成十六进制的格式,并且存放到remcomOutBuffer里面 <br>
。在第586行break退出之后,在679行将remcomOutBuffer的数据发送到本地主机上。 <br>
679 putpacket(remcomOutBuffer); <br>
handle_exception()函数并不是显式调用的,而是通过linux_debug_hook这个函数指针 <br>
进行调用的。linux_debug_hook函数指针定义在arch/i386/kernel/traps.c里面。 <br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -