转载地址:https://blog.youkuaiyun.com/LinSeeker85/article/details/86597145
刚刚接触内核,在调试过程中用printk打印信息当然是直接有效的办法,但当我们不知到一个函数或者一个模块到底在哪里出了问题时我们可以利用dump_stack有效的找到问题的根源,下面只是简单的给出了使用方法。
下面是使用例子
Makefile文件
obj-m := hello.o
KERNELBUILD :=/lib/modules/$(shell uname -r)/build
default:
make -C $(KERNELBUILD) M=$(shell pwd) modules
clean:
rm -rf *.o *.ko *.mod.c *.cmd *.markers *.order *.symvers *.tmp_versions
hello.c文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kprobes.h>
#include <asm/traps.h>
MODULE_LICENSE("Dual BSD/GPL");
static int __init hello_init(void)
{
printk(KERN_ALERT "dump_stack start\n");
dump_stack();
printk(KERN_ALERT "dump_stack over\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_ALERT "test module\n");
}
module_init(hello_init);
module_exit(hello_exit);
注意:使用dump_stack()要加上这两个头文件
#include <linux/kprobes.h>
#include <asm/traps.h>
然后make得到hello.ko
在运行insmod hello.ko把模块插入内核
运行查看内核启动信息的阿命令 dmesg
[ 452.785164] dump_stack start
[ 452.785177] Pid: 1874, comm: insmod Tainted: G O 3.8.6 #2
[ 452.785179] Call Trace:
[ 452.785185] [<f80ff017>] hello_init+0x17/0x27 [hello]
[ 452.785218] [<c0101124>] do_one_initcall+0x34/0x170
[ 452.785233] [<c0195c8c>] do_init_module+0x3c/0x1a0
[ 452.785237] [<c0198571>] load_module+0x1551/0x15f0
[ 452.785243] [<c019872b>] sys_init_module+0x9b/0xb0
[ 452.785266] [<c0653dd9>] sysenter_do_call+0x12/0x28
[ 452.785267] dump_stack over
上面打出运行这个模块时调用的函数
删除模rmmod hello
但是,使用dump_stack有不足之处
dump_stack的原理是遍历堆栈,把所有可能是内核函数的内容找出来,并打印对应的函数。因为函数调用时会把下一条指令的地址放到堆栈中。所以只要找到这些return address,就可以找到这些return address所在函数,进而打印函数的调用关系。
使用dump_stack可能不准确,可能的原因有三:
1.所有这些可以找到的函数地址,存在/proc/kallsyms中。它并不包括内核中所有的函数,而只包括内核中stext~etext和sinittext~einittext范围的函数,及模块中的函数。详细可参考scripts/kallsyms.c
2.一些函数在编译时进行了优化,把call指令优化为jmp指令,这样在调用时就不会把return address放到堆栈,导致dump_stack时在堆栈中找不到对应的信息。
3.堆栈中可能有一些数值,它们不是return address,但是在内核函数地址的范围里,这些数值会被误认为return address从而打印出错误的调用关系。
对于调用关系比较复杂的函数,在目标函数里面调用dump_stack()可以查看目标函数的调用链
但是如果情况更复杂,涉及到很多函数指针的传递和钩子函数,那么dump_stack的作用也是有限