0%

Linux内核崩溃信息分析

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;

Ref

  1. 内核崩溃的日志
  2. linux内核栈与用户栈及调用栈观察方法