dump_stack分析函数调用关系实例及其实现

简介: dump_stack分析函数调用关系实例及其实现

一、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函数实质上是打印当前堆栈的函数信息。


目录
打赏
0
0
0
0
4
分享
相关文章
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
|
6月前
crash —— 如何查看数据是什么类型以及函数原型
crash —— 如何查看数据是什么类型以及函数原型
Python内存管理用引用计数(对象的`ob_refcnt`)跟踪对象,但循环引用(如A-&gt;B-&gt;A)可导致内存泄漏。
【6月更文挑战第20天】Python内存管理用引用计数(对象的`ob_refcnt`)跟踪对象,但循环引用(如A-&gt;B-&gt;A)可导致内存泄漏。为解决此问题,Python使用`gc`模块检测并清理循环引用,可通过`gc.collect()`手动回收。此外,Python结合标记清除和分代回收策略,针对不同生命周期的对象优化垃圾回收效率,确保内存有效释放。
66 3
堆栈的区别是什么
堆和栈是计算机内存中两种不同的数据结构,它们用来存储程序运行时所需的数据。虽然堆和栈都是用于存储数据的,但它们在内存管理和数据访问方面有着明显的区别。下面我将详细解释堆和栈的区别。
272 0
【C++初阶:内存管理】C/C++内存分布及管理方式 | new/delete实现原理及operator new和operator delete函数 上
【C++初阶:内存管理】C/C++内存分布及管理方式 | new/delete实现原理及operator new和operator delete函数
173 0
【C++初阶:内存管理】C/C++内存分布及管理方式 | new/delete实现原理及operator new和operator delete函数 上
【C++初阶:内存管理】C/C++内存分布及管理方式 | new/delete实现原理及operator new和operator delete函数 下
【C++初阶:内存管理】C/C++内存分布及管理方式 | new/delete实现原理及operator new和operator delete函数
210 0
【C++初阶:内存管理】C/C++内存分布及管理方式 | new/delete实现原理及operator new和operator delete函数 下
stack以及使用举例--C++基础
stack以及使用举例--C++基础
173 0
stack以及使用举例--C++基础
利用Memory Graph定位内存泄露位置
有时候不清楚类中的内存泄露是在哪个方法中泄露,而通过Leaks比较难定位,可以借助Xcode的Memory Graph进行定位
761 0
分析函数调用关系图(call graph)的几种方法
绘制函数调用关系图对理解大型程序大有帮助。我想大家都有过一边读源码(并在头脑中维护一个调用栈),一边在纸上画函数调用关系,然后整理成图的经历。如果运气好一点,借助调试器的单步跟踪功能和call stack窗口,能节约一些脑力。
10852 1
OB有问必答 | 参数和变量的区别是什么?
OceanBase 的参数和变量设置方式常让初学者很迷惘,实际掌握其实并不难,下面就为大家展开介绍