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的时候因为出现问题而core dump出来的一个文件,里面包括了这个程序读写的那段内存的影像,而可执行文件只是包括了程序的代码和变量。GDB在运行的时候就先在core文件里面 找,然后在可执行文件里面找你需要访问的内存数据。

如果运行了run命令,可执行程序就激活了一个进程的产生。这种情况下,所有的GDB的命令就从进程这个活动target里面获取数据,在core文件和可执行文件里面的地址就没有用处了。

使用exec-file命令来指定可执行文件作为调试目标,使用core-file命令来指定core文件。如果要指定一个活动的进程作为调试的目标,则使用attach命令。

 

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

 

target type parameters

将GDB的主机环境和目标机器或进程连接起来,这是在使用target命令时的通用结构,使用type来决定用什么协议和被调试的程序通信。 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/ttyS0)。

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 Runtime 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和Intel386的体系结构。在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里面的remote.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进行patch的版本。安装的过程如下:

首先下载linux-2.2.12.tar.gz的核心源代码,将其解在/usr/src/linux-2.2下面,然后用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对核心进行配置,选上“Kernel support for GDB”这个选项,对应于核心代码里面的宏就是CONFIG_GDB。以后只要判断CONFIG_GDB是选上的,这段代码就要进行编译。

从Kgdb的这个patch文件里面就可以看出整个kgdb是如何进行核心的调试的。

 

(一) 串口设备的驱动模块

在drivers/char/serial.c里面增加了对kgdb需要的串口设备驱动的支持函数: struct serial_state * gdb_serial_setup(int ttyS, int baud)。

入口参数:串口号ttyS,传输波特率baud。

在这个函数里面,根据ttyS和baud的值初始化出一个串口,用于将来的数据传输。返回就是这个串口的状态指针。

 

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

 

调试远程机器的核心已经改造成为了在系统的启动导入核心的时候,让导入过程暂停,将控制交给远程的GDB,这个核心也可以用于正常的核心来使用,区别就在于在启动的时候将一个gdb的参数传给核心。

这段是对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 $# */
//寻找$ # 的匹配
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;
/* $ # . */
//格式化成$ # 的格式
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) != '+');
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值