荔园在线
荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀
[回到开始]
[上一篇][下一篇]
发信人: jjksam (UNIX+C+XML+??), 信区: Linux
标 题: Linux核心调试环境的搭建(转寄)[转载]
发信站: 荔园晨风BBS站 (Wed Apr 17 15:13:59 2002), 转信
【 以下文字转载自 jjksam 的信箱 】
【 原文由 jjksam@smth.org 所发表 】
发信人: ysqcn.bbs@apue.dhs.org (岁月无声), 信区: KernelTech
标 题: Linux核心调试环境的搭建
发信站: APUE (Mon Jan 14 21:57:22 2002)
转信站: SMTH!thunews.dhs.org!news.tiaozhan.com!APUE
标题:Linux核心调试环境的搭建
作者:刘文峰
来源:《共创软件》电子版1期
一、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(®s) ;//打印出寄存器的值
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(®s) ;
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, ®s) ;//将寄存器的值传递给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, ®s) ;//转换成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(®s) ;
}
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=%02x '%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) != '+');
}
(完)
--
※ 来源:.UNIX编程WWW apue.dhs.org. [FROM: 211.69.197.81]
--
※ 转载:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.0.146]
[回到开始]
[上一篇][下一篇]
荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店