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. oracle跟SQL Server 2005 的区别

    Oracle与Sql server的区别   一直搞不明白Oracle数据库和sql server的区别,今天我特意查资料把他们的区别整理出来 Oracle数据库:Oracle Database,又名 ...

  2. Simpsons’ Hidden Talents

    Homer: Marge, I just figured out a way to discover some of the talents we weren’t aware we had. Marg ...

  3. 发布一本用 GitBook 编辑的书

    在上一篇的文章里,我们已经写好了一本名叫 erdong-first-book 的书,但是在本地浏览很不方便,我们希望放到网络上,可以随时.方便的访问这个书籍.这个需求可以使用多种方式来实现,比如第一种 ...

  4. 奇葩的狐火浏览器border属性

    今天接到一个bug任务,客户反映火狐浏览器访问时某个商品楼层不显示商品.我立即打开我的火狐浏览器发现没有复现这个bug,后来经过一番折腾,才发现火狐浏览器缩放到90%时,商品楼层果然就消失了,而且每台 ...

  5. linux下解压缩.tar.bz2到某目录下

    1\可先进入要解压的目的目录,然后使用tar命令 比如我要将桌面的a.tar.bz2解压到/var/b cd   /var/b tar   -xjf   /root/Desktop/a.tar.bz2 ...

  6. Mysql 服务器管理程序 mysqladmin

    mysqladmin [oprions] command 选项                                      说明 create db_name               ...

  7. 认识一下Qt用到的开发工具

    http://c.biancheng.net/view/3868.html Qt 不是凭空产生的,它是基于现有工具链打造而成的,它所使用的编译器.链接器.调试器等都不是自己的,Qt 官方只是开发了上层 ...

  8. Mongo--03 mongo副本集、备份与恢复

    目录 一.mongo副本集配置 二.查看副本集状态 三.副本集权重调整 四.创建节点 五.仲裁节点 六.mongo备份与恢复 七.准备测试数据 一.mongo副本集配置 1.创建节点目录和数据目录 # ...

  9. 牛客练习赛14 E - 无向图中的最短距离 (bfs+bitset)

    一个链接:https://ac.nowcoder.com/acm/contest/82/E来源:牛客网 无向图中的最短距离 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 262144 ...

  10. font-awesome样式只显示方框

    这是一个踩过的坑:使用font-awesome中的css样式库时,比如fa-user-circle-o,显示的不是一个用户图标,而是一个方框. 怎么回事呢? 进入css文件,发现: 咦,这些文件呢?我 ...