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. 3.golang 的注释

    package main import ( "fmt" "math" ) func main() { fmt.Println(pi(5000)) } // pi ...

  2. Django @csrf_exempt不适用于基于通用视图的类(Django @csrf_exempt does not work on generic view based class)

    class ChromeLoginView(View): def get(self, request): return JsonResponse({'status': request.user.is_ ...

  3. shopnc如何配置微信支付和支付宝支付

    步骤一,支付宝账号申请 申请支付宝商家账号 ,填写好公司名称,资质,审核过了,然后填写下面这些参数 步骤二  微信支付申请 登陆微信公众平台-企业微信支付,得到商户号,再申请密钥 注意:支付宝加密方式 ...

  4. 制作的第一个java小游戏

    package java1; import java.awt.*; public class java1 extends Frame { //球桌和桌球图片 Image ball = Toolkit. ...

  5. neo4j 初探

    neo4j 初探 参考 转载:http://shomy.top/2018/06/08/neo4j-start/ 近期需要处理图数据,考察后打算使用neo4j, 相比其他一些图数据库,neo4j开源,跨 ...

  6. activemq热备与消息丢失

    1. 解压 tar -zxvf apache-activemq-5.12.0-bin.tar.gz2. 改名 mv apache-activemq-5.12.0 activemq3. cd activ ...

  7. MFC- 网络编程

    一.MFC网络编程 a)CAsyncSocket用于异步非阻塞类,用UDP通信: b)CAsyncSocket的子类(派生类):Csocket同步阻塞类,用于TCP通信: c)通信前,必须调用AfxS ...

  8. Hibernate:基于HQL实现数据查询

    HQL:  hibernate query language(hibernate特有的查询语言) hql是基于对象的查询语言,其语法与sql类似,但是他和sql的区别在于sql是面向表和字段的查询,而 ...

  9. 第一次整合ssm环境后,对请求流程的理解 ,以及一些配置(有错就更新)

    工程结构图: 显示层(handler/controller): request请求到springmvc的前端控制器,从处理器映射器找相应的handler(用@RequestMapping(" ...

  10. mongodb 在 linux 中的安装和简单使用

    一.环境介绍 1.mongodb版本: mongodb-linux-x86_64-rhel70-3.2.22  # 点击下载2.linux版本: Ubuntu 18.04.2 LTS 二.安装1.上传 ...