kdb代码分析(三)

Linux中有些模块,你看明白它怎么初始化的你基本上就能明白它是怎么工作了,比如usb-storage,以及usb hub driver,但有些模块就没有这么简单了,就比如uhci/ehci,就比如kdb.初始化完了之后故事才刚刚拉开帷幕,如果拿近期百家讲坛热播的纪连海老师讲的李连英的故事对比,那么现在也就相当于李连英公公刚刚进宫,刚刚开始他那伟大的太监生涯.

usb-storage那样的模块,你可以很清楚它的结构,从哪里开始到哪里结束,整个就是一条直线.kdb就不一样了,它初始化完成了之后,就将准备应对多种情况了,比如你进入kdb,这就有多种情形,你可以直接调用相关的宏进入kdb,也可以设置断点来进入kdb,你可以按pause键进入kdb,也可以在serial console上按ctrl-a进入kdb,还可能是系统崩溃了自动进入kdb.总而言之有诸多的可能,所以就要有相应的代码来应付.下面我们首先就先来看一下,从串行终端上按了ctrl-a之后,为什么就可以进入kdb.

很明显,这里牵涉到了serial console的驱动,更准确地说其实是Intel 8250串口芯片驱动.虽然串口芯片很多,但是Intel 8250无疑是最有名的,大多数服务器上的串口都是8250芯片. kdb-v4.4-2.6.22-common-1这个patch中说了,以下四个文件是作了修改的.

     17  drivers/serial/8250.c         |   53

     18  drivers/serial/8250_early.c   |   34

     19  drivers/serial/sn_console.c   |   73

     25  include/linux/console.h       |    5

很长一段时间我一直困惑,计算机怎么知道我按了”control-a”,后来才明白,键盘上的control-a实际上对应的是ASCII码中的001.(control-b对应002,control-c对应003,…另外,control-@对应000)所以从键盘驱动来说,它就把这个组合键当作一个字符来处理.

kdbpatchdrivers/serial/8250.c中加了这么一段:

   3295 Index: linux/drivers/serial/8250.c

   3296 ===================================================================

   3297 --- linux.orig/drivers/serial/8250.c

   3298 +++ linux/drivers/serial/8250.c

   3299 @@ -45,6 +45,19 @@

   3300  #include <asm/irq.h>

   3301

   3302  #include "8250.h"

   3303 +#include <linux/kdb.h>

   3304 +#ifdef CONFIG_KDB

   3305 +/*

   3306 + * kdb_serial_line records the serial line number of the first serial console.

   3307 + * NOTE: The kernel ignores characters on the serial line unless a user space

   3308 + * program has opened the line first.  To enter kdb before user space has opened

   3309 + * the serial line, you can use the 'kdb=early' flag to lilo and set the

   3310 + * appropriate breakpoints.

   3311 + */

   3312 +

   3313 +static int  kdb_serial_line = -1;

   3314 +static const char *kdb_serial_ptr = kdb_serial_str;

   3315 +#endif /* CONFIG_KDB */

   3316

   3317  /*

   3318   * Configuration:

   3319 @@ -1287,6 +1300,20 @@ receive_chars(struct uart_8250_port *up,

   3320

   3321         do {

   3322                 ch = serial_inp(up, UART_RX);

   3323 +#ifdef CONFIG_KDB

   3324 +               if ((up->port.line == kdb_serial_line) && kdb_on == 1) {

   3325 +                   if (ch == *kdb_serial_ptr) {

   3326 +                       if (!(*++kdb_serial_ptr)) {

   3327 +                           atomic_inc(&kdb_8250);

   3328 +                           kdb(KDB_REASON_KEYBOARD, 0, get_irq_regs());

   3329 +                           atomic_dec(&kdb_8250);

   3330 +                           kdb_serial_ptr = kdb_serial_str;

   3331 +                           break;

   3332 +                       }

   3333 +                   } else

   3334 +                       kdb_serial_ptr = kdb_serial_str;

   3335 +               }

   3336 +#endif /* CONFIG_KDB */

   3337                 flag = TTY_NORMAL;

   3338                 up->port.icount.rx++;

   3339

这里的kdb_serial_str其实就是control-a,或者说用ascii码的形式表示为”/001”.这里的receive_char很明显,就是串行终端接收字符时调用的函数.ch就是接收到的字符,如果它是control-a,那么3328添加的代码就会执行,换言之,kdb()函数会被调用.

不过这里我们需要注意的是,当我还是青春期的时候,当我还在看琼瑶剧的时候,当我还迷恋快乐大本营的时候,人们要从串行终端进入kdb得按control-a,但后来人们发现control-a进入不了kdb,要进入kdb得按另外的键,这就是escape键后接KDB.

关于这一点,原因是kdb_serial_str这个字符串经过了修改,曾几何时,它是被定义为/001,但现在你会发现,这个字符串被定义为”/eKDB”,”/e”实际上对应你敲击的键盘就是escape.关于这个字符串的定义,我们可以从patch里面找到:

   8639 +/*

   8640 + * kdb_serial_str is the sequence that the user must enter on a serial

   8641 + * console to invoke kdb.  It can be a single character such as "/001"

   8642 + * (control-A) or multiple characters such as "/eKDB".  NOTE: All except the

   8643 + * last character are passed through to the application reading from the serial

   8644 + * console.

   8645 + *

   8646 + * I tried to make the sequence a CONFIG_ option but most of CML1 cannot cope

   8647 + * with '/' in strings.  CML2 would have been able to do it but we lost CML2.

   8648 + * KAO.

   8649 + */

   8650 +const char kdb_serial_str[] = "/eKDB";

   8651 +EXPORT_SYMBOL(kdb_serial_str);

所以结合上面的代码来看,kdb_serial_str表示一个const的字符串,kdb_serial_ptr则是一个char型指针,指针开始指向kdb_serial_str,然后不停的游荡,每次串行终端上有输入,换言之,ch有值,就拿它和kdb_serial_ptr所指向的字符相比较,如果相同就令kdb_serial_ptr指向下一个字符,然后接着如果你继续输入,就继续比较,直到比较完了以后发现,你输入的恰恰就是<escape>KDB,那么调用kdb(),从而进入kdb.下面是效果图:(在串行终端上KB都没有回显出来,只有D回显了.)

[root@localhost ~]# D

Entering kdb (current=0xffffffff805563a0, pid 0) due to Keyboard Entry

kdb>

不过像我这种习惯了按control-akdb的人,一般会把这里/eKDB手工改为/001.

知道了serial console这边是如何进入kdb,我们再来看本地键盘,在这里只要你按Pause键就可以进入kdb,这又是为什么呢?看人家的patch改了什么:

   3182 Index: linux/drivers/char/keyboard.c

   3183 ===================================================================

   3184 --- linux.orig/drivers/char/keyboard.c

   3185 +++ linux/drivers/char/keyboard.c

   3186 @@ -40,6 +40,9 @@

   3187  #include <linux/sysrq.h>

   3188  #include <linux/input.h>

   3189  #include <linux/reboot.h>

   3190 +#ifdef CONFIG_KDB

   3191 +#include <linux/kdb.h>

   3192 +#endif /* CONFIG_KDB */

   3193

   3194  extern void ctrl_alt_del(void);

   3195

   3196 @@ -1138,6 +1141,13 @@ static void kbd_keycode(unsigned int key

   3197                         if (keycode < BTN_MISC && printk_ratelimit())

   3198                                 printk(KERN_WARNING "keyboard.c: can't emulate rawmode for keycode %d/n", keycode);

   3199

   3200 +#ifdef CONFIG_KDB

   3201 +       if (down && !rep && keycode == KEY_PAUSE && kdb_on == 1) {

   3202 +               kdb(KDB_REASON_KEYBOARD, 0, get_irq_regs());

   3203 +               return;

   3204 +       }

   3205 +#endif /* CONFIG_KDB */

   3206 +

   3207  #ifdef CONFIG_MAGIC_SYSRQ             /* Handle the SysRq Hack */

   3208         if (keycode == KEY_SYSRQ && (sysrq_down || (down == 1 && sysrq_alt))) {

   3209                 if (!sysrq_down) {

很显然,就是修改drivers/char/keyboard.c,这就是键盘驱动,我们看到会比较keycodeKEY_PAUSE,如果相同,就说明你输入的是pause,于是3202行这里我们看到kdb()再一次被调用.

这样我们就明白了为什么从串行终端按control-a以及从本地键盘按pause键会触发kdb.

但这些方式都太直接了,而且是你主动要进kdb,颇有一种纸上谈兵的味道.须知有的时候,进入kdb并不是你主观上期望的,往往是系统崩溃的时候自动进入的,这又是怎么回事儿呢?

来看一个关键的函数,来自kernel/panic.c:

     60 NORET_TYPE void panic(const char * fmt, ...)

     61 {

     62         long i;

     63         static char buf[1024];

     64         va_list args;

     65 #if defined(CONFIG_S390)

     66         unsigned long caller = (unsigned long) __builtin_return_address(0);

     67 #endif

     68

     69         /*

     70          * It's possible to come here directly from a panic-assertion and not

     71          * have preempt disabled. Some functions called from here want

     72          * preempt to be disabled. No point enabling it later though...

     73          */

     74         preempt_disable();

     75

     76         bust_spinlocks(1);

     77         va_start(args, fmt);

     78         vsnprintf(buf, sizeof(buf), fmt, args);

     79         va_end(args);

     80         printk(KERN_EMERG "Kernel panic - not syncing: %s/n",buf);

     81         bust_spinlocks(0);

     82

     83         /*

     84          * If we have crashed and we have a crash kernel loaded let it handle

     85          * everything else.

     86          * Do we want to call this before we try to display a message?

     87          */

     88         crash_kexec(NULL);

     89

     90 #ifdef CONFIG_SMP

     91         /*

     92          * Note smp_send_stop is the usual smp shutdown function, which

     93          * unfortunately means it may not be hardened to work in a panic

     94          * situation.

     95          */

     96         smp_send_stop();

     97 #endif

     98

     99         atomic_notifier_call_chain(&panic_notifier_list, 0, buf);

    100

    101         if (!panic_blink)

    102                 panic_blink = no_blink;

    103

    104         if (panic_timeout > 0) {

    105                 /*

    106                  * Delay timeout seconds before rebooting the machine.

    107                  * We can't use the "normal" timers since we just panicked..

    108                  */

    109                 printk(KERN_EMERG "Rebooting in %d seconds..",panic_timeout);

    110                 for (i = 0; i < panic_timeout*1000; ) {

    111                         touch_nmi_watchdog();

    112                         i += panic_blink(i);

    113                         mdelay(1);

    114                         i++;

    115                 }

    116                 /*      This will not be a clean reboot, with everything

    117                  *      shutting down.  But if there is a chance of

118                  *      rebooting the system it will be rebooted.

    119                  */

    120                 emergency_restart();

    121         }

    122 #ifdef __sparc__

    123         {

    124                 extern int stop_a_enabled;

    125                 /* Make sure the user can actually press Stop-A (L1-A) */

    126                 stop_a_enabled = 1;

    127                 printk(KERN_EMERG "Press Stop-A (L1-A) to return to the boot prom/n");

    128         }

    129 #endif

    130 #if defined(CONFIG_S390)

    131         disabled_wait(caller);

    132 #endif

    133         local_irq_enable();

    134         for (i = 0;;) {

    135                 touch_softlockup_watchdog();

    136                 i += panic_blink(i);

    137                 mdelay(1);

    138                 i++;

    139         }

    140 }

    141

    142 EXPORT_SYMBOL(panic);

每当系统崩溃的时候,或者我们经常说的Oops的时候,这个函数会被调用.这个文件我们并没有改过,kdbpatch没有对它作任何修改.不过我们注意到这其中调用了atomic_notifier_call_chain(),此函数的第一个参数不是别人,正是&panic_notifier_list,你不要厚着脸皮说你没见过这玩艺,kdb_init()中就露过脸了,当时我们有下面这句:

  12434 +       atomic_notifier_chain_register(&panic_notifier_list, &kdb_block);

那会儿是向panic_notifier_list这张表注册,这会儿就该使用这张表了, atomic_notifier_call_chain这么一调用,凡是注册到这张表里的结构体变量都会受到影响,它们所关联的那个函数就会被调用,对于kdb_block来说,前面咱们也介绍过,与之关联的那个函数就是kdb_panic(),所以这时候,kdb_panic()会被调用,从而进入了kdb.

关于kdb()以及KDB_ENTER()我们现在能告诉你的就是,这俩都能带你进入kdb,至于它们具体是怎么做的,先搁一搁,下面即将会讲到.

这就是三种进入kdb的情形.如果说你们公司有一台服务器,跑的是Linux,上面装了kdb.那么前两种方法进入kdb是你人为的,是故意的,或者说恶意的,类似于恶意讨薪,恶意取款,恶意打工大多数和老百姓相关的行为;而后一种方法进入kdb往往意味着真的是系统出了问题,这种情况是kdb真正发挥作用的时候,是合理的,类似于合理贪污,合理违法,合理拆迁等大多数和go-vern-ment相关的行为.

 
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
The kernel has two different debugger front ends (kdb and kgdb) which interface to the debug core. It is possible to use either of the debugger front ends and dynamically transition between them if you configure the kernel properly at compile and runtime. Kdb is simplistic shell-style interface which you can use on a system console with a keyboard or serial console. You can use it to inspect memory, registers, process lists, dmesg, and even set breakpoints to stop in a certain location. Kdb is not a source level debugger, although you can set breakpoints and execute some basic kernel run control. Kdb is mainly aimed at doing some analysis to aid in development or diagnosing kernel problems. You can access some symbols by name in kernel built-ins or in kernel modules if the code was built with CONFIG_KALLSYMS. Kgdb is intended to be used as a source level debugger for the Linux kernel. It is used along with gdb to debug a Linux kernel. The expectation is that gdb can be used to "break in" to the kernel to inspect memory, variables and look through call stack information similar to the way an application developer would use gdb to debug an application. It is possible to place breakpoints in kernel code and perform some limited execution stepping. Two machines are required for using kgdb. One of these machines is a development machine and the other is the target machine. The kernel to be debugged runs on the target machine. The development machine runs an instance of gdb against the vmlinux file which contains the symbols (not boot image such as bzImage, zImage, uImage...). In gdb the developer specifies the connection parameters and connects to kgdb. The type of connection a developer makes with gdb depends on the availability of kgdb I/O modules compiled as built-ins or loadable kernel modules in the test machine’s kernel.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值