从堆栈快速定位出错的代码行

 转自:http://tinylab.org/find-out-the-code-line-of-kernel-panic-address

  • 问题描述

    内核调试中最常见的一个问题是:内核Panic后,如何快速定位到出错的代码行?

    就是这样一个常见的问题,面试过的大部分同学都未能很好地回答,这里希望能够做很彻底地解答。

  • 问题分析

    内核Panic时,一般会打印回调,并打印出当前出错的地址:

    kernel/panic.c:panic():

       
       
    1. #ifdef CONFIG_DEBUG_BUGVERBOSE
    2. /*
    3. * Avoid nested stack-dumping if a panic occurs during oops processing
    4. */
    5. if (!test_taint(TAINT_DIE) && oops_in_progress <= 1)
    6. dump_stack();
    7. #endif

    dump_stack()调用关系如下:

       
       
    1. dump_stack() --> __dump_stack() --> show_stack() --> dump_backtrace()

    dump_backtrace()会打印整个回调,例如:

       
       
    1. [<001360ac>] (unwind_backtrace+0x0/0xf8) from [<00147b7c>] (warn_slowpath_common+0x50/0x60)
    2. [<00147b7c>] (warn_slowpath_common+0x50/0x60) from [<00147c40>] (warn_slowpath_null+0x1c/0x24)
    3. [<00147c40>] (warn_slowpath_null+0x1c/0x24) from [<0014de44>] (local_bh_enable_ip+0xa0/0xac)
    4. [<0014de44>] (local_bh_enable_ip+0xa0/0xac) from [<0019594c>] (bdi_register+0xec/0x150)

    通常,上面的回调会打印出出错的地址。

  • 解决方案

    通过分析,要快速定位出错的代码行,其实就是快速查找到出错的地址对应的代码?

    • 情况一

      在代码编译连接时,每个函数都有起始地址和长度,这个地址是程序运行时的地址,而函数内部,每条指令相对于函数开始地址会有偏移。那么有了地址以后,就可以定位到该地址落在哪个函数的区间内,然后找到该函数,进而通过计算偏移,定位到代码行。

    • 情况二

      但是,如果拿到的日志文件所在的系统版本跟当前的代码版本不一致,那么编译后的地址就会有差异。那么简单地直接通过地址就可能找不到原来的位置,这个就可能需要回调里头的函数名信息。先通过函数名定位到所在函数,然后通过偏移定位到代码行。

    相应的工具有addr2line, gdb, objdump等,这几个工具在How to read a Linux kernel panic?都有介绍,我们将针对上面的实例做更具体的分析。

    需要提到的是,代码的实际运行是不需要符号的,只需要地址就行。所以如果要调试代码,必须确保调试符号已经编译到内核中,不然,回调里头打印的是一堆地址,根本看不到符号,那么对于上面提到的情况二而言,将无法准确定位问题。

    如果要获取到足够多的调试信息,请根据需要打开如下选项:

       
       
    1. CONFIG_KALLSYMS=y
    2. CONFIG_KALLSYMS_ALL=y
    3. CONFIG_DEBUG_BUGVERBOSE=y
    4. CONFIG_STACKTRACE=y

    下面分别介绍各种用法。

    • addr2line

      如果出错的内核跟当前需要调试的内核一致,而且编译器等都一致,那么可以通过addr2line直接获取到出错的代码行,假设出错地址为0019594c:

           
           
      1. $ addr2line -e vmlinux_with_debug_info 0x0019594c
      2. mm/backing-dev.c:335

      然后用vim就可以直接找到代码出错的位置:

           
           
      1. $ vim mm/backing-dev.c +335

      如果是情况二,可以先通过nm获取到当前的vmlinux中bdi_register函数的真实位置。

           
           
      1. $ nm vmlinux | grep bdi_register
      2. 0x00195860 T bdi_register

      然后,加上0xec的偏移,即可算出真实地址:

           
           
      1. $ echo "obase=16;ibase=10;$((0x00195860+0xec))" | bc -l
      2. 19594C
    • gdb

      这个也适用情况二,因为可以直接用 符号+偏移 的方式,因此,即使其他地方有改动,这个相对的位置是不变的。

           
           
      1. $ gdb vmlinux_with_debug_info
      2. $ list *(bdi_register+0xec)
      3. 0x0019594c is in bdi_register (/path/to/mm/backing-dev.c:335).
      4. 330 bdi->dev = dev;
      5. 331
      6. 332 bdi_debug_register(bdi, dev_name(dev));
      7. 333 set_bit(BDI_registered, &bdi->state);
      8. 334
      9. 335 spin_lock_bh(&bdi_lock);
      10. 336 list_add_tail_rcu(&bdi->bdi_list, &bdi_list);
      11. 337 spin_unlock_bh(&bdi_lock);
      12. 338
      13. 339 trace_writeback_bdi_register(bdi);

      如果是情况一,则可以直接用地址:list *0x0019594c

    • objdump

      如果是情况一,直接用地址dump出来。咱们回头看一下Backtrace信息:bdi_register+0xec/0x150,这里的0xec是偏移,而0x150是该函数的大小。用objdump默认可以获取整个vmlinux的代码,但是咱们其实只获取一部分,这个可以通过--start-address--stop-address来指定。另外-d可以汇编代码,-S则可以并入源代码。

           
           
      1. $ objdump -dS vmlinux_with_debug_info --start-address=0x0019594c --end-address=$((0x0019594c+0x150))

      如果是情况二,也可以跟addr2line一样先算出真实地址,然后再通过上面的方法导出。

    总地来看,gdb还是来得简单方便,无论是情况下和情况二都适用,而且很快捷地就显示出了出错的代码位置,并且能够显示代码的内容。

    对于用户态来说,分析的方式类似。如果要在应用中获取Backtrace,可以参考Generating backtraces。其例子如下:

       
       
    1. #include <execinfo.h>
    2. #define BACKTRACE_SIZ 64
    3. void show_backtrace (void)
    4. {
    5. void *array[BACKTRACE_SIZ];
    6. size_t size, i;
    7. char **strings;
    8. size = backtrace(array, BACKTRACE_SIZ);
    9. strings = backtrace_symbols(array, size);
    10. for (i = 0; i < size; i++) {
    11. printf("%p : %s\n", array[i], strings[i]);
    12. }
    13. free(strings); // malloced by backtrace_symbols
    14. }

    编译代码时需要加上:-funwind-tables-g-rdynamic

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进课程实践、课外项目或毕业设计。通过分析和运源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值