kernel panic
之后输出 backtrace
信息有助于调试问题,本文分析 backtrace
内核版本 v2.6,需要支持backtrace
,并且需要打开配置kallsyms
使用的内核为Linux2.6
,对当前的arch
不支持backtrace
,当内核崩溃时输出信息为:
Backtrace not supported!
arch/xxx/kernel/traps.c
函数如下
void show_trace(void)
{
printk("Backtrace not supported!\n");
}
从相应的Linux3.0.8
中移植函数show_trace
void show_trace(unsigned long *stack)
{
unsigned long *endstack;
unsigned long addr;
int i;
printk("Call Trace:");
addr = (unsigned long)stack + THREAD_SIZE - 1;
endstack = (unsigned long *)(addr & -THREAD_SIZE);
i = 0;
while (stack + 1 <= endstack) {
addr = *stack++;
/*
* If the address is either in the text segment of the
* kernel, or in the region which contains vmalloc'ed
* memory, it *may* be the address of a calling
* routine; if so, print it so that someone tracing
* down the cause of the crash will be able to figure
* out the call path that was taken.
*/
if (__kernel_text_address(addr)) {
#ifndef CONFIG_KALLSYMS
if (i % 5 == 0)
printk("\n ");
#endif
printk(" [<%08lx>] %pS\n", addr, (void *)addr);
i++;
}
}
printk("\n");
}
kernel panic之后输出
具体输出信息定义在traps.c
中,相关函数
void dump_stack(void)
{
unsigned long stack;
_show_trace(&stack);
}
EXPORT_SYMBOL(dump_stack);
用于输出 backtrace
/*
* Generic dumping code. Used for panic and debug.
*/
void show_registers(struct pt_regs *fp)
分析函数show_trace
/data/OpenSourceCode/smallprj/C/backtrace/show_pid_backtrace.c
用法
#echo pid > /proc/show_stack
#cat /proc/show_stack
printk("[<%p>] %pS/n", &printk, &printk)
%pS
参数将地址转换为函数名,需要kallsyms
支持
每次函数调用时候,都会将函数的返回地址(调用函数指令的下一句指令的地址)压入堆栈,已备函数返回时。
我们就可以靠这个返回地址来帮助打印函数执行流。
但是这个地址并不是一个函数的准确地址呀?%pS
需要的参数不一定是准确的函数地址,在函数内部任意指令地址都可以,这就解释了输出形式是 "printk+0x0/0x1c"
,0x0
表示参数地址相对于printk地址的偏移,0x1c
表示printk函数大小。
并且如果函数属于某个模块,还会在输出后面加上模块名称,类似:[<f8cd40a5>] exit+0xd/0xf [hello]
这里知道地址在堆栈里,那么怎么取堆栈呢?
其实很简单:
int stack_pointer;
我们只要取临时变量地址值 &stack_pointer 就可以了(也可以用内联汇编取esp值),然后只要循环遍历堆栈上所有值,然后判断该值是否在
内核代码段空间内,如果是那么就用%pS输出。
那么堆栈的结束地址是什么呢?
就是当前进程的内核态堆栈段,不懂的话请google,一定要搞清除这个。
这里我们记堆栈底为:
bottom = (unsigned int)current_thread_info() + THREAD_SIZE;
但是怎么判断地址值是否在内核代码段呢?
我们可以用kernel_text_address这个函数就可以了,但是很不幸的是此函数内核没有导出,我们不能使用,
那么我们就自己实现个kernel_text_address吧,但是更不幸的是此函数内部实现所依赖的变量_etext等也没有
被内核导出,其实我也没想到很好的方法,索性就用个笨办法:
手动找出此函数内核中的地址,
grep kernel_text_address /proc/kallsyms
c044f107 T kernel_text_address
在代码中通过地址值调用kernel_text_address
int (kernel_text_addressp)(unsigned int) = (int ()(unsigned int))0xc044f107;