在内核编程和调试的过程中,对于被调试程序运行状态的观察和查看是必不可少的,kernel为此也提供了很多机制,就我曾经使用而言,包括以下3种:
1.printk:内核调试中最常用的就是printk,但是过多printk会产生刷屏的效果,如果不留意的话会将真正的错误掩盖掉。
2.kgdb: kgdb在2.6.26版本中进入了内核,它是一个互动的调试器,功能很强大,可以进行源码级别的调试,但是它的使用比较麻烦,而且对硬件要求比较高。
3.kprobes: 这
种方法类似于printk,为内核提供了一种动态的、插入式的内核调试方法。kprobes的调试机制类似于断点调试,不过不会影响程序的流程。在查看了
kernel/kprobes.c后,我觉得它在实现方法上类似于WINDOWS的SEH机制,在原先的函数堆栈帧上进行扩展,将原来的返回地址进行一次
包装,在kprobes相关部分完成之后再返回到原函数继续执行,具体可以查看Documentation下的kprobes.txt.
因为以前使用过kgdb调试研究内核的netlink机制,因此在这主要介绍一下如何使用kprobes调试.
kprobes调试的要点如下:
1.插入点。对应着struct krpobe结构,这个结构中有个成员变量symbol_name,指明了插入点的位置,这个symbol必须是内核导出的可被lookup_symbol_name搜索到的字符串。
2.pre_handler, post_handler,fault_handler,分别对应着插入点的三个不同的回调函数。
下面这个是一个简单的例子,插入点在do_fork函数处,因此symbol_name被设置为do_fork,回调函数主要显示了当前线程和线程所属进程的信息。代码如下:
/*
* You will see the trace data in /var/log/messages and on the console
* whenever do_fork() is invoked to create a new process.
*/
#include
#include
#include
static struct kprobe kp = {
.symbol_name = "do_fork",
};
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
struct thread_info *thread = current_thread_info();
printk(KERN_INFO "pre-handler thread info: flags = %x, \
status = %d, cpu = %d, task->pid = %d\n",
thread->flags, thread->status, thread->cpu, thread->task->pid);
return 0;
}
static void handler_post(struct kprobe *p, struct pt_regs *regs,
unsigned long flags)
{
/* Add by casualfish */
/* struct task_struct *task;
for_each_process(task) {
task_lock(task);
printk(KERN_INFO "pid =%x \n", task->pid);
printk(KERN_INFO "thread-info element sp = %lx, sp0 = %lx\n",
task->thread.sp, task->thread.sp0);
task_unlock(task);
}*/
struct thread_info *thread = current_thread_info();
printk(KERN_INFO "post-handler thread info: flags = %x, \
status = %d, cpu = %d, task->pid = %d\n",
thread->flags, thread->status, thread->cpu, thread->task->pid);
}
static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)
{
printk(KERN_INFO "fault_handler: p->addr = 0x%p, trap #%dn",
p->addr, trapnr);
return 0;
}
static int __init kprobe_init(void)
{
int ret;
kp.pre_handler = handler_pre;
kp.post_handler = handler_post;
kp.fault_handler = handler_fault;
ret = register_kprobe(&kp);
if (ret < 0) {
printk(KERN_INFO "register_kprobe failed, returned %d\n", ret);
return ret;
}
printk(KERN_INFO "Planted kprobe at %p\n", kp.addr);
return 0;
}
static void __exit kprobe_exit(void)
{
unregister_kprobe(&kp);
printk(KERN_INFO "kprobe at %p unregistered\n", kp.addr);
}
module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");
编译出模块并加载之后,dmesg| tail 查看如下:
Planted kprobe at c04345ea
....
post-handler thread info: flags = 80, status = 0, cpu = 1, task->pid = 12314
pre-handler thread info: flags = 80, status = 0, cpu = 1, task->pid = 12314
post-handler thread info: flags = 80, status = 0, cpu = 1, task->pid = 12314
pre-handler thread info: flags = 80, status = 0, cpu = 1, task->pid = 12314
post-handler thread info: flags = 80, status = 0, cpu = 1, task->pid = 12314
pre-handler thread info: flags = 80, status = 0, cpu = 0, task->pid = 7305
post-handler thread info: flags = 80, status = 0, cpu = 0, task->pid = 7305
pre-handler thread info: flags = 80, status = 0, cpu = 0, task->pid = 7305
post-handler thread info: flags = 80, status = 0, cpu = 0, task->pid = 7305
可见,在kprobe调试模块在内核停留期间,系统fork出了新的进程。
使用 cat /proc/kallsyms | grep do_fork查看系统调用所在的地址如下
c04345ea T do_fork
c06ee0a5 t do_fork_idle
c0888550 d do_fork_test
可见do_fork的地址与kprobe注册的地址一致。
阅读(3074) | 评论(1) | 转发(1) |