Linux内核栈信息获取与理解

1 内核栈获取

C语言的函数调用,是通过栈来实现的。如下图所示:

                1. 函数调用栈

内核异常或死机时,经常在内核日志中看到打印的栈信息和寄存器值。从函数栈信息,我们可以知道函数间的调用关系,从栈和寄存器数据,还可以得到各个变量、参数的值。对Linux内核理解和故障定位非常有帮助。


若希望打印出栈信息的函数中加上dump_stack()即可,dump_stack()已导出到内核符号表中,可直接调用。

00276: / *

00277: * The architecture- independent dump_stack generator

00278: */

00279: void dump_stack(void)

00280: {

00281: unsigned long stack;

00282:

00283: show_trace(current, NULL, &stack);

00284: }

00285:

00286: EXPORT_SYMBOL(dump_stack);

00287:


若是内核代码,则需要重新编译内核。若是内核模块,编译后,重新加载该模块。注意要控制好打印栈信息的次数和频度。内核中有些代码是大量被执行的,这时要控制打印次数,否则系统长时间无法响应。

下面栈信息是在文件fs/jbd/transaction.c,函数journal_dirty_data()中加入dump_stack()函数后的获取到的栈信息。

2 内核栈信息的理解

对于Linux内核栈信息来说,也主要是函数的调用关系。


如下面栈信息:


对于语句:

[<c03743c8>] smp_scan_config+0xa9/0xcb


因为是最后一个函数,c03743c8dump_stack()函数的地址。

linux:~ # cat /proc/kallsyms |grep smp_scan_config

c037431f t smp_scan_config


通过查找内核符号表,我们看到smp_scan_config()函数在内核中的地址是c037431f


+0xa9/0xcb”0xa9含义是当前函数在上一层函数内的偏移量。对于本句来说,就是dump_stack()函数在smp_scan_config()内核的偏移量为0xa9,行首打印的地址是c037431f + a9 = c03743c8 。“0xcb”表示smp_scan_config()函数代码大小为0xcb


同理,我们知道在find_smp_config()函数中调用了smp_scan_config()函数,调用该函数的位置是代码c037441c处,偏移量为0x32find_smp_config()函数大小为0x5c


打印内核栈信息的原理是:将栈中的值都作地址对待,然后根据地址,首先查看该地址是否属于内核代码(text)段或初始代码(inittext)段中的函数。若是,则打印函数信息。

if (is_kernel_text(addr) || is_kernel_inittext(addr))

00165: const char *kallsyms_lookup(unsigned long addr,

00166: unsigned long *symbolsize,

00167: unsigned long *offset,

00168: char **modname, char *namebuf)

… …

00179: if ((all_var && is_kernel(addr)) ||

00180: (! all_var && (is_kernel_text(addr) ||

00181: is_kernel_inittext(addr)|| is_kernel_extratext(addr)))) {

… …


若不是内核代码(text)段或初始代码(inittext)段中的函数,那么进而查找是为模块(驱动)中的函数。若是,则打印函数信息。


00224: / * see if it's in a module */

00225: msym = module_address_lookup(addr, symbolsize, offset,

modname);

00226: if (msym)

00227: return strncpy(namebuf, msym, KSYM_NAME_LEN);


若既不是内核代码(text)段或初始代码(inittext)段中的函数,也不是模块(驱动)中的函数;那么什么信息都不打印,继续取栈中的值,直到栈底。