转自:http://blog.chinaunix.net/uid-27714502-id-3434761.html

简单实现dump_stack
0.首先确保你能写个内核模块:打印"hello kernel"
  如果熟悉dump_stack的话,完全可以绕开此文,或者自己去看dump_stack代码实现之。
 
1.dump_stack是什么
经常调试内核一定对这个函数不陌生,因为我们大多数人调试内核的时候都受这个函数的
折磨,不信,那么我们调用下这个函数看看(随意写个内核模块调用dump_stack(),插入内核),
我们来看看输出:
Pid: 9982, comm: insmod Not tainted 2.6.31.5-127.fc12.i686.PAE #1
Call Trace:
 [<f7e98008>] init+0x8/0xc [hello]
 [<c040305b>] do_one_initcall+0x51/0x13f
 [<c0462e2f>] sys_init_module+0xac/0x1bd
 [<c0408f7b>] sysenter_do_call+0x12/0x28
看到输出,大家一定很熟悉, 没见过类似输出的,一定没把kernel搞崩过.
(来我教你:*(int *)NULL = 0xdead;)
其实不见得每次内核崩溃都会调用dump_stack,但是看到dump_stack的我们不应该被吓到,
反而应该高兴:内核在临挂前还喘口气给我们提示了宝贵的调试信息
 
2.构造dump_stack第一句
有的人已经不耐烦我这唠叨,自己开始查看代码了,但是为了满足我们小小的虚荣,看懂还不行,
自己也要来写个玩玩,不能老被dump_stack欺负阿。
我们看看dump_stack里面第一句代码:
 printk("Pid: %d, comm: %.20s %s %s %.*s\n",
                 current->pid, current->comm, print_tainted(),
                 init_utsname()->release,
                 (int)strcspn(init_utsname()->version, " "),
                 init_utsname()->version);
这里就不解释printk每个参数了,代码本身就自解释了,剩下的请google,
对于不太理解print_tainted,请看下此函数实现的源码上方的注释:)
 
3.构造dump_stack的call trace
1)先来句printk("Call Trace:\n"),
 
2)接下来就神奇了,当初我就觉得能将函数执行流打印出来实在是很神奇,内核到底用了什么方法呢?
先不说,我们来看一句简单的代码:
printk("[<%p>] %pS\n", &printk, &printk);
观察输出:
[<c0776cf4>] printk+0x0/0x1c
你发现,这个输出结果和dump_stack输出的部分惊人的相似,但是我们传给printk的参数是确确实实的
地址值,原来prink自己能转换地址到相应的函数名,只要用参数"%pS"就可以了。
我相信看到这里的人,估计已经走开自己去实现dump_stack玩了。
但是输出也可能是:
[<c0776cf4>] c0776cf4S
如果你不幸看到这个,那么你还是升级下内核吧,或者仔细阅读下dump_stack代码,完全靠自己去实现
下,那么你收获一定会远超出这篇文章。
printk之所以能够识别函数地址,靠的是kallsyms子系统的帮助,
用过类似grep -w "printk" /proc/kallsyms命令的人,一定要好好谢谢这个子系统,
多亏它我们才能从内核导出symbol
深入研究kallsyms就靠大家了。
 
3)在刚刚的惊喜后,我们回到正题,怎么用printk把当前的执行流打印出来?
这里用到x86中堆栈对函数调用的帮助,详细信息请google,我们要知道一点:每次函数调用时候,
都会将函数的返回地址(调用函数指令的下一句指令的地址)压入堆栈,已备函数返回时。
我们就可以靠这个返回地址来帮助打印函数执行流。
但是这个地址并不是一个函数的准确地址呀?
%pS需要的参数不一定是准确的函数地址,在函数内部任意指令地址都可以,这就解释了输出形式是
"printk+0x0/0x1c",0x0表示参数地址相对于printk地址的偏移,0x1c表示printk函数大小。
你可以尝试下:printk("%pS\n", &printk + 1);
 
并且如果函数属于某个模块,还会在输出后面加上模块名称,类似:" [<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;
 
4)给出个较为完整的代码(在本机上写的:2.6.31.5-127.fc12.i686.PAE, 虚拟机上文档写的麻烦)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/utsname.h>
 
/* 
 * change the value to real addr of kernel_text_address :
 * grep -w kernel_text_address /proc/kallsyms
 */
static int (*kernel_text_addressp)(unsigned int) = (int (*)(unsigned int))0xc044f107;
 
void my_dump_stack(void)
{
unsigned int stack;
unsigned int bottom;
unsigned int addr;
printk("Pid: %d, comm: %.20s %s %s %.*s\n",
current->pid, current->comm, print_tainted(),
init_utsname()->release,
(int)strcspn(init_utsname()->version, " "),
init_utsname()->version); 
printk("Call Trace:\n");
 
/* get stack point */
stack = (unsigned int)&stack;
 
/* get stack bottom point */
bottom = (unsigned int)current_thread_info() + THREAD_SIZE;
 
for (; stack < bottom; stack += 4) {
addr = *(unsigned int *)stack;
if (kernel_text_addressp(addr))
printk(" [<%p>] %pS\n", (void *)addr, (void *)addr);
}
}
 
static int __init init(void)
{
/* test */
my_dump_stack();
return 0;
}
 
static void __exit exit(void)
{
/* test */
my_dump_stack();
}
 
MODULE_LICENSE("GPL");
module_init(init);
module_exit(exit);
 
3.改进
1)
如果你还没厌烦的话,这里有个改进的地方。
你会发现内核中的dump_stack会又类似如下输出:
 [<c04cf4e6>] ? path_put+0x1a/0x1d
这里有个问号:这个表示堆栈中有确有此值(某个函数内部地址),但是并不代表此函数被
执行,也许这个值是个临时变量寄存在堆栈中。
要区分这个很容易,只要比较地址值所在堆栈的位置是否紧贴当前函数栈空间底的上方,
可以利用ebp(记录当前堆栈底)指针值来作比较,代码就留给大家写了。
 
2)
x86 64 实现要比x86 32复杂(见内核注释):
/*
 * x86-64 can have up to three kernel stacks:
 * process stack
 * interrupt stack
 * severe exception (double fault, nmi, stack fault, debug, mce) hardware stack
 */
 
4.后记
你可能认为作者在忽悠你,这就整一个dump_stack注释的文章呀,贯上了写dump_stack的头衔!
我只有一句话:
Just for fun!

写个dump_stack【转】的更多相关文章

  1. 关于自己写C++的一点风格

    现在,我学了很长时间的C++,但是自己就是无法精通.许多知识是入门书上没有的.现在写C++最重要的就是风格问题. 我现在的C++风格: 把自己所有的东西都放在一个名称空间下. 没有全局的函数,有的函数 ...

  2. ASP.NET Core 折腾笔记二:自己写个完整的Cache缓存类来支持.NET Core

    背景: 1:.NET Core 已经没System.Web,也木有了HttpRuntime.Cache,因此,该空间下Cache也木有了. 2:.NET Core 有新的Memory Cache提供, ...

  3. 使用 .NET WinForm 开发所见即所得的 IDE 开发环境,实现不写代码直接生成应用程序

    直接切入正题,这是我09年到11年左右业余时间编写的项目,最初的想法很简单,做一个能拖拖拽拽就直接生成应用程序的工具,不用写代码,把能想到的业务操作全部封装起来,通过配置的方式把这些业务操作组织起来运 ...

  4. 用CIL写程序:你好,沃尔德

    前言: 项目紧赶慢赶总算在年前有了一些成绩,所以沉寂了几周之后,小匹夫也终于有时间写点东西了.以前匹夫写过一篇文章,对CIL做了一个简单地介绍,不过不知道各位看官看的是否过瘾,至少小匹夫觉得很不过瘾. ...

  5. 我为什么要写LeetCode的博客?

    # 增强学习成果 有一个研究成果,在学习中传授他人知识和讨论是最高效的做法,而看书则是最低效的做法(具体研究成果没找到地址).我写LeetCode博客主要目的是增强学习成果.当然,我也想出名,然而不知 ...

  6. sonn_game网站开发01:写在最前面

    之前做的个人博客项目,日向博客现在已经进入后期完善阶段了.是时候开始打造一个新坑了. 然而改造个什么坑呢?构思了好几天,想了好多方案,都觉得没啥动手欲望.因为,我想做的是那种,自己能用得上,而且有一定 ...

  7. 写出易调试的SQL(修订版)

    h4 { background: #698B22 !important; color: #FFFFFF; font-family: "微软雅黑", "宋体", ...

  8. .NET平台开源项目速览(16)C#写PDF文件类库PDF File Writer介绍

    1年前,我在文章:这些.NET开源项目你知道吗?.NET平台开源文档与报表处理组件集合(三)中(第9个项目),给大家推荐了一个开源免费的PDF读写组件 PDFSharp,PDFSharp我2年前就看过 ...

  9. .NET Core的日志[5]:利用TraceSource写日志

    从微软推出第一个版本的.NET Framework的时候,就在“System.Diagnostics”命名空间中提供了Debug和Trace两个类帮助我们完成针对调试和跟踪信息的日志记录.在.NET ...

随机推荐

  1. Activiti5工作流笔记三

    组任务 直接指定办理人 流程图如下: import java.util.HashMap; import java.util.List; import java.util.Map; import org ...

  2. 详解 ES6 Modules

    详解 ES6 Modules 对于新人朋友来说,想要自己去搞定一个ES6开发环境并不是一件容易的事情,因为构建工具的学习本身又是一个非常大的方向,我们需要花费不少的时间才能掌握它. 好在慢慢的开始有大 ...

  3. 【刷题】UOJ #79 一般图最大匹配

    从前一个和谐的班级,所有人都是搞OI的.有 \(n\) 个是男生,有 \(0\) 个是女生.男生编号分别为 \(1,-,n\) . 现在老师想把他们分成若干个两人小组写动态仙人掌,一个人负责搬砖另一个 ...

  4. POJ2187:Beauty Contest——题解

    http://poj.org/problem?id=2187 题目大意:给n个点,求点对最大距离的平方. ———————————————————— 很容易证明最大距离的点对在最大凸包上. 那么就是旋转 ...

  5. warning: React does not recognize the xxx prop on a DOM element

    这是React不能识别dom元素上的非标准attribute报出的警告,最终的渲染结果中React会移除这些非标准的attribute. 通常{...this.props}和cloneElement( ...

  6. MyBatis子查询

    一.父查询BaseChildResultMap: <?xml version="1.0" encoding="UTF-8" ?> <!DOCT ...

  7. PowerDesigner12.5下载汉化及破解

    一.下载: 在网上直接输入PowerDesigner12.5下载,很多地方都可以下载成功. 二.汉化: 直接将解压的汉化文件复制[覆盖]到安装目录即可. 三.破解[稍微费事一点]: 1.安装完成后,修 ...

  8. Qt -------- 容器类

    QVector(数组).QLinkedList(链表).QMap(映射表).QHash(哈希表).QQueue(队列) QHash遍历举例: 法1: QThread& ThreadHandle ...

  9. DataXceiver error processing unknown operation src: /127.0.0.1:36479 dst: /127.0.0.1:50010处理

    异常信息如下: 2015-12-09 17:39:20,310 ERROR datanode.DataNode (DataXceiver.java:run(278)) - hadoop07:50010 ...

  10. Oracle raw类型

    RAW(size):长度为size字节的原始二进制数据,size的最大值为2000字节; RAW类型好处:在网络中的计算机之间传输 RAW 数据时,或者使用 Oracle 实用程序将 RAW 数据从一 ...