前言
在软件开发维护过程中,开发人员难免会遇到软件发版或者上线后出现异常,且通过日志无法分析出造成异常具体原因;也许是程序出现死锁导致运行阻塞,也许是程序出现内存出现异常导致软件崩溃,也许这个问题是偶现难以找到快速复现手段。当你无法通过Debug调试时,你需要掌握抓取程序运行过程中和程序奔溃时的程序运行快照技巧,便于开发人员定位问题。
本文主要介绍在Window和Linux平台上定位软件异常状态的技巧。
WinDbg工具下载地址:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/debugger/debugger-download-tools
Window PC软件崩溃位置定位
在程序中嵌入dump文件生成对象,用于捕捉崩溃时刻软件运行状态。
dump.h
#pragma once
#include <Windows.h>
namespace dump
{
class Dump
{
public:
Dump();
~Dump();
private:
static long __stdcall UnhandleExceptionFilter(_EXCEPTION_POINTERS* ExceptionInfo);
};
}
dump.cpp
#include <Windows.h>
#include <ratio>
#include <chrono>
#include <ctime>
#include <sstream>
#include <DbgHelp.h>
#include "dump.h"
#pragma comment(lib, "dbghelp.lib")
namespace dump
{
Dump::Dump()
{
/* 设置程序崩溃前的回调函数 */
SetUnhandledExceptionFilter(UnhandleExceptionFilter);
}
Dump::~Dump() {}
long __stdcall Dump::UnhandleExceptionFilter(_EXCEPTION_POINTERS* ExceptionInfo)
{
auto tp = std::chrono::system_clock::now();
std::time_t curTime = std::chrono::system_clock::to_time_t(tp);
std::stringstream fileName;
fileName << curTime << ".dmp"; // 崩溃生成的dump文件名为[时间戳].dmp
/* 创建dump文件 */
HANDLE hFile = CreateFile(fileName.str().c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
MINIDUMP_EXCEPTION_INFORMATION exInfo;
exInfo.ThreadId = ::GetCurrentThreadId(); // 引发异常的线程的标识符。
exInfo.ExceptionPointers = ExceptionInfo; // 指向EXCEPTION_POINTERS结构的指针,该 结构指定异常的计算机独立描述和异常时的处理器上下文。
exInfo.ClientPointers = FALSE; // 如果在调试过程中则设置为TRUE,否则是FALSE
/* 写Dump文件 */
BOOL ret = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &exInfo, NULL, NULL);
CloseHandle(hFile);
if (!ret)
{
/* 写dump文件出错处理,异常交给windows处理 */
system("pause");
return EXCEPTION_CONTINUE_SEARCH;
}
else
{ /* 表示该异常已经被处理 */
system("pause");
return EXCEPTION_EXECUTE_HANDLER;
}
}
else
{
/* 写dump文件出错处理,异常交给windows处理 */
system("pause");
return EXCEPTION_CONTINUE_SEARCH;
}
}
}
使用示例
main.cpp
#include "dump.h"
void run_test()
{
int *a = nullptr;
*a = 3; // 崩溃点
}
int main()
{
auto dmp = dump::Dump();
run_test();
return 0;
}
如果开发人员想要精准定位崩溃上下文,则可以开启以下配置:
以 VS2017为例,工程属性中【调试信息格式】选择为【程序数据库】,【生成调试信息】选择为【生成调试信息】。
程序运行后生成dmp文件
使用WinDbg工具加载dmp文件,使用【!analyze -v】命令查看崩溃信息
如下图所示在STATCK_TEXT堆栈信息上可以看到奔溃点为run_test函数
如果在工程属性上增加调试信息,如下图所示在FAULTING_SOURCE_CODE故障源码信息上可以看到具体奔溃点的上下文代码
Linux 程序崩溃位置定位
在程序中嵌入dump文件生成对象,用于捕捉崩溃时刻软件运行状态。
dump.h
#pragma once
int set_dump();
dump.cpp
#include<stdio.h>
#include <sys/time.h>
#include <sys/resource.h>
#include "dump.h"
#define CORE_SIZE (1024 * 1024 * 500)
int set_dump()
{
/* 设置进程资源限制 */
struct rlimit rlmt;
rlmt.rlim_cur = (rlim_t)CORE_SIZE;
rlmt.rlim_max = (rlim_t)CORE_SIZE;
return setrlimit(RLIMIT_CORE, &rlmt);
}
使用示例
main.cpp
#include <stdio.h>
#include "dump.h"
void run_test()
{
int *a = nullptr;
*a = 3; // 崩溃点
}
int main()
{
set_dump();
run_test();
return 0;
}
编译选项需要加-g
程序运行后生成core文件
使用gdb工具调试,输入命令【gdb test2 core】,可以查看程序堆栈信息和崩溃语句