dump_stack 实现分析【转】

转自:http://kernel.meizu.com/2017/03/18-40-19-dump_stack.html

1 简介

说起 dump_stack() ,相信从事 Linux 内核或者驱动相关开发的同行对于此函数肯定不陌生。我们经常会用到此函数来对自己的代码进行 debug,可以快速帮助开发者理清函数调用流程,或者说解决 bug…… 首先我们来看一下 dump_stack 的打印,相信很多人都遇到过 :

[    4.778339] <1>-(1)[258:charger_thread]CPU: 1 PID: 258 Comm: charger_thread Tainted: G        W       4.4.15+ #50

[    4.778342] <1>-(1)[258:charger_thread]Hardware name: MT67XX

[    4.778345] <1>-(1)[258:charger_thread]Call trace:

[    4.778347] <1>-(1)[258:charger_thread][<ffffffc00008a40c>] dump_backtrace+0x0/0x14c

[    4.778354] <1>-(1)[258:charger_thread][<ffffffc00008a56c>] show_stack+0x14/0x1c

[    4.778358] <1>-(1)[258:charger_thread][<ffffffc000368930>] dump_stack+0x8c/0xb0

[    4.778364] <1>-(1)[258:charger_thread][<ffffffc000852140>] ipanic_die+0x50/0x104

[    4.778368] <1>-(1)[258:charger_thread][<ffffffc0000bfd94>] notifier_call_chain+0x88/0x2d4

[    4.778374] <1>-(1)[258:charger_thread][<ffffffc0000c08d4>] notify_die+0x48/0x5c

[    4.778377] <1>-(1)[258:charger_thread][<ffffffc00008a60c>] die+0x98/0x1b8

[    4.778381] <1>-(1)[258:charger_thread][<ffffffc000099ab4>] __do_kernel_fault.part.6+0x74/0x90

[    4.778386] <1>-(1)[258:charger_thread][<ffffffc000097cbc>] do_page_fault+0x2e8/0x2f8

[    4.778392] <1>-(1)[258:charger_thread][<ffffffc000097d84>] do_translation_fault+0xb8/0xe0

[    4.778396] <1>-(1)[258:charger_thread][<ffffffc0000822bc>] do_mem_abort+0x40/0xa0

从打印可以清晰的了解程序到底发生了什么,这是一个很好用的函数,那么今天我们就来透过现象看本质,看看这个函数到底是何方神圣,怎么来实现这种功能的。

2 实现分析

通过 grep,发现 dump_stack 函数原型存在于 kernel/lib/dump_stack.c 文件中(注:笔者使用的是 4.4 版本的代码)。它的实现流程如下图所示:

dump_stack

可以看到关键的两个函数分别是 dump_stack_print_info 和 show_stack 。其中第一个函数是用来打印 info 信息的,而第二个函数是用来打印 Call trace 的。

Step 1: dump_stack_print_info

第一部分主要实现 print info ,函数比较简单,我们直接看代码:


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);
}

其实 print info 的关键信息就是一句代码实现的:

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);

current 指针指向的是当前进程,那么这句代码就是分别打印出了:log_level, CPU id, command, kernel taint state, kernel version, 这样就和前面例子中的打印信息对上了 ~

关于这个 print_tainted() 函数,笔者也不甚了解,通过函数的注释可以知其一二:


/**
  *  print_tainted - return a string to represent the kernel taint state.
  *
  *  'P' - Proprietary module has been loaded.
  *  'F' - Module has been forcibly loaded.
  *  'S' - SMP with CPUs not designed for SMP.
  *  'R' - User forced a module unload.
  *  'M' - System experienced a machine check exception.
  *  'B' - System has hit bad_page.
  *  'U' - Userspace-defined naughtiness.
  *  'D' - Kernel has oopsed before
  *  'A' - ACPI table overridden.
  *  'W' - Taint on warning.
  *  'C' - modules from drivers/staging are loaded.
  *  'I' - Working around severe firmware bug.
  *  'O' - Out-of-tree module has been loaded.
  *  'E' - Unsigned module has been loaded.
  *  'L' - A soft lockup has previously occurred.
  *  'K' - Kernel has been live patched.
  *
  *  The string is overwritten by the next call to print_tainted().
  */

Step 2: show_stack

第二部分的主要功能是实现 Call trace ,它的执行流程如下:

show_stack

unwind_frame 是判断是否到达栈底的函数,一个线程堆栈大小为 THREAD_SIZE,SP 寄存器存储的是栈顶,由此可以找到对应的栈底,如果没有到堆栈底部,则每次持续打印出相关的函数调用列表。

接下来就是另一个关键函数 dump_backtrace_entry ,看一下它的代码:


static void dump_backtrace_entry(unsigned long where)
{
 /*
  * Note that 'where' can have a physical address, but it's not handled.
  */
	print_ip_sym(where);
}

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 直接去用做好的轮子就行了。

3 关于堆栈

关于堆栈,首先要从 CPU 说起,以 ARM 32bit 为例来说,我们知道它有很多种 mode,usr/fiq/irq/svc/abt/und/sys。对于每一种 mode 都存在自己的堆栈,并由 SP 寄存器指定,由于进行模式切换就需要保护现场,因此不同 mode 的 SP 要设置为不同值。在内核态使用的都是 svc mode 的堆栈,那如何把不同线程的堆栈分开呢,实际上内核针对不同线程会分配不同的堆栈地址,而堆栈地址都被存在 task_struct 中,这样每次线程调度时就可以把相应的地址设置给 SP 寄存器,由此实现不同内核线程堆栈的切换。

再来说中断,不管 CPU 是在 usr mode 还是在 svc mode,只要中断到来都会使 CPU 进入到 irq mode,这是一种硬件行为,不过在这之后的处理就属于软件范畴了,kernel 中只是把 irq mode 作为一个中转状态,只存在了很短暂的时间,甚至中断处理程序都不再 irq mode 里执行。根据代码可知,经过短暂的 irq mode 中转后,CPU 会进入到 svc mode 来处理中断服务程序,此时使用的堆栈就是当前被中断进程的内核 svc 堆栈。

综上所述,dump_stack 其实就是根据当前 svc mode 的 SP 寄存器,打印出堆栈中的函数信息的。

4、总结

dump_stack 的实现流程其实都是基于如下两条关键代码:

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); 以及

printk("[<%p>] %pS\n", (void *) ip, (void *) ip);

有了这两个作为基础,其实对于我们来说,实现一个自己的 dump stack 函数又有何难呢?这个任务就交给读者练习吧,相信一定会收获更多的!

时间: 2024-10-03 18:48:50

dump_stack 实现分析【转】的相关文章

linux内核中打印栈回溯信息 - dump_stack()函数分析【转】

转自:http://blog.csdn.net/jasonchen_gbd/article/details/45585133 版权声明:本文为博主原创文章,转载请附上原博链接.   目录(?)[-] 简介 相关基本知识 关键寄存器介绍 内核中的函数栈 dump_stack函数   简介 当内核出现比较严重的错误时,例如发生Oops错误或者内核认为系统运行状态异常,内核就会打印出当前进程的栈回溯信息,其中包含当前执行代码的位置以及相邻的指令.产生错误的原因.关键寄存器的值以及函数调用关系等信息,这

输入子系统--event层分析【转】

转自:http://blog.csdn.net/beyondioi/article/details/9186723 ##################################################################################################### 早前曾研究了一下输入子系统的原理,给人的感觉是输入子系统很复杂.但其实内核开发者在这方面已经做得很完善了,输入子系统虽然错综复杂,但是只要我们领会了输入子系统的一些设计思想后,我们

dump_stack的简单使用 【转】

转自:http://blog.chinaunix.net/uid-26403844-id-3361770.html http://blog.csdn.net/ryfjx6/article/details/7064854 刚刚接触内核,在调试过程中用printk打印信息当然是直接有效的办法,但当我们不知到一个函数或者一个模块到底在哪里出了问题时我们可以利用dump_stack有效的找到问题的根源,下面只是简单的给出了使用方法.  我在自己的主机上试了一下dump_stack()  Makefile

linux中断线程化分析【转】

转自:http://blog.csdn.net/qq405180763/article/details/24120895 版权声明:本文为博主原创文章,未经博主允许不得转载. 最近在为3.8版本的Linux内核打RT_PREEMPT补丁,并且优化系统实时性,这篇文章主要对RTlinux中中断线程化部分进行分析.我们知道在RT_PREEMPT补丁中之所以要将中断线程化就是因为硬中断的实时性太高,会影响实时进程的实时性,所以需要将中断处理程序线程化并设置优先级,使中断处理线程的优先级比实时进程优先级

从图片上传的三个步骤来分析其中的交互过程

传统的图片上传交互很简单:一个文件域要求用户选择图片文件,一个提交按钮(如下图). 这种方式有很多缺点,比如选择图片后看不到预览,一次只能选择一张图,上传过程看不到进度.当然也有它自身的优点:html本身的表单控件,代码简单,上传不易出错,适合低速网络环境.现在富媒体横行的时代,用户需要长传大量图片,这种传统表单的方式上传图片显然已经跟不上时代的需求,基于 flash.html5的新型上传方式被广泛的应用. 我们从上传图片前.上传中.上传后三个步骤来分析其中的交互过程. 上传前 上传图片前一般可

python网络编程之数据传输UDP实例分析

  本文实例讲述了python网络编程之数据传输UDP实现方法.分享给大家供大家参考.具体分析如下: 一.问题: 你觉得网络上像msn,qq之类的工具在多台机器之间互相传输数据神秘吗?你也想玩一下在两台机器之间传数据吗?今天让python告诉我们基本原理吧,当然只是做简单的了解,实际情况复杂的多. 我们今天用python实现一个简单的udp程序. 二.程序实现: 1) 使用模块 (socket)套接字模块: 套接字模块是一个非常简单的基于对象的接口,它提供对低层BSD套接字样式网络的访问 .使用

python中enumerate函数用法实例分析

  本文实例讲述了python中enumerate函数用法.分享给大家供大家参考.具体分析如下: 今日发现一个新函数 enumerate .一般情况下对一个列表或数组既要遍历索引又要遍历元素时,会这样写: ? 1 2 for i in range (0,len(list)): print i ,list[i] 但是这种方法有些累赘,使用内置enumerrate函数会有更加直接,优美的做法,先看看enumerate的定义: ? 1 2 3 4 5 6 7 def enumerate(collect

python实现马耳可夫链算法实例分析

  本文实例讲述了python实现马耳可夫链算法的方法.分享给大家供大家参考.具体分析如下: 在<程序设计实践>(英文名<The Practice of Programming>)的书中,第三章分别用C语言,C++,AWK和Perl分别实现了马耳可夫链算法,来通过输入的文本,"随机"的生成一些有用的文本. 说明: 1. 程序使用了字典,字典和散列可不是一个东西,字典是键值对的集合,而散列是一种能够常数阶插入,删除,不过可以用散列来实现字典. 2. 字典的setd

php中关于抽象(abstract)类和抽象方法的问题分析

 这篇文章主要介绍了php中关于抽象(abstract)类和抽象方法的问题分析,有需要的朋友可以参考一下 在面向对象(OOP)语言中,一个类可以有一个或多个子类,而每个类都有至少一个公有方法作为外部代码访问的接口.而抽象方法就是为了方便继承而引入的,现在来看一下抽象类和抽象方法分别是如何定义以及他们的特点.   什么是抽象方法?我们在类里面定义的只有方法名没有方法体的方法就是抽象方法,所谓没有方法体就是在方法声明的时候没有大括号以及其中的内容,而是直接声明时在方法名后加上分号结束,另外在声明抽象