kgdb源代码分析(2.6.27)
本文由penny编写, 你可以通过发邮件到[email protected]联系penny,
也可以直接评论本文章与penny交流.
0. 概述
1. 异步通知
2. 准备工作
3.gdb 远程串行协议(GDB remote serial protocol)
4. 命令实现:
4.1 断点.
4.2 continue 和 step
5 初始化时机.
5.初始化时机
看完上面这些内容,难免有人会问,那kgdb在什么时候起来的呀?假如我们用
"kgdboc=ttyS0,115200 kgdbwait"作为启动参数.就在 drivers/serial/kgdboc.c 中的
module_init(init_kgdboc)里面.就这样完了吗?那内核究竟在启动的那个阶段对kgdb有反应?
这里就扯远一点,暂时离开一下 kgdb.我们从module_init开始,它的定义在include/linux/init.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 259 #define module_init(x) __initcall(x); 204 #define __initcall(fn) device_initcall(fn) 199 #define device_initcall(fn) __define_initcall("6",fn,6) 159 /* initcalls are now grouped by functionality into separate 160 * subsections. Ordering inside the subsections is determined 161 * by link order. 162 * For backwards compatibility, initcall() puts the call in 163 * the device init subsection. 164 * 165 * The `id' arg to __define_initcall() is needed so that multiple initcalls 166 * can point at the same handler without causing duplicate-symbol build errors. 167 */ 168 169 #define __define_initcall(level,fn,id) \ 170 static initcall_t __initcall_##fn##id __used \ 171 __attribute__((__section__(".initcall" level ".init"))) = fn (include/linux/init.h) 135 typedef int (*initcall_t)(void); |
一个程序编译好之后有代码段,只读数据段,数据段等一些段,而
__attribute__((__section__(...)))这个gcc的编译扩展属性,它能够把函数或数据放入
指定名字的段中。
那__define_initcall这一团是什么意思呢?首先这是一个定义和初始化的语句,
initcall_t是一个函数指针类型,所以这里定义一个静态的函数指针,它的名字是
__initcall_##fn##id,"##"在宏里面是连接两个字符串,所以这个变量名是根据进来的参数
名字不一样而不同.并且把传进来fn(我们这里就是init_kgdboc())赋给这个变量。最重要的
是把这个变量放在".initcall" level ".init"段中,我们的level是6,所以定义的这个函数指针
放在.initcall6.init这个段中。
连接器允许我们通过连接脚本修改默认段的起始位置,段的内容等很多东西,先来看看
x86的脚本arch/x86/kernel/vmlinux_32.lds.S。从名字我们大概可以猜出我们上面的
那些段对应于这个文件中的:
1 2 3 4 | 133 .initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {134 __initcall_start = .; 135 INITCALLS 136 __initcall_end = .; 137 } |
连接脚本里面,'.'代表当前位置,所以__initcall_start和__initcall_end分别代表.
initcall.init段的开始位置和结束位置,这种东西可以理解为一个常量,后者我们下面马上会用到。
而 INITCALLS 则在 include/asm-generic/vmlinux.lds.h 里面定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 363 #define INITCALLS \ 364 *(.initcallearly.init) \ 365 VMLINUX_SYMBOL(__early_initcall_end) = .; \ 366 *(.initcall0.init) \ 367 *(.initcall0s.init) \ 368 *(.initcall1.init) \ 369 *(.initcall1s.init) \ 370 *(.initcall2.init) \ 371 *(.initcall2s.init) \ 372 *(.initcall3.init) \ 373 *(.initcall3s.init) \ 374 *(.initcall4.init) \ 375 *(.initcall4s.init) \ 376 *(.initcall5.init) \ 377 *(.initcall5s.init) \ 378 *(.initcallrootfs.init) \ 379 *(.initcall6.init) \ 380 *(.initcall6s.init) \ 381 *(.initcall7.init) \ 382 *(.initcall7s.init) |
像__initcall_start和__initcall_end一样,__early_initcall_end的值也是段里面的一个位置。
上面两段话合起来就是说在连接内核时,把所有放在中间文件(输入文件)中的.initcallxx.init段里面的东西都连接到输出文件的.initcall.init段中。
所以我们刚才的那个函数指针最后会在目标文件的.initcall.init段中.那现在问题是谁会去管这个段? 它就是do_initcalls
而INITCALLS则在include/asm-generic/vmlinux.lds.h里面定义:
1 2 3 4 5 6 7 8 9 10 11 | (init/main.c) 749 static void __init do_initcalls(void) 750 { 751 initcall_t *call; 752 753 for (call = __early_initcall_end; call < __initcall_end; call++) 754 do_one_initcall(*call); 755 756 /* Make sure there is no pending stuff from the initcall sequence */ 757 flush_scheduled_work(); 758 } |
do_initcalls()遍历一次放在__early_initcall_end和__initcall_end之间的函数指针,并
依次调用它们指向的函数,当然包括我们的init_kgdboc()了。那do_initcalls()又是在
那里被调用了呢?
1 2 3 4 5 | start_kernel() '->rest_init() '->kernel_init() '->do_basic_setup() '->do_initcalls() |
这样清楚了吧,do_initcalls 是在 init 进程里面才被调用的。init_kgdboc()所做的事
情由于本人知识有限,暂时说不出来, 不过它所干的最重要的两件事情是: 根据内核启动参
数寻找对应的驱动和在初始化完成后直接执行一条 asm("int $3")汇编手动触发一个中断,
把控制权交给 kgdb。