一、dump_stack实例
在正点原子阿尔法开发板中查看insmod命令使用什么方法:
#include <linux/module.h> #include <linux/init.h> static int hello_init(void){ #ifndef DEBUG printk("no def DEBUG\n"); #else printk(" def DEBUG\n"); #endif dump_stack(); return 0; } static void hello_exit(void){ printk("hello exit!!!\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_AUTHOR("Paranoid"); MODULE_VERSION("V1.0"); MODULE_LICENSE("GPL");
dump_stack();函数打印调用关系
从上往下:
sys_finit_module->load_module->do_init_module->do_one_initcall
所以可知使用的是finit_module
insmod命令流程
insmod_main->bb_init_module->finit_module
二、dump_stack实现分析
通过 grep,发现 dump_stack 函数原型存在于 kernel/lib/dump_stack.c 文件中。它的实现流程如下图所示:
static void __dump_stack(void) { dump_stack_print_info(KERN_DEFAULT); show_stack(NULL, NULL); }
可以看到关键的两个函数分别是dump_stack_print_info和show_stack。其中第一个函数是用来打印 info 信息的,而第二个函数是用来打印 Call trace的。
Step 1: dump_stack_print_info
第一部分主要实现 print info ,函数比较简单,我们直接看代码:
文件:kernel\kernel\printk\printk.c
/** * dump_stack_print_info - print generic debug info for dump_stack() * @log_lvl: log level * * Arch-specific dump_stack() implementations can use this function to * print out the same debug information as the generic dump_stack(). */ void dump_stack_print_info(const char *log_lvl) { printk("%sCPU: %d PID: %d Comm: %.20s %s %s %.*s\n", log_lvl, raw_smp_processor_id(), current->pid, current->comm, print_tainted(), init_utsname()->release, (int)strcspn(init_utsname()->version, " "), init_utsname()->version); if (dump_stack_arch_desc_str[0] != '\0') printk("%sHardware name: %s\n", log_lvl, dump_stack_arch_desc_str); print_worker_info(log_lvl, current); }
current指针指向的是当前进程,那么这句代码就是分别打印出了:log_level, CPU id, command, kernel taint state, kernel version
Step 2: show_stack
第二部分的主要功能是实现 Call trace ,它的执行流程如下:
文件:kernel\arch\metag\kernel\traps.c
void show_stack(struct task_struct *tsk, unsigned long *sp) { if (!tsk) tsk = current; if (tsk == current) sp = (unsigned long *)current_stack_pointer; else sp = (unsigned long *)tsk->thread.kernel_context->AX[0].U0; show_trace(tsk, sp, NULL); }
kstack_end 是判断是否到达栈底的函数,一个线程堆栈大小为 THREAD_SIZE,SP 寄存器存储的是栈顶,由此可以找到对应的栈底,如果没有到堆栈底部,则每次持续打印出相关的函数调用列表。
接下来就是另一个关键函数 print_ip_sym ,看一下它的代码:
static inline void print_ip_sym(unsigned long ip) { printk("[<%p>] %pS\n", (void *) ip, (void *) ip); }
可以看到真正的打印函数也就一句代码,这个是真正的精髓所在:
printk("[<%p>] %pS\n", (void *) ip, (void *) ip);
把 %pS 作为格式化参数传递给 printk,printk 将负责把对应地址的函数名打印出来。由此看来,如何从地址转换到函数名这个最复杂的工作内核已经帮你做好了,dump stack 直接去用做好的轮子就行了。
三、关于堆栈
对于ARM 32bit CPU的不同模式,每种模式都有其自己的堆栈,并由SP寄存器指定。在内核态中,通常使用svc mode的堆栈,为了实现不同线程之间的堆栈分离,内核会为每个线程分配一个独立的堆栈地址,并将其存储在task_struct结构体中。当线程调度发生时,相应的堆栈地址会被设置给SP寄存器,以完成对不同线程之间堆栈的切换。
当中断到来时,CPU会进入irq mode进行硬件处理,然后进入svc mode处理中断服务程序。此时使用的堆栈是被中断进程的svc堆栈。dump_stack函数就是通过当前svc mode的SP寄存器打印堆栈信息。因此,dump_stack函数实质上是打印当前堆栈的函数信息。