如何快速定位 Linux Panic 出错的代码行

问题描述

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

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

问题分析

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

kernel/panic.c:panic():

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

dump_stack()调用关系如下:

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

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

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

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

解决方案

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

  • 情况一

    在代码编译连接时,每个函数都有起始地址和长度,这个地址是程序运行时的地址,而函数内部,每条指令相对于函数开始地址会有偏移。那么有了地址以后,就可以定位到该地址落在哪个函数的区间内,然后找到该函数,进而通过计算偏移,定位到代码行。
    
  • 情况二
    但是,如果拿到的日志文件所在的系统版本跟当前的代码版本不一致,那么编译后的地址就会有差异。那么简单地直接通过地址就可能找不到原来的位置,这个就可能需要回调里头的函数名信息。先通过函数名定位到所在函数,然后通过偏移定位到代码行。
    

    相应的工具有addr2line, gdb, objdump等,这几个工具在[How to read a Linux kernel panic?](http://stackoverflow.com/questions/13468286/how-to-read-a-linux-kernel-panic)都有介绍,我们将针对上面的实例做更具体的分析。

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

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

    CONFIG_KALLSYMS=y
    CONFIG_KALLSYMS_ALL=y
    CONFIG_DEBUG_BUGVERBOSE=y
    CONFIG_STACKTRACE=y
    

    下面分别介绍各种用法。

    • addr2line

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

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

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

      $ vim mm/backing-dev.c +335
      

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

      $ nm vmlinux | grep bdi_register
      0x00195860 T bdi_register
      

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

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

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

      $ gdb vmlinux_with_debug_info
      $ list *(bdi_register+0xec)
      0x0019594c is in bdi_register (/path/to/mm/backing-dev.c:335).
      330     bdi->dev = dev;
      331
      332     bdi_debug_register(bdi, dev_name(dev));
      333     set_bit(BDI_registered, &bdi->state);
      334
      335     spin_lock_bh(&bdi_lock);
      336     list_add_tail_rcu(&bdi->bdi_list, &bdi_list);
      337     spin_unlock_bh(&bdi_lock);
      338
      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则可以并入源代码。

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

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

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

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

    
    #include <execinfo.h>
    
    #define BACKTRACE_SIZ 64
    
    void show_backtrace (void)
    {
            void    *array[BACKTRACE_SIZ];
            size_t   size, i;
            char   **strings;
    
            size = backtrace(array, BACKTRACE_SIZ);
            strings = backtrace_symbols(array, size);
    
            for (i = 0; i < size; i++) {
                printf("%p : %s\n", array[i], strings[i]);
            }
    
            free(strings);  // malloced by backtrace_symbols
    }

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

时间: 2024-10-13 17:40:44

如何快速定位 Linux Panic 出错的代码行的相关文章

【iOS开发】如何在程序出错崩溃时快速定位到具体出错代码行

[写在前面]最近在做iOS的开发.之前开发Android应用程序的时候程序出错时很容易根据logcat信息获取错误类型并且定位到具体出错的代码行,但是最近在做iOS的时候发现Xcode无法定位到具体错误行.搜索了一下,找到了解决办法,还挺简单. 步骤1 在xcode添加一个通用断点就行了.方法如下: 点击项目导航断点那,如图 步骤2 点击+号: 一步步按上面图完成操作. 再运行程序 自动就断点到这里来了, log信息是: 1 2013-05-20 11:14:19.635 GestureReco

【未解决】对于使用Windows的IDEA进行编译的文件,但无法在Linux系统中统计代码行数的疑问

在我学习使用Windows的IDEA的过程中,将代码文件转移到Linux虚拟机当中,但无法在Linux系统中统计代码行数. 注意:拷贝进虚拟机的文件均全能编译运行. 具体过程如下: root@yogile-VirtualBox:/alive/string# ls bin/ docs/ statistics.sh string/ work/ ##/shared/为虚拟机与宿主机的共享文件夹 root@yogile-VirtualBox:/alive/string# cp -r /shared/id

linux shell 递归统计代码行数

一句话: find /path -name '*.cpp' |xargs wc -l

linux设备驱动第四篇:从如何定位oops的代码行谈驱动调试方法

上一篇我们大概聊了如何写一个简单的字符设备驱动,我们不是神,写代码肯定会出现问题,我们需要在编写代码的过程中不断调试.在普通的c应用程序中,我们经常使用printf来输出信息,或者使用gdb来调试程序,那么驱动程序如何调试呢?我们知道在调试程序时经常遇到的问题就是野指针或者数组越界带来的问题,在应用程序中运行这种程序就会报segmentation fault的错误,而由于驱动程序的特殊性,出现此类情况后往往会直接造成系统宕机,并会抛出oops信息.那么我们如何来分析oops信息呢,甚至根据oop

Linux 使用core file文件快速定位程序崩溃代码行

问题描述 如果在 Linux下编写程序,有时运行程序的时候程序崩溃,比如说只有"Segmentation fault (core dumped) ",程序比较小的话,还可以一行一行查看,但是如果程序很庞大,一行行查询,效率非常低下.Linux下可以程序可以生成core file文件,借助gdb很快能定位到崩溃的代码行. 解决方案 测试程序,除零操作,程序会崩溃 /* test.c */ #include <stdio.h> #include <stdlib.h>

linux下编译make文件报错“/bin/bash^M: 坏的解释器,使用grep快速定位代码位置

一.linux下编译make文件报错"/bin/bash^M: 坏的解释器 参考文章:http://blog.csdn.net/liuqiyao_01/article/details/41542101#comments 自己测试的结果: [1]使用windows 下的编辑工具 新建文件doc2unix.sh #!/usr/bin/env bash # test PID=$(ps -aef | grep nginx | grep -v grep | grep master |awk '{print

使用grep命令快速定位代码位置

有时候临时修改代码时,我们不需要或者没有条件使用复杂的代码编辑器.此时使用普通的文本编辑器如nano或vim,加上grep命令即可快速定位并修改代码. 例如,在kernel目录下搜索 CONFIG_BOARD_S3C4410_XXX 内容: grep -nrE 'CONFIG_BOARD_S3C4410_XXX' kernel 参数说明: n - 输出行号 r - 遍历 E - 使用正则表达式 如此就会将你要找的内容遍历出来,并列出文件名和行号. 配合vim命令: vim xxx +n 其中:

android 代码混淆导致问题,快速定位

代码混淆导致问题,快速定位 在代码混淆打包时,屏蔽了用到的第三方库,以及常规的android混淆屏蔽,但生成的apk,运行还是会崩溃.事出必有因,后来分析找到原因是使用greendao自动生成的java-gen下package中的内容没有屏蔽代码混淆,导致存储数据库时,报*a(SourceFile:) NullPointerException * 混淆打包apk,运行崩溃 总结如下: 我们在打包时,debug版本没问题,但混淆后release版本有时会出现异常崩溃, 比如:a(SourceFil

使用grep快速定位代码位置

有时候临时修改代码时,我们不需要或者没有条件使用复杂的代码编辑器. 此时使用普通的文本编辑器如nano或vim,加上grep命令即可快速定位并修改代码. 例如,在kernel目录下搜索 CONFIG_BOARD_S3C4410_XXX 内容: grep -nrE 'CONFIG_BOARD_S3C4410_XXX' kernel 参数说明: n - 输出行号 r - 遍历 E - 使用正则表达式 如此就会将你要找的内容遍历出来,并列出文件名和行号. 配合vim命令: vim xxx +n 其中: