荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: 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(&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=%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软件 网络书店