Linux内核调试⽅法总结之反汇编
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所在的⾏。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。