linux下使用backtrace记录程序崩溃时的堆栈信息,并进行分析

写在前面:

linux下跟踪并打印某程序的堆栈信息指令为:

strace -tT -f [size] [appName]

如在terminal中执行:strace -tT -f -s 1024 ./app

正文:

在项目软件代码开发中,当软件代码量多了后,由于各种疏忽和编码审查不严格,导致代码中存在缺陷,程序运行后总是有各种异常出现,严重的导致程序崩溃,这个时候就期望能够在程序崩溃时,记录异常点相关的堆栈信息,用于对异常的分析,常见的方法有在系统中配置程序崩溃生成coredump文件,后续用gdb分析,这种方法需要了解gdb常用指令,并且涉及到修改了系统的一些配置,同时release版本下,有时候用gdb分析不一定有效,因此本文通过软件代码实现,简单记录程序崩溃的信息,主要使用到的是backtrace、abi::__cxa_demangle、nm、addr2line等函数和指令。

0. 程序中需要用到的头文件信息如下:

#include <iostream>
#include <fstream>
#include <string>
#include <signal.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#if defined (__linux__)
#include <map>
#include <sstream>
#include <fcntl.h>
#include <unistd.h>
#include <execinfo.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <string.h>
#include <cxxabi.h>
#include <math.h>
#endif

1. 注册异常处理函数:

// 信号处理的map集合
static const std::map<int, std::string> gsc_mp4Signals =
{
    {SIGSEGV, "SIGSEGV"},
    {SIGABRT, "SIGABRT"},
    {SIGINT, "SIGINT"},
    {SIGFPE, "SIGFPE"},
    {SIGILL, "SIGILL"},
    {SIGSYS, "SIGSYS"},
    {SIGBUS, "SIGBUS"}
    // 可以添加其他信号
};

/*************************************************************
 * 功能:注册异常处理,linux系统
 * ***********************************************************/
static void registerExceptionHandler()
{
	// 需要配合nm,addr2line等指令使用
    struct sigaction action;
    sigemptyset(&action.sa_mask);
    action.sa_sigaction = &pfnExceptionHandler_linux;
    action.sa_flags = SA_SIGINFO;

    for (const auto& signals: gsc_mp4Signals)
    {
        if (0 > sigaction(signals.first, &action, NULL))
        {
            printf("Error: sigaction failed!\n");
        }
    }
	
    return;
}

1.5. 实现异常处理函数之前,还需要实现几个辅助接口,获取进程名称,运行路径,格式化的时间,实现如下:

涉及到的宏定义如下:

#define FORMAT_TIME_MAX_LEN         64 // 格式化时间最长长度
#define APP_NAME_MAX_LEN            128 // APP名称最大长度
#define APP_MAX_LEN                 1024 // 定义APP处理最长长度
#define RATIO_1000                  1000 // 1000的进率
#define FOLDER_MAX_LEN              512 // 文件夹长度最大值
/*************************************************************
 * 功能:获取应用运行目录,不包含应用名称
 * 输入参数:
 *		pPath:存放应用运行目录的内存空间
 *		uiLen:存放应用运行目录的内存空间长度,字节
 * 输出参数:
 *		pPath:存放应用运行目录的内存空间
 * ***********************************************************/
void getAppRunPath(char* pPath, unsigned int uiLen)
{
    if (!pPath)
    {
        return;
    }

    memset(pPath, 0, uiLen);
    std::string strPath = "";
	
    char szAbsPath[APP_MAX_LEN] = { 0 };
    ssize_t iPathLen = readlink("/proc/self/exe", szAbsPath, APP_MAX_LEN); // 获取可执行程序的绝对路径
    if (0 < iPathLen && iPathLen < APP_MAX_LEN)
    {
        std::string strFullPath(szAbsPath);
        size_t iPos = strFullPath.find_last_of('/');
        if (iPos != std::string::npos)
        {
            strPath = strFullPath.substr(0, iPos); // return the directory without the file name
        }
    }

    memcpy(pPath, strPath.data(), (uiLen < strPath.length() ? uiLen : strPath.length()));
	
    return;
}
/*************************************************************
 * 功能:创建文件目录
 * 输入参数:
 *		pFolderPath:文件目录
 * 返回值:
 *		bool:创建结果,true -- 成功,false -- 失败
 * ***********************************************************/
bool createDirectory(const char* pFolderPath)
{
    if (!pFolderPath)
    {
        return false;
    }

    bool bRet = true;

    // 创建目录,函数方式
    if (0 != access(pFolderPath, 0))
    {
        // 返回0表示创建成功,-1表示失败
        if (0 > mkdir(pFolderPath, S_IRUSR | S_IWUSR | S_IXUSR | S_IRWXG | S_IRWXO))
        {
            bRet = false;
        }
    }

    return bRet;
}

或者使用:

// 创建文件夹
bool createDirectory(const char *pFolderPath)
{
    if (!pFolderPath)
    {
        return false;
    }

    bool bRet = true;
#if defined(__linux__)
    struct stat info;
    if (0 != stat(pFolderPath, &info))
    {
        // 文件/目录不存在,info.st_mode & S_IFDIR为目录
        std::string command = std::string("mkdir -p ") + std::string(pFolderPath); // -p表示可以创建多级目录
        system(command.data());
    }
#endif
    return bRet;
}

/*************************************************************
 * 功能:获取应用名称
 * 输入参数:
 *		pName:存放应用名称的内存空间
 *		uiLen:存放应用名称的内存空间长度,字节
 * 输出参数:
 *		pName:存放应用名称的内存空间
 * ***********************************************************/
void getAppName(char* pName, unsigned int uiLen)
{
    if (!pName)
    {
        return;
    }

    memset(pName, 0, uiLen);
    std::string strAppName = "unknown";

    char szAbsPath[APP_MAX_LEN] = { 0 };
    ssize_t iPathLen = readlink("/proc/self/exe", szAbsPath, APP_MAX_LEN); // 获取可执行程序的绝对路径
    if (0 < iPathLen && iPathLen < APP_MAX_LEN)
    {
        std::string strFullPath(szAbsPath);
        size_t iPos = strFullPath.find_last_of('/');
        if (iPos != std::string::npos)
        {
            strAppName = strFullPath.substr(iPos + 1);
        }
    }

    memcpy(pName, strAppName.data(), (uiLen < strAppName.length() ? uiLen : strAppName.length()));

    return;
}
/*************************************************************
 * 功能:获取格式化时间,格式为yyyyMMdd_HHmmss.zzz
 * 返回值:
 *		std::string:格式化时间
 * ***********************************************************/
static std::string getFormatTime()
{
    time_t stTimeNow;
    time(&stTimeNow);
    char szTmp[FORMAT_TIME_MAX_LEN] = { 0 };

    strftime(szTmp, sizeof(szTmp), "%Y%m%d_%H%M%S", localtime(&stTimeNow));

    int iMillsec = 0;
    struct timeval tv;
    gettimeofday(&tv, NULL);
    iMillsec = tv.tv_usec / RATIO_1000;

    char szTime[FORMAT_TIME_MAX_LEN] = { 0 };
    sprintf(szTime, "%s_%03d", szTmp, iMillsec);
    std::string strTime(szTime);
	
    return strTime;
}

 2. 实现异常处理接口:

/*************************************************************
 * 功能:注册异常处理,linux系统
 * 输入参数:
 *		signum:信号值
 *      info:信息
 *      ctx:上下文
 * ***********************************************************/
static void pfnExceptionHandler_linux(int signum, siginfo_t* info, void* ctx)
{
    signal(signum, SIG_DFL); // 还原默认的信号处理

    // 创建dump日志文件夹
    char szAppFolder[FOLDER_MAX_LEN] = { 0 };
    getAppRunPath(szAppFolder, sizeof(szAppFolder));
    std::string strFolder = std::string(szAppFolder) + "/dump/";
    if (!createDirectory(strFolder.data()))
    {
        return;
    }

    // 写入异常自定义头部信息
    std::ostringstream oss;
    oss << "Stack Trace: " << std::endl;
    oss << "Signal (" << signum << "), " << strsignal(signum) << std::endl;

    // 读取堆栈信息
    const size_t sFrameSize = 32; // 堆栈大小
    void* pStackBuffer[sFrameSize] = { 0 };
    int iSize = backtrace(pStackBuffer, sFrameSize);
    //backtrace_symbols_fd(pStackBuffer, iSize, STDOUT_FILENO);
    char** symbols = backtrace_symbols(pStackBuffer, iSize);
    if (!symbols)
    {
        return;
    }

    // 解析函数符号信息
    for (int i = 0; i < iSize; ++i)
    {
        char* mangleName = 0;
        char* offsetBegin = 0;
        char* offsetEnd = 0;

        for (char* p = symbols[i]; *p; ++p)
        {
            if ('(' == *p)
            {
                mangleName = p;
            }
            else if ('+' == *p)
            {
                offsetBegin = p;
            }
            else if (')' == *p)
            {
                offsetEnd = p;
                break;
            }
        }

        if (mangleName && offsetBegin && offsetEnd && (mangleName < offsetBegin))
        {
            *mangleName++ = '\0';
            *offsetBegin++ = '\0';
            *offsetEnd++ = '\0';

            int status = -4;
			// 获取混淆解析后可读的函数符号信息
            char* retRealName = abi::__cxa_demangle(mangleName, nullptr, nullptr, &status); // 解析符号,得到真正的函数名
            if (0 == status && retRealName)
            {
                oss << "[Bt" << i << "] status(" << status << "), " << symbols[i] << ": " << retRealName << " + ";
            }
            else
            {
                oss << "[Bt" << i << "] status(" << status << "), " << symbols[i] << ": " << mangleName << " + ";
            }

            oss << offsetBegin << offsetEnd << std::endl;

            if (retRealName)
            {
                free(retRealName);
                retRealName = nullptr;
            }
        }
        else
        {
            oss << "[Bt" << i << "] status(-4), " << symbols[i] << std::endl;
        }
    }
    free(symbols);

    oss << std::endl;

    // 异常写入文件
    char szAppName[APP_NAME_MAX_LEN] = { 0 };
    getAppName(szAppName, sizeof(szAppName));
    std::string strAppName(szAppName);
    std::string strFilePath = strFolder + "core-" + strAppName + "-" + getFormatTime();
    std::ofstream fout(strFilePath.data());
    do
    {
        if (!(fout.is_open()))
        {
            break;
        }

        fout << oss.str().data();
        fout.close();

    } while (0);

    exit(1);

    return;
}

3. 在main函数中靠前的位置注册函数即可:

int main(int argc, char** argv)
{
    registerExceptionHandler();
	
	// TODO 添加业务代码
	
}

4. 编译业务程序,注:编译过程中,编译选项需要添加:-g和-rdynamic,否则异常日志是不可识别的。

例如:直接使用g++编译:

g++ -g -rdynamic local.h local.cpp exceptionhelper.h exceptionhelper.cpp main.cpp -o App

使用cmakelists中时,需添加:

set(CMAKE_C_FLAGS "-std=gnu99 -g -rdynamic")
set(CMAKE_CXX_FLAGS "-std=c++11 -g -rdynamic")

使用qtcreator编译时,在pro中配置:

QMAKE_CFLAGS += -std=gnu++11 -g -rdynamic
QMAKE_CXXFLAGS += -std=c++11 -g -rdynamic
QMAKE_LFLAGS += -g -rdynamic

5. 结果测试,使用了异常程序和main函数同一个工程,以及使用导出动态库的方式分别进行测试,编译的应用名称为App,导出库名为libApple.so。分别设置的异常代码如下图所示:

 

生成的三个异常日志信息如下:

 

 

 日志中可以看到记录了异常的函数入口,以及偏移地址。

6.  借助nm、addr2line分析,查看函数地址的指令(在应用和库同级目录下,启动终端(Terminal))如下:

nm -C [appName] | grep [functionName]

nm -C [libName] | grep [functionName]

查看行数的指令如下:

addr2line -e [appName] [address]

addr2line -e [libName] [address]

查询到Local::send()地址为0x71a0,加上偏移地址0x159,即0x71a0 + 0x159 = 0x72f9。

 即看到和第5步中展示的程序代码行数一致的,其他两个如下:

表明了该方式的可行性。 

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值