Linux核心调试环境的搭建

发 布 时 间 : 2008-11-18 来 源 : 来自网络 作 者 : 匿名 浏 览 :
Linux核心调试环境的搭建
一、GDB远程调试方法的使用

GDB是GNU C自带的调试工具,它可以使得程序的开发者了解到程序在运行时的详细细节,从而
能够很好地除去程序的错误,达到调试的目的。英文debug的原意就是“除虫”,而gdb的全称
就是Gnu DeBugger。目前GDB支持的可以调试的语言有C、C 、Modula-2等几种语言,现在还
可能支持Fortran语言的调试。
使用GDB可以完成下面这些任务:
(1) 运行程序,可以给程序加上你所需要的任何条件。
(2) 在规定的条件下让程序停止。
(3)检查在程序停止的时候它所处的状态。
(4) 在程序中改变一些数据,以便更好地改正程序的错误。
由于这里主要是介绍GDB的远程调试方法,因此关于GDB的基础问题不再多述,尽早进入主题,
说明清楚GDB远程调试的原理和使用方法。
在这里说明的GDB是3.5版本以后的版本,以前的版本可能会有点不适用,可以到GNU的主页上或
者你的Unix/Linux厂家的主页上去下载,因为GDB是免费发放的,不用担心要你付钱,所以只需
要把版权信息同时下载过来就行。
(一) 什么是调试目标(target)
在用GDB进行调试的时候,需要制定一个调试目标,就是所谓的target。target实际上就是你的
程序所获得的执行环境。一般的情况下,要调试的程序和你当前所在的环境是完全一样的,那
么用file或者core命令就可以指定调试目标。另外一种情况就是需要详细描述的,如果需要调
试的程序和你现在所在的环境不同,或者说需要调试的环境上根本无法运行起GDB,那么就没有
办法使用file或者core命令来指定调试目标了。这里,就需要使用远程调试功能,通过一台可
以使用KGDB的机器,通过串口的通信协议和被调试的程序所在的机器连接,来调试程序,这种
情况是很多的,比如说你要调试一个独立的系统,或者说是实时系统,都需要和这个系统建立起
连接才能完成调试过程。在GDB里面就是使用target命令完成这项工作的。
在GDB里面,target实际上分为三种:进程、core 文件。可执行文件.GDB上可以同时跑三个不
同种类的target,它们之间互相都是有关系的。比如说调试的时候,指定可执行文件,同时又指
定这个文件执行时的一个进程和上次运行发生core dump的core文件。
比如,在调试的时候,先指定可执行文件a.out,那么此时活动的target就是a.out这个可执行文
件。此时可以制订一个core文件,这个core文件是你上次运行a.out的时候因为出现问题而co
re dump出来的一个文件,里面包括了这个程序读写的那段内存的影像,而可执行文件只是包括
了程序的代码和变量。GDB在运行的时候就先在core文件里面找,然后在可执行文件里面找你
需要访问的内存数据。
如果运行了run命令,可执行程序就激活了一个进程的产生。这种情况下,所有的GDB的命令就
从进程这个活动target里面获取数据,在core文件和可执行文件里面的地址就没有用处了。
使用exec-file命令来指定可执行文件作为调试目标,使用core-file命令来指定core文件。如
果要指定一个活动的进程作为调试的目标,则使用attach命令。

(二) 使用target的一些命令解释

target type parameters
将GDB的主机环境和目标机器或进程连接起来,这是在使用target命令时的通用结构,使用ty
pe来决定用什么协议和被调试的程序通信。parameters是这种类型的target在调试时需要的
参数,一般是通信设备名称,需要连接主机的名称、进程数目和波特率等数据。
help target
显示你可以使用的target的名称,要知道你目前使用的target的信息,只要使用info target
命令就可以了。
target exec program
一个可执行文件。target exec program 等同于 exec-file program。
target core filename
一个core文件。target core corefile 等同于 core-file corefile。
target remote dev
通过由GDB自己定义的串口协议的远程串口目标。dev是需要进行连接的串口设备(如/dev/tt
yS0)。
target child
调试子进程,使用run来运行一个子进程,然后进行调试。
target extended-remote dev
和target remote dev类似,也是通过串口协议调试一个远程的程序。
target linuxthreads
调试Linux下面的线程和pthread的支持。

(三) 远程调试

就像在前面所解释的那样,要是需要调试一个不能在通常情况下运行GDB的机器上的程序,就需
要使用到GDB的远程调试方法。可能会利用这个功能来调试一个操作系统的核心,或者是一个
小得连运行起调试环境的可能性都没有的机器里面的程序,想想这多么的有趣。
GDB里面就有串口或者是基于TCP/IP协议的通信方法用来将调试目标和本地机器连接起来。一
般情况下,GDB使用的是一个通用的串口协议(只是在GDB里面有的,在调试目标机器里面并没有
)。那么在你的调试目标所在的机器里面需要实现一个stub文件,这个stub文件就是替代了在
本地机器里面的GDB串口协议的位置,用来实现和本地机器的通信。
1、GDB本身自带的远程串口协议
要调试在远程的机器上的程序,必须要知道程序运行所需要的所有先决条件。举个例子说,如
果你运行C程序,那么你需要运行起C运行环境的初始化程序,一般都是一个叫crt0的程序(C R
untime environment)。这可能是由你的硬件生产商提供,也可以是你自己来写。
需要一些函数库来支持你的子过程调用,主要是控制输入和输出的部分。
2、把你的程序下载到另外一台机器的方法。这也需要从你的硬件上来考虑。
3、现在就是要考虑如何使用串口和运行GDB的机器连接的问题。我们可以分两个方面来考虑
:
(1) 在主机上(运行GDB的那台):GDB已经运行起来了,它知道如何使用这个协议;当所有的事情
已经弄好了之后,只要运行target remote 命令就可以了。
(2) 在调试目标所在的机器上:需要把你的需要调试的程序和实现GDB的远程串口协议的程序
连接起来。这个文件就是所谓的stub文件。stub文件是针对远程计算机的体系结构进行编写
的,如果你是使用sparc的机器,那么就一定要使用sparc-stub.c文件作为你的stub。这个.c文
件是由GDB提供给你的,GDB还提供了m68k-stub.c,i386-stub.c等文件分别用于m68000和Int
el386的体系结构。在stub文件里面主要是需要提供下面这些函数:
set_debug_traps()
用于在你调试的程序停止时挂在中断上。如果有调试的中断到达,就进入handle_exception(
)函数。那么在你需要调试的程序的开始一定要加上handle_exception()函数的调用。
handle_exception()
是中断处理的整个过程,可以说,调试的大部分内容都是在这里完成的。要知道的是,程序并不
会显式地调用它,而是通过set_debug_traps()函数里面给中断处理函数指针初始化的时候把
它写进去的。在你的程序停止运行的时候(比如说,出现了断点),通过这个函数内部的操作和
主机的GDB进行通信,那么还可以说,就是在这里实现和串口通信,从而完成调试的。
实际上,可以认为handle_exception()函数完成的就是GDB在主机里面完成的工作。它首先是
发送一些主机的状态信息,比如说是寄存器的值一类的信息,然后继续运行,检索和发送GDB需
要的数据信息,直到你的GDB要求程序继续运行,这个时候handle_exception()把控制权交回给
机器。
breakpoint()
这个函数使得你的程序里面包含有一个断点。在某些特殊的情况下,这可能是你的GDB获得控
制权的唯一办法。
以上三个函数是stub文件提供给计算机的调用接口,在stub内部需要提供一些内部函数来实现
这些调用接口,如下所述:
int getDebugChar()
从串口设备里面读入一个字节的数据。
void putDebugChar(int)
向串口设备写入一个字节的数据。
这两个函数足以完成任务,不过有时候在实际情况下会使用指令的缓存,那么可以再加一些包
装函数,使得发送和接收数据包更为简单。
(四) 远程调试的具体过程的做法:
(1) 检查你的系统是否支持这些对计算机的调用接口:
getDebugChar(); putDebugChar()
(2) 在需要调试的程序的开始插入这几行:
set_debug_traps();
breakpoint();
(3) 编译连接你的程序。将你的程序,GDB Stub文件,还有实现的那些调用接口等编译连接在
一起,成为你的可执行程序。
(4) 在两台机器之间用串口线连接起来。
(5) 把需要调用的程序放到远程的机器里面,启动这个程序。
(6) 在你的主机里面启动GDB,指定在远程机器上运行的exec-file,从而可以获得这个可执行
文件的符号表和程序段代码。
(7) 使用target remote命令建立和远程机器的连接。
(8) 然后就可以像使用一般的GDB一样进行程序的调试了。

(五) 通信协议的具体描述

在stub文件里面实现的是远程端的GDB串口协议的实现,在本机实现的地方是GDB里面的remot
e.c文件。
所有GDB的数据包都是用调试信息 检验码进行传送的,在调试信息的开始用“$”作为标记,在
调试信息的结束用“#”符号作为标记,结构如下所示:
$<调试信息>#<校验码>
校验码是将调试信息里面的字符加起来除以256得出来的余数。
在接收到数据包之后,用“ ”的回答作为接收到正确的数据,用“-”表示接收出错,要求重新
发送数据。
另外,从主机的GDB可以发送一些命令消息数据,具体描述如下:
g:CPU寄存器的值。
G:设置CPU寄存器的值。
maddr,count:在addr位置读取count个字节的数据。
Maddr,count:在addr位置写count个字节的数据。
c
caddr: 在当前位置,重新开始执行或者是从addr的位置开始。
s
saddr: 单步执行当前的指令,或者执行到指定的addr位置。
k:杀掉target进程。
?:打印出最近的信号(signal)。

二、KGDB的分析

Kgdb是利用GDB的远程调试方法和stub文件的写法,为Linux/FreeBSD/Unix-like操作系统开发
的核心调试工具。我这里分析的是kgdb0.2-2.2.12,是针对Linux 2.2.12版本的Kernel进行p
atch的版本。安装的过程如下:
首先下载linux-2.2.12.tar.gz的核心源代码,将其解在/usr/src/linux-2.2.12目录下面,然
后用kgdb0.2-2.2.12对核心做patch:

#cd /usr/src/linux-2.2.12/
#patch -p0 < /tmp/kgdb0.2-2.2.12

然后使用make config 或者 make menuconfig 或者 make xconfig对核心进行配置,选上“K
ernel support for GDB”这个选项,对应于核心代码里面的宏就是CONFIG_GDB。以后只要判
断CONFIG_GDB是选上的,这段代码就要进行编译。
从Kgdb的这个patch文件里面就可以看出整个kgdb是如何进行核心的调试的。

(一) 串口设备的驱动模块
在drivers/char/serial.c里面增加了对kgdb需要的串口设备驱动的支持函数: struct seri
al_state * gdb_serial_setup(int ttyS, int baud)。
入口参数:串口号ttyS,传输波特率baud。
在这个函数里面,根据ttyS和baud的值初始化出一个串口,用于将来的数据传输。返回就是这
个串口的状态指针。

(二) 如何启动核心的调试呢?

调试远程机器的核心已经改造成为了在系统的启动导入核心的时候,让导入过程暂停,将控制
交给远程的GDB,这个核心也可以用于正常的核心来使用,区别就在于在启动的时候将一个gd
b的参数传给核心。
这段是对init/main.c函数的patch,系统启动就是先运行main函数的。

#ifdef CONFIG_GDB
if (!strcmp(line,"gdb")) {/传入了gdb参数
gdb_enter = 1;/将gdb_enter0置位
continue;
}
if (!strcmp(line,"gdbttyS=")) {/如果传入了指定的串口号
gdb_ttyS = simple_strtoul(line 8,NULL,10);
continue;
}
if (!strcmp(line,"gdbbaud=")) {/如果传入了指定的波特率
gdb_baud = simple_strtoul(line 8,NULL,10);
continue;
}
#endif /* CONFIG_GDB */
如果gdb_enter == 1,那么下面的代码将被执行:
#ifdef CONFIG_GDB
if (gdb_enter)
gdb_hook();/进入这个函数,开始kgdb的控制过程
#endif

(三) gdb_hook()函数式的系统进入调试模式。

gdb_hook()函数在drivers/char/serialgdb.c文件里面被定义:
int gdb_hook(void)
{
... .../定义变量
if((ser = gdb_serial_setup(gdb_ttyS, gdb_baud)) == 0) {/初始化串口驱动设备
printk ("gdb_serial_setup() error");
return(-1);
}
gdb_port = ser->port;
gdb_irq = ser->irq;
free_irq(gdb_irq, NULL);
retval = request_irq(gdb_irq,/登记中断号
gdb_interrupt,/中断处理程序
SA_INTERRUPT,
"GDB-stub", NULL);
/*
* Call GDB routine to setup the exception vectors for the debugger
*/
set_debug_traps() ;/设定linux_debug_hook函数指针指向handle_exception()
/*
* Call the breakpoint() routine in GDB to start the debugging
* session.
*/
printk("Waitng for connection from remote gdb... ")
breakpoint() ;/设定断点
gdb_null() ;/什么也不干.
printk("Connected.\n");;
return(0) ;
} /* gdb_hook_interrupt2 */

(四) 重要函数set_debug_traps()
这个是用于和计算机的第一个接口函数,用于向系统登记在调试过程中的中断处理程序.这里
的中断处理程序是handle_exception()函数,这个函数在arch/i386/kernel/gdb.c里面定义。

void set_debug_traps(void)/defined in arch/i386/kernel/gdb.c
{
/*
* linux_debug_hook is defined in traps.c. We store a pointer
* to our own exception handler into it.
*/
linux_debug_hook = handle_exception ;/初始化linux_debug_hook函数指针
/* In case GDB is started before us, ack any packets (presumably
"$?#xx") sitting there. */
putDebugChar (' ');/发送第一个包,表示可以开始了
initialized = 1;
}


(五) 重要函数handle_exception()

这个函数前面介绍了,是整个调试的核心部分,要详细介绍.

/*
* This function does all command procesing for interfacing to gdb.
*
* NOTE: The INT nn instruction leaves the state of the interrupt
* enable flag UNCHANGED. That means that when this routine
* is entered via a breakpoint (INT 3) instruction from code
* that has interrupts enabled, then interrupts will STILL BE
* enabled when this routine is entered. The first thing that
* we do here is disable interrupts so as to prevent recursive
* entries and bothersome serial interrupts while we are
* trying to run the serial port in polled mode.
*
* For kernel version 2.1.xx the cli() actually gets a spin lock so
* it is always necessary to do a restore_flags before returning
* so as to let go of that lock.
*/

/*在这个函数里面,INT nn指令使得中断使能的flag不会发生变化。这表示当这个程序在通过
INT 3的断点运行的时候,中断仍然是允许的状态.那么我们首先要做的是关闭中断,防止递归
地进入中断。在2.1以上版本的核心里面的cli()都有一个spin lock,因此我们要调用restor
e_flags才能正常地使用spin lock*/

int handle_exception(int exceptionVector,/中断向量号
int signo,/信号
int err_code,/出错码
struct pt_regs *linux_regs)/用于调试的寄存器向量
{
int addr, length;
char * ptr;
int newPC;
unsigned long flags;
int gdb_regs[NUMREGBYTES/4];
#define regs (*linux_regs)
/*
* If the entry is not from the kernel then return to the Linux
* trap handler and let it process the interrupt normally.
*/
if ((linux_regs->eflags & VM_MASK) || (3 & linux_regs->xcs))
return(0);
save_flags(flags);
cli(); /* 2.1 kernel must have matching restore_flags */
if (remote_debug)
printk("handle_exception(exceptionVector=%d, "/打印出传入的参数信息
"signo=%d, err_code=%d, linux_regs=%p)\n",
exceptionVector, signo, err_code, linux_regs) ;
if (remote_debug)
print_regs(&regs) ;/打印出寄存器的值
switch (exceptionVector)
{
case 0: /* divide error */
case 1: /* debug exception */
case 2: /* NMI */
case 3: /* breakpoint */
case 4: /* overflow */
case 5: /* bounds check */
case 6: /* invalid opcode */
case 7: /* device not available */
case 8: /* double fault (errcode) */
case 10: /* invalid TSS (errcode) */
case 12: /* stack fault (errcode) */
case 16: /* floating point error */
case 17: /* alignment check (errcode) */
default: /* any undocumented */
break ;
case 11: /* segment not present (errcode) */
case 13: /* general protection (errcode) */
case 14: /* page fault (special errcode) *//页面异常
if (mem_err_expected)
{
/*
* This fault occured because of the get_char or set_char
* routines. These two routines use either eax of edx to
* indirectly reference the location in memory that they
* are working with. For a page fault, when we return
* the instruction will be retried, so we have to make
* sure that these registers point to valid memory.
*/

/*这个错误是因为get_char或者set_char过程出了问题。这两个函数是使用edx或eax来间接
地读取内存的数据。对一个页面异常,当我们返回的时候,这个指令会被重试,然后我们知道这
个寄存器指针指向的是个无效地址*/

mem_err = 1 ; /* set mem error flag */
mem_err_expected = 0 ;

regs.eax = (long) &garbage_loc ; /* make valid address */
regs.edx = (long) &garbage_loc ; /* make valid address */

if (remote_debug) printk("Return after memory error\n");
if (remote_debug) print_regs(&regs) ;
restore_flags(flags) ;
return(0) ;
}
break ;
}
gdb_i386vector = exceptionVector;/用传入参数初始化
gdb_i386errcode = err_code ;
/* reply to host that an exception has occurred *//表示有一个中断出现

remcomOutBuffer[0] = 'S';/应该是$吧?
remcomOutBuffer[1] = hexchars[signo >> 4];
remcomOutBuffer[2] = hexchars[signo % 16];
remcomOutBuffer[3] = 0;

putpacket(remcomOutBuffer);

while (1==1) {
error = 0;
remcomOutBuffer[0] = 0;
getpacket(remcomInBuffer);
switch (remcomInBuffer[0]) {
case '?' : remcomOutBuffer[0] = 'S';/上次的信号
remcomOutBuffer[1] = hexchars[signo >> 4];
remcomOutBuffer[2] = hexchars[signo % 16];
remcomOutBuffer[3] = 0;
break;
case 'd' : /切换远程调试方式,实际上就是改变remote_debug的值。
remote_debug = !(remote_debug); /* toggle debug flag */
printk("Remote debug %s\n", remote_debug ? "on" : "off");
break;
case 'g' : /* return the value of the CPU registers *//得到CPU寄存器的值
regs_to_gdb_regs(gdb_regs, &regs) ;/将寄存器的值传递给gdb_regs。
/把gdb_regs指向的数据放到remcomOutBuffer里面去.
mem2hex((char*) gdb_regs, remcomOutBuffer, NUMREGBYTES, 0);
break;
case 'G' : /* set the value of the CPU registers - return OK */
/设置CPU寄存器的数值
/把remcomInBuffer里面的值放到gdb_regs里面去
hex2mem(&remcomInBuffer[1], (char*) gdb_regs, NUMREGBYTES, 0);
gdb_regs_to_regs(gdb_regs, &regs) ;/转换成regs.
strcpy(remcomOutBuffer,"OK");
break;
/* mAA..AA,LLLL Read LLLL bytes at address AA..AA */
case 'm' :/maddr,count的形式
/读取addr开始的count个字节的内容.
/* TRY TO READ %x,%x. IF SUCCEED, SET PTR = 0 */
ptr = &remcomInBuffer[1];/地址信息
if (hexToInt(&ptr,&addr))
/从prt所指的地址读取数据到addr中
if (*(ptr ) == ',')
if (hexToInt(&ptr,&length))
{
ptr = 0;
mem2hex((char*) addr, remcomOutBuffer, length, 1);
if (mem_err) {
strcpy (remcomOutBuffer, "E03");
debug_error ("memory fault\n", NULL);
}
}
if (ptr)
{
strcpy(remcomOutBuffer,"E01");
debug_error("malformed read memory
command: %s\n",remcomInBuffer);
}
break;
/* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */
case 'M' :/写内存数据
/* TRY TO READ '%x,%x:'. IF SUCCEED, SET PTR = 0 */
ptr = &remcomInBuffer[1];
if (hexToInt(&ptr,&addr))
if (*(ptr ) == ',')
if (hexToInt(&ptr,&length))
if (*(ptr ) == ':')
{
hex2mem(ptr, (char*) addr, length, 1);
if (mem_err) {
strcpy (remcomOutBuffer, "E03");
debug_error ("memory fault\n", NULL);
} else {
strcpy(remcomOutBuffer,"OK");
}
ptr = 0;
}
if (ptr)
{
strcpy(remcomOutBuffer,"E02");
debug_error("malformed write memory command: %s\n",remcomInBuffer);
}
break;
/* cAA..AA Continue at address AA..AA(optional) */
/* sAA..AA Step one instruction from AA..AA(optional) */
case 'c' :/继续运行
case 's' :/单步执行
/* try to read optional parameter, pc unchanged if no parm */
ptr = &remcomInBuffer[1];
if (hexToInt(&ptr,&addr))
{
if (remote_debug)
printk("Changing EIP to 0x%x\n", addr) ;
regs.eip = addr;
}newPC = regs.eip ;/指令寄存器内容
/* clear the trace bit */
regs.eflags &= 0xfffffeff;/清除trace位
/* set the trace bit if we're stepping */
if (remcomInBuffer[0] == 's') regs.eflags |= 0x100;
if (remote_debug)
{
printk("Resuming execution\n") ;
print_regs(&regs) ;
}
restore_flags(flags) ;
return(0) ;
/* kill the program *//杀死进程
case 'k' : /* do nothing only to break the loop of while*/
break;
} /* switch */
/* reply to the request */
putpacket(remcomOutBuffer);/对主机的回答
}
restore_flags(flags) ;
return(0) ;
}


(六) 底层函数调用过程

底层的函数包括getDebugChar()和putDebugChar()函数。getDebugChar()函数是从串口获得
一个字节的数据,putDebugChar()是向串口写一个字节的数据。这两个函数在drivers/char/
serialgdb.c里面定义。

int getDebugChar(void)/from drivers/char/serialgdb.c
{/如果从串口读到数据,那么返回它.
volatile int chr ;
#if PRNT
printk("getDebugChar: ") ;
#endif
while ( (chr = read_char()) < 0 ) ;/读取数据
#if PRNT
printk("%c", chr > ' ' && chr < 0x7F ? chr : ' ') ;
#endif
return(chr) ;
} /* getDebugChar */
void putDebugChar(int chr)/from drivers/char/serialgdb.c
{
#if PRNT
printk("putDebugChar: chr=x '%c'\n", chr,
chr > ' ' && chr < 0x7F ? chr : ' ') ;
#endif
write_char(chr) ; /* this routine will wait */

} /* putDebugChar */

这里需要两个支撑函数read_char()和write_char(),用来从串口读取和写入数据,在下面的支
撑函数里面描述。

(七) 支撑函数(主要是关于I/O的读写,缓冲区的使用)
read_char()和write_char()是用来支撑getDebugChar()和putDebugChar()两个函数的.

* read_data_bfr
/*
* Get a byte from the hardware data buffer and return it
*/
static int read_data_bfr(void)/从硬件的缓冲区里面读取一个字节并且返回它。
{
if (inb(gdb_port UART_LSR) & UART_LSR_DR)
return(inb(gdb_port UART_RX));

return( -1 ) ;
} /* read_data_bfr */
* read_data
/*
* Get a char if available, return -1 if nothing available.
* Empty the receive buffer first, then look at the interface hardware.
*/
/如果有的话,读取一个字节;否则就返回-1,首先是看接收缓冲区,然后看硬件接口
static int read_char(void)
{
if (gdb_buf_in_cnt != 0) /* intr routine has q'd chars */
{/缓冲区里面有数据
int chr ;
chr = gdb_buf[gdb_buf_out_inx ] ;
gdb_buf_out_inx &= (GDB_BUF_SIZE - 1) ;
gdb_buf_in_cnt-- ;
return(chr) ;
}
return(read_data_bfr()) ; /* read from hardware *//从硬件里面读取
} /* read_char */
* write_char
/*
* Wait until the interface can accept a char, then write it
*//等待,直到接口接收到一个字符,然后写入
static void write_char(int chr)
{
while ( !(inb(gdb_port UART_LSR) & UART_LSR_THRE) ) ;
/等待并检查端口,接受一个字符
outb(chr, gdb_port UART_TX); /向硬件写
} /* write_char */
* gdb_interrupt
/*
* This is the receiver interrupt routine for the GDB stub.
* It will receive a limited number of characters of input
* from the gdb host machine and save them up in a buffer.

*这是GDB stub的接收中断处理程序,它从主机上的GDB获得有限的数据信息,然后将数据存放
在缓冲区中。

* When the gdb stub routine getDebugChar() is called it
* draws characters out of the buffer until it is empty and
* then reads directly from the serial port.

*当gdb stub的getDebugChar()被调用的时候,从缓冲区里面获取数据,直到缓冲区变空,然后
直接从串口设备读取。

* We do not attempt to write chars from the interrupt routine
* since the stubs do all of that via putDebugChar() which
* writes one byte after waiting for the interface to become
* ready.
*我们并不在中断过程里面写数据,因为stub都是通过putDebugChar()写数据的。它每次在设
备准备好之后向设备写一个字节。

* The debug stubs like to run with interrupts disabled since,
* after all, they run as a consequence of a breakpoint in
* the kernel.

*stub喜欢在中断禁止的情况下运行,毕竟,它们是在核心里面的一个断点后面运行。

* Perhaps someone who knows more about the tty driver than I
* care to learn can make this work for any low level serial
* driver.
*/
static void gdb_interrupt(int irq, void *dev_id, struct pt_regs * regs)
{/ drivers/char/serialgdb.c
int chr ;
for (;;)
{
chr = read_data_bfr() ;
if (chr < 0) break ;
if (chr == 3) /* Ctrl-C means remote interrupt */
{
breakpoint();
continue ;

}
if (gdb_buf_in_cnt >= GDB_BUF_SIZE)
{ /* buffer overflow, clear it *//缓冲区溢出,清除
gdb_buf_in_inx = 0 ;
gdb_buf_in_cnt = 0 ;
gdb_buf_out_inx = 0 ;
break ;
}
gdb_buf[gdb_buf_in_inx ] = chr ;/向缓冲区写数据
gdb_buf_in_inx &= (GDB_BUF_SIZE - 1) ;
gdb_buf_in_cnt ;
}
} /* gdb_interrupt */
* getpacket()
/* scan for the sequence $<data>#<checksum> */
/寻找$<packet info>#<checksum>的匹配
void getpacket(char * buffer)
{
unsigned char checksum;
unsigned char xmitcsum;
int i;
int count;
char ch;
do {
/* wait around for the start character, ignore all other characters */
while ((ch = (getDebugChar() & 0x7f)) != '$');/开始了一次数据
checksum = 0;
xmitcsum = -1;
count = 0;
/* now, read until a # or end of buffer is found */
while (count < BUFMAX) {
ch = getDebugChar() & 0x7f;
if (ch == '#') break;/结束
checksum = checksum ch;

buffer[count] = ch;
count = count 1;
}
buffer[count] = 0;
if (ch == '#') {
xmitcsum = hex(getDebugChar() & 0x7f) << 4;
xmitcsum = hex(getDebugChar() & 0x7f);
if ((remote_debug ) && (checksum != xmitcsum)) {/检查checksum
printk ("bad checksum. My count = 0x%x, sent=0x%x. buf=%s\n",
checksum,xmitcsum,buffer);

}
if (checksum != xmitcsum) putDebugChar('-'); /* failed checksum */
/返回一个错误

else {
putDebugChar(' '); /* successful transfer *//正确接收数据

/* if a sequence char is present, reply the sequence ID */
if (buffer[2] == ':') {
putDebugChar( buffer[0] );
putDebugChar( buffer[1] );
/* remove sequence chars from buffer */
count = strlen(buffer);
for (i=3; i <= count; i ) buffer[i-3] = buffer[i];
}
}
}
} while (checksum != xmitcsum);
}
* putpacket
/* send the packet in buffer. */
/把buffer里面的数据发送出去
void putpacket(char * buffer)
{
unsigned char checksum;
int count;
char ch;
/* $<packet info>#<checksum>. */
/格式化成$<packet info>#<checksum>的格式
do {

putDebugChar('$');
checksum = 0;
count = 0;

while ((ch=buffer[count])) {
if (! putDebugChar(ch)) return;
checksum = ch;/计算checksum

count = 1;
}

putDebugChar('#');/结束
putDebugChar(hexchars[checksum >> 4]);/checksum
putDebugChar(hexchars[checksum % 16]);
} while ((getDebugChar() & 0x7f) != ' ');
}


上一篇:嵌入式系统关键技术分析与开发应用 下一篇:Linux 运行级init详解