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相关的行为.

你可能感兴趣的:(DB)