Linux下进程崩溃时定位源代码位置

在Linux系统下,进程可能由于各种原因崩溃,此时我们要找到出问题的源代码在某一个文件的具体行号,这样调试起来就会方便,高效很多。下面是解决问题的思路和步骤以及自己的一些想法  

解决该问题的大体思路是这样的:在Linux下,进程崩溃时内核(也就是我们所谓的操作系统)会向进程发送信号,比如我们程序运行崩溃时经常会看到segmentation falt这样的信息,这是进程非法操作内存,内核会向进程发送SIGENV信号,那么我们凭什么可以找到进程崩溃的原因对应的源代码的位置呢,我们知道,每个进程都有自己的堆栈,当某个进程崩溃时,堆栈里保存了一些关键信息,通过这些信息我们可以定位到出错的源代码位置,那么我们怎么来获得进程崩溃时堆栈的信息呢,请记住,Linux是当今世界上最为强大的操作系统(不管你信不信,反正我是信了,^_^),在Linux系统里有个backtrace这些个函数可以获得当前堆栈信息,好了,到这里问题已经解决了一半,backtrace信息中的有一个地址包含了出错代码在文件中的偏移量

需要注意的是编译时一定要加上 -g和-rdynamic参数

如果我们使用的是静态库或者出错的代码不在动态库中,那么我们可以直接用命令"addr2line -e 可执行文件名 偏移地址"打印出出错的代码行,下面是具体步骤
在测试程序的29行非法操作了内存

我们把addr2line命令放在程序里面做了,在代码中也可以看得到,下面这幅图片是程序的输出,可以看到
打印出源代码出错的行数为29

如果crash在一个动态库so里面,比较麻烦一点,此时addr2line不能直接给出代码行。因为我们都知道,so里面的地址在可执行文件装载的时候,是可以被 reallocate的。所以,如果只有一个so的地址,要找出对应代码行的话,送给 addr2line的参数地址就是一个偏移地址,这里的偏移地址就是backtrace中的地址减去动态库加载的时候的基地址,这个基地址我们可以通过/proc/pid/maps这个文件找到,pid是当前进程号,下面是具体步骤,跟之前的步骤类似,只不过我们为了测试,将func函数编译进了一个动态库里面
这里我们只给出测试文件,具体怎么实现动态库,这个很容易,不在此多提,下面是测试程序的一部分

我们在/proc/pid/maps文件中找出出错动态库加载的基地址,用backtrace中的地址与基地址相减得到偏移地址就可以了,下面是程序输出
可以看到,我们用hello.c做的动态库,定位到代码出错在hello.c的第五行。

#include <stdio.h> 
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "hello.h"


char* FindBaseAddress(const char* SoName)
{
 static char BaseAddr[20]; 
 int pid = getpid();
 char file_name[50];
 sprintf(file_name, "/proc/%d/maps", pid);
 char cmd[100];
 sprintf(cmd, "grep %s %s | head -1 | awk -F- \'{print $1}\'", SoName, file_name);
 FILE* file = popen(cmd, "r");
 int n = fread(BaseAddr, 1, 20,file);
 BaseAddr[n-1] = '\0';
 printf("The Base address of the %s is:%s\n", SoName, BaseAddr);
 return BaseAddr;
}


int PrintLineNumber(char* So_Name, int OffsetAddress)
{
    char cmd[128];
    sprintf(cmd, "addr2line -e %s 0x%x", So_Name, OffsetAddress);
    system(cmd);
}


char* FindSoName()
{
  static char p[50] = {'\0'};
  char*SoName = NULL;
  int fd = open(".backtrace_file", O_RDONLY); 
  if(!fd)
  {
    perror("No such file: .backtrace_file or you have not permission to access it\n");
  }
  else
  {
   char cmd_so[100] = "awk -F\"(\" '{if(NR==3) print$1}' .backtrace_file";
   FILE* file =  popen(cmd_so, "r");
   int n =    fread(p, 1, 50, file);
   p[n-1] = 0;
   SoName = p;
   printf("The .so name is:%s\n", SoName);
  }
  close(fd);
  return SoName;
}


void OutputBacktrace(int sig) 
{
    char *bt[128];
    int backtrace_file = open(".backtrace_file", O_CREAT | O_RDWR);
    int nentries = backtrace(bt, sizeof(bt)/sizeof(bt[0]));
    backtrace_symbols_fd(bt, nentries, backtrace_file);
    //backtrace_symbols_fd(bt, nentries, fileno(stdout));
    char*So_Name = FindSoName();
    char*BaseAddress = FindBaseAddress(So_Name);
    printf("The Stack Address is:%p\n", bt[2]);
    char Stack[50];
    sprintf(Stack, "%p", bt[2]);
    int OffsetAddress = strtol(Stack, NULL, 16) - strtol(BaseAddress, NULL, 16);
    printf("OffsetAddress:%d\n", OffsetAddress);
    PrintLineNumber(So_Name, OffsetAddress); 
    exit(-1);
}


int main()
{
 int pid = getpid();
 printf("The pid is:%d\n", pid);
 signal(SIGSEGV, OutputBacktrace); 
 func();
 return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值