Linux反汇编调试方法

Linux内核模块或者应用程序经常因为各种各样的原因而崩溃,一般情况下都会打印函数调用栈信息,那么,这种情况下,我们怎么去定位问题呢?本文档介绍了一种反汇编的方法辅助定位此类问题。

代码示例如下:

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

#include <execinfo.h>

#include <fcntl.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#define PRINT_DEBUG

#define MAX_BACKTRACE_LEVEL 10

#define BACKTRACE_LOG_NAME "backtrace.log"

static void show_reason(int sig, siginfo_t *info, void *secret)

{

void *array[MAX_BACKTRACE_LEVEL];

size_t size;

#ifdef PRINT_DEBUG

char **strings;

size_t i;

size = backtrace(array, MAX_BACKTRACE_LEVEL);

strings = backtrace_symbols(array, size);

printf("Obtain %zd stack frames.\n", size);

for(i = 0; i < size; i++)

printf("%s\n", strings[i]);

free(strings);

#else

int fd = open(BACKSTRACE_LOG_NAME, O_CREAT | O_WRONLY);

size = backtrace(array, MAX_BACKTRACE_LEVEL);

backtrace_symbols_fd(array, size, fd);

close(fd);

#endif

exit(0);

}

void die() {

char *str1;

char *str2;

char *str3;

char *str4 = NULL;

strcpy(str4, "ab");

}

void let_it_die() {

die();

}

int main(int argc, char **argv){

struct sigaction act;

act.sa_sigaction = show_reason;

sigemptyset(&act.sa_mask);

act.sa_flags = SA_RESTART | SA_SIGINFO;

sigaction(SIGSEGV, &act, NULL);

sigaction(SIGUSR1, &act, NULL);

sigaction(SIGFPE, &act, NULL);

sigaction(SIGILL, &act, NULL);

sigaction(SIGBUS, &act, NULL);

sigaction(SIGABRT, &act, NULL);

sigaction(SIGSYS, &act, NULL);

let_it_die();

return  0;

}

在该示例中,我们通过自定义的信号处理函数,在程序异常时通过调用backtrace()和backtrace_symbols()函数获取并打印函数调用栈信息。接下来我们编译运行该程序.

编译的时候,添加了-g和–rdynamic选项,主要是添加调试信息。可以看到,运行时出现异常,打印了函数调用栈,栈层数(stack frame)为7层。栈帧信息中,内核在获取函数调用栈信息时是逐层往上逆推的,所以函数调用的顺序是倒序的,即main->let_it_die()->die()。

函数调用栈的每一行显示的格式是:出问题的代码所在的可执行文件(符号+相对位移)[加载地址]

以“./backtrace(main+0xf7) [0x80488cd]”这行调用栈信息为例,说明当前的可执行文件是backtrace, 代码行为main符号所在地址往下偏移0xf7行,正常情况下通过可执行文件反汇编出来的汇编代码中,main符号所在地址加相对位移等于后面的加载地址。有时候,可能因为版本更新,我们重新编译代码生成可执行文件之后,再反汇编分析问题时,因为代码和出问题时相比较有更新,那么main+0xf7可能就不等于出问题时打印的调用栈的加载地址。通过计算符号加相对位移的值,然后与加载地址比较,可以确认代码是否与出问题时保持一致。

接下来我们反汇编可执行文件

justin@ubuntu:~/workspace/backtrace$ objdump -dS backtrace > backtrace.asm

接下来我们通过分析反汇编出来的汇编代码和出问题时的调用栈信息定位问题,首先从最底层调用栈开始,即./backtrace() [0x804873d],在汇编代码中搜索0x804873d地址符,如下:

可以看到对应着代码行size = backtrace(array, MAX_BACKTRACE_LEVEL); 通过查看代码可以知道,该函数是在show_reason函数中被调用的,尝试着在汇编代码中找到show_reason符号的地址:

分析代码,发现show_reason函数是异常发生时的自定义异常处理函数,尝试着找上一层函数调用栈信息打印的地址0xb7707410,发现不在汇编代码中,说明是一个外部地址,因为show_reason是程序内部异常时才会被调用的,所以导致程序异常的一定是内部的代码,所以接下来我们分析下一行调用栈信息,即./backtrace(die+0x18) [0x80487c0]。首先,在汇编代码中找到die符号,其地址为0x80487a8。

用该地址加载相对位移0x18等于0x80487c0和函数调用栈显示的加载地址一致。在汇编代码中找到该地址所在行:

可以看到,出问题的是在strcpy(str4, “ab”);这一行代码,可以明显的看到前面定义的str4是空指针,往空指针指向区域复制字符串就会导致空指针异常。

当然,更多情况下,我们会因为找不到出问题时对应的代码,或者导出来的可执行文件在编译的时候没有添加调试选项而无法反汇编出源码和汇编代码相对照的反汇编信息。对于前一种情况,加载地址就没有参考意义了,我们只能通过在反汇编信息中找到符号,通过相对位移找到出错行,并和现有代码对照分析问题。对于后者,我们只能定位到出错行的汇编代码,然后阅读汇编代码片段分析问题的原因。

通常我们使用objdump反汇编分析问题是,还会用到另外两个特别实用的命令,即nm和addr2line。

nm是用来从可执行文件中导出符号表,其作用和readelf –s或者objdump –T(t)类似

通过nm命令查找的die符号和let_it_die符号的地址和反汇编出来的地址是一致的。

addr2line命令可以通过指定地址从可以执行文件里面打印符号、可执行文件和出错代码行:

这里我尝试着找./backtrace(die+0x18) [0x80487c0]中的加载地址,发现出错时调用的die函数,出错行是第80行:

可以很清晰地看到,addr2line定位的正是die函数中调用strcpy所在的行。

Linux内核调试方法总结之反汇编的更多相关文章

  1. Linux内核调试方法总结

    Linux内核调试方法总结 一  调试前的准备 二  内核中的bug 三  内核调试配置选项 1  内核配置 2  调试原子操作 四  引发bug并打印信息 1  BUG()和BUG_ON() 2   ...

  2. Linux内核调试方法总结【转】

    转自:http://my.oschina.net/fgq611/blog/113249 内核开发比用户空间开发更难的一个因素就是内核调试艰难.内核错误往往会导致系统宕机,很难保留出错时的现场.调试内核 ...

  3. 【转】Linux内核调试方法总结

    目录[-] 一  调试前的准备 二  内核中的bug 三  内核调试配置选项 1  内核配置 2  调试原子操作 四  引发bug并打印信息 1  BUG()和BUG_ON() 2  dump_sta ...

  4. Linux内核调试方法【转】

    转自:http://www.cnblogs.com/shineshqw/articles/2359114.html kdb:只能在汇编代码级进行调试: 优点是不需要两台机器进行调试. gdb:在调试模 ...

  5. Linux内核调试方法总结之栈帧

    栈帧 栈帧和指针可以说是C语言的精髓.栈帧是一种特殊的数据结构,在C语言函数调用时,栈帧用来保存当前函数的父一级函数的栈底指针,当前函数的局部变量以及被调用函数返回后下一条汇编指令的地址.如下图所示: ...

  6. Linux内核调试方法总结之序言

    本系列主要介绍Linux内核死机.异常重启类稳定性问题的调试方法. 在Linux系统中,一切皆为文件,而系统运行的载体,是一类特殊的文件,即进程.因此,我尝试从进程的角度分析Linux内核的死机.异常 ...

  7. Linux内核调试方法总结之ddebug

    [用途] Linux内核动态调试特性,适用于驱动和内核各子系统调试.动态调试的主要功能就是允许你动态的打开或者关闭内核代码中的各种提示信息.适用于驱动和内核线程功能调试. [使用方法] 依赖于CONF ...

  8. Linux内核调试方法总结之调试宏

    本文介绍的内核调试宏属于静态调试方法,通过调试宏主动触发oops从而打印出函数调用栈信息. 1) BUG_ON 查看bug处堆栈内容,主动制造oops Linux中BUG_ON,WARN_ON用于调试 ...

  9. 转载:Linux内核调试方法

    转载文章请注明作者和二维码及全文信息. 转自:http://blog.csdn.net/swingwang/article/details/72331196 不会编程的程序员,不是好的架构师,编程和内 ...

随机推荐

  1. Scrapy 教程(四)-命令

    scrapy 没有界面,需要命令行来操作. 非常简单,总共也就十四五个命令,分为全局命令和项目命令. 全局命令 在哪都能用 常用命令 scrapy startproject name 创建项目/工程 ...

  2. 在JSP中<%= >,<%! %>,<% %>所代表的含义

    <%! %>:是jsp中的声明标签,通常声明全局变量,常量,方法等. <% %>:<% java代码 %>,其中可以包含局部变量,java语句等. <%= % ...

  3. $strobe$monitor$display

      $strobe:当该时刻的所有事件处理完后,在这个时间步的结尾打印一行格式化的文本,语法$strobe( Argument,...);$fstrobe( Mcd, Argument,...);Mc ...

  4. mobilefacenet

    insightface作者训练的mobileFaceNet:    https://github.com/deepinsight/insightface/issues/214 ncnn的转换:http ...

  5. GUI学习之十五——QAbstractSpinBox学习总结

    QAbstractSpinBox是一个抽象类,是将所有步长调节器的通用的功能抽象出了一个父类.虽然QAbstractSpinBox是一个抽象类,但是可以直接实例化使用.QAbstractSpinBox ...

  6. 【u-boot-2018.05】make配置过程分析

    https://blog.csdn.net/q_z_r_s/article/details/80718518 从u-boot-2014.10版本引入Kbuild系统之后,Makefile的管理和组织跟 ...

  7. .NET界面控件DevExpress v19.1.3重磅来袭

    DevExpress Universal Subscription(又名DevExpress宇宙版或DXperience Universal Suite)是全球使用广泛的.NET用户界面控件套包,De ...

  8. Django【第14篇】:Django之Form组件补充

    补充 一.定义的规则 class TeacherForm(Form): #必须继承Form # 创建字段,本质上是正则表达式 username = fields.CharField( required ...

  9. hdu 6206 : Apple 【计算几何 + 分数类】

    题目链接 比赛时C++上__float128都被卡精度,然后扔给队友用Java的BigDecimal过了 算法不多说,求三角形外心可以参考 维基百科 https://zh.wikipedia.org/ ...

  10. CSS3制作太极图以及用JS实现旋转太极图

     太极图可以理解为一个一半黑一半白的半圆,上面放置着两个圆形,一个黑色边框白色芯,一个白色边框黑色芯. 1.实现黑白各半的圆形. .box{ width:200px;height:200px; bor ...