zoukankan      html  css  js  c++  java
  • 写个dump_stack【转】

    转自: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 ",
                     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: "),
     
    2)接下来就神奇了,当初我就觉得能将函数执行流打印出来实在是很神奇,内核到底用了什么方法呢?
    先不说,我们来看一句简单的代码:
    printk("[<%p>] %pS ", &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 ", &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 ",
    current->pid, current->comm, print_tainted(),
    init_utsname()->release,
    (int)strcspn(init_utsname()->version, " "),
    init_utsname()->version); 
    printk("Call Trace: ");
     
    /* 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 ", (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!
  • 相关阅读:
    C#编程(七十九)---------- 反射
    C#编程(七十一)---------- 自定义特性
    C#编程(七十六)----------使用指针实现基于栈的高性能数组
    C#编程(七十五)----------C#使用指针
    微信开发之移动手机WEB页面(HTML5)Javascript实现一键拨号及短信发送功能
    [asp.net]c# winform打印类
    Exception in thread "main" brut.androlib.AndrolibException: Could not decode arsc file
    ValueError: invalid literal for int() with base 10: 'abc'
    检查网址是否正常访问
    Python测试网络连通性示例【基于ping】
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/7651660.html
Copyright © 2011-2022 走看看