免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 10781 | 回复: 4
打印 上一主题 下一主题

写个dump_stack [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-02-27 22:34 |只看该作者 |倒序浏览
[i=s] 本帖最后由 chobit_s 于 2011-02-28 09:22 编辑 [/i]

简单实现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 "printk" /proc/kallsyms命令的人,一定要好好谢谢这个子系统,
多亏它我们才能从内核导出symbol
深入研究kallsyms就靠大家了。

3)在刚刚的惊喜后,我们回到正题,怎么用printk把当前的执行流打印出来?
这里用到x86中堆栈对函数调用的帮助,详细信息请google,我们要知道一点:每次函数调用时候,
都会将函数的返回地址(调用函数指令的下一句指令的地址)压入堆栈,已备函数返回时。

我们就可以靠这个返回地址来帮助打印函数执行流。
但是这个地址并不是一个函数的准确地址呀?
%pS需要的参数不一定是准确的函数地址,在函数内部任意指令地址都可以,这就解释了输出形式是
"printk+0x0/0x1c",0x0表示参数地址相对于printk地址的偏移,0x1c表示printk函数大小。
并且如果函数属于某个模块,还会在输出后面加上模块名称,类似:" [<f8cd40a5>] exit+0xd/0xf [hello]"

你可以尝试下:printk("%pS\n", &printk + 1);

这里知道地址在堆栈里,那么怎么取堆栈呢,其实很简单:
int stack_pointer;
我们只要取值&stack_pointer就可以了(也可以用内联汇编取esp值),然后只要循环遍历堆栈上所有值,然后判断该值是否在
内核代码段空间内,如果是那么就用%pS输出。

那么堆栈的结束地址是什么呢?
就是当前进程的内核态堆栈段,不懂的话请google,一定要搞清除这个。
这里我们记堆栈顶为
top = (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 top;
        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);

        /* get stack point */
        stack = (unsigned int)&stack;
        /* get stack top point */
        top = (unsigned int)current_thread_info() + THREAD_SIZE;

        for (; stack < top; 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.txt.tar.bz2

3.43 KB, 下载次数: 59

评分

参与人数 1可用积分 +30 收起 理由
Godbach + 30 感谢分享

查看全部评分

论坛徽章:
0
2 [报告]
发表于 2011-02-28 14:09 |只看该作者
本帖最后由 qiwei9743 于 2011-02-28 14:27 编辑

学习一下

看了一半的时候发现,这个top是怎么得到的呢?
按照我的理解,你这个top是栈的最小地址吧?也就是栈满的时候的值?
我感觉是不是应该查找 esp到current_thread_info + 2 *PAGE_SIZE的地址呢?

没看过dump_stack,我现在就去看看,嘿嘿。

论坛徽章:
0
3 [报告]
发表于 2011-02-28 15:14 |只看该作者
我晕菜了,你这个top把我误导了,你这个top是不是应该叫栈底呢,呵呵。
刚看了一下,你这个就是dump_stack的简易版本,真得很不错

应该是有2个地方不一样?
1.多个task的dump;
2.top=current_thread_info + 2 *PAGE_SIZE - sizeof(unsigned long)

第一个没啥问题,但是第二个是为什么会多一个减号呢?求解

论坛徽章:
0
4 [报告]
发表于 2011-02-28 19:01 |只看该作者
本帖最后由 chobit_s 于 2011-02-28 19:04 编辑

回复 3# qiwei9743
1.
我对栈顶栈底这个名词分不清的(用top不合适,应该是堆栈底),图示下内核栈:
+---+---------+
+---+---------+
|       |             `--- top = current_thread_info + THREAD_SIZE  (high addr)
|       `--------------current stack pointer(esp)
`-------------------current_thread_info                                      (low addr)

2.
早先2.6.11是 +THREAD_SIZE - 3意味着栈底元素是可以被dump_stack使用的
但是现在改成+THREAD_SIZE - sizeof(stack),最后元素不能使用了,
应该是栈底被用作stack_canary了(CC_STACKPROTECTOR搜下这个内核补丁)
(另外栈底本身应该不可能存放代码段地址值)

论坛徽章:
0
5 [报告]
发表于 2011-12-02 14:31 |只看该作者
这种好文不顶一把 ,可惜啊
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP