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. 使用foreach一次性添加多个单选按钮

    <?php //这个函数创建一个单选按钮 //函数接收一个论据:值 //函数也会让按钮有"黏性"-->记住你是如何填写它的. function create_radio ...

  2. mysql 大数据分页优化

    一.mysql大数据量使用limit分页,随着页码的增大,查询效率越低下. 1.   直接用limit start, count分页语句, 也是我程序中用的方法: select * from prod ...

  3. 搜索专题: HDU1258Sum It Up

    Sum It Up Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total ...

  4. 【学习总结】快速上手Linux玩转典型应用-第1章-课程介绍

    课程目录链接 快速上手Linux玩转典型应用-目录 1. Linux有什么用 2. 课程安排 3. 课程收获 基本运维能力,等等 END

  5. 锋利的jQuery ——jQuery中的事件和动画(四)

    一.jQuery中的事件 1)加载DOM $(document).ready()和window.onload的区别 1>执行时机 $(document).ready(){}  方法内注册的事件, ...

  6. Vue的渐进式理解(笔记)

    在我看来,渐进式代表的含义是:主张最少. 每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式. 比如说,Angu ...

  7. CentOS 7 环境下部署 SVN 并实现自动更新(版本库放在Tomcat下)

    1.安装 SVN 1.1先检查是否有安装 svn rpm -qa subversion #没有什么显示就说明没有安装过yum remove subversion #如果有安装就运行删除老版本yum i ...

  8. Codeforces Round #426 (Div. 2) - C

    题目链接:http://codeforces.com/contest/834/problem/C 题意:两个在玩一个游戏,对于每一轮,选定一个自然是k,初始两人的分数都为1,每一个回合赢的人在他当前分 ...

  9. 4Linux 终端命令格式

    Linux 终端命令格式 转自 目标 了解终端命令格式 知道如何查阅终端命令帮助信息 01. 终端命令格式 command [-options] [parameter] 说明: command:命令名 ...

  10. 「BZOJ1669」D 饥饿的牛 [Usaco2006 Oct] Hungry Cows 牛客假日团队赛5 (LIS,离散化树状数组)

    链接:https://ac.nowcoder.com/acm/contest/984/D 来源:牛客网 饥饿的牛 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32768K,其他语言 ...