Windows捕获Crash
打开regedit.exe
在注册表中找到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting
项,新建3个“字符串值”:配置DumpCount = 10
,DumpFolder = C:\XUranusDump
,DumpType = 2
如图所示:
其中DumpType代表的含义是:
- 0 :Create a Custom Dump
- 1 :Mini Dump
- 2 :Full Dump
这里配置将会在程序崩溃时在C:\XUranusDump
目录下生成完整的Dump信息,最多生成10个。生成的Dump信息以*.dmp
文件形式存在:
有了*.dmp
文件就可以用VS调试Dump。调试会依赖可执行程序及对应的*.pdb
符号文件。如果是本地执行产生的*.dmp
文件,VS可以直接从*.dmp
文件拿到*.pdb
的位置,如果是其他环境生成的*.dmp
文件在本地调试,需要在VS中指定符号文件和源码
的路径,将源码、*.pdb
和*.dmp
文件放在同一目录下。
*.pdb
文件和可执行程序的构建是一一对应的,即:同样的源码两次构建产生的pdb不一样。因此必须在生成每一个Release后保存好对应的*.pdb
文件以供日后调试。
在生产环境上,可以在用户程序中捕获异常并生成MiniDump文件。
Windows提供SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER* handler)
API用于注册一个异常捕获函数:// Register windows crash handler
static LONG ApplicationCrashCollector(EXCEPTION_POINTERS *pException)
{
// Handle Exception via pException ptr here ...
return EXCEPTION_EXECUTE_HANDLER;
}
通过EXCEPTION_POINTERS* pException
可以拿到异常相关信息,并生成MiniDump文件。我们来实现一个DumpCollector
来实现上述功能:
完整代码详见:MiniDump
class DumpCollector { |
在DumpCollector::Init
中注册异常处理函数ApplicationCrashCollector
,在ApplicationCrashCollector
中处理EXCEPTION_POINTERS *pException
,依次调用:
DumpCollector::CreateDumpFile()
生成MiniDump文件DumpCollector::GetWin32ExceptionInfo()
获取异常信息DumpCollector::DumpWin32StackTrace()
获取当前堆栈DumpCollector::Win32CrashHandler()
通过注册的回调函数返回MiniDump路径、异常原因、堆栈// Register windows crash handler
static LONG ApplicationCrashCollector(EXCEPTION_POINTERS *pException)
{
// set exception pointer
DumpCollector::exceptionPtr = static_cast<void*>(pException);
// prepare to collect dump info
std::string dumpFilePath = DumpCollector::CreateDumpFile();
std::string exceptionCause = DumpCollector::GetWin32ExceptionInfo();
std::vector<StackFrame> stacktrace = DumpCollector::DumpWin32StackTrace();
if (DumpCollector::Win32CrashHandler == nullptr) {
WRITE_ERROR("missing win32 crash handler");
} else {
DumpCollector::Win32CrashHandler(dumpFilePath, exceptionCause, stacktrace);
}
return EXCEPTION_EXECUTE_HANDLER;
}
void DumpCollector::Init()
{
::SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashCollector);
}用
MiniDumpWriteDump()
从EXCEPTION_POINTERS* pException
生成MiniDump文件:// Create dump file from DumpCollector::exceptionPtr
std::string DumpCollector::CreateDumpFile()
{
if (DumpCollector::exceptionPtr == nullptr) {
WRITE_ERROR("failed to create dump file, exception ptr is null");
return "";
}
std::string dumpFilePath = DumpCollector::GenerateDumpFilePath();
EXCEPTION_POINTERS *pException = static_cast<EXCEPTION_POINTERS*>(DumpCollector::exceptionPtr);
std::wstring wDumpFilePath = Utf8ToUtf16(dumpFilePath);
HANDLE hDumpFile = ::CreateFileW(
wDumpFilePath.c_str(),
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (hDumpFile == INVALID_HANDLE_VALUE) {
return ""; // failed to create file
}
// fill dump information
MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
dumpInfo.ExceptionPointers = pException;
dumpInfo.ThreadId = ::GetCurrentThreadId();
dumpInfo.ClientPointers = TRUE;
// write dump information to file
::MiniDumpWriteDump(
::GetCurrentProcess(),
::GetCurrentProcessId(),
hDumpFile,
MiniDumpNormal,
&dumpInfo,
nullptr,
nullptr);
::CloseHandle(hDumpFile);
return dumpFilePath;
}从
pException->ExceptionRecord->ExceptionCode
枚举错误码,获取异常原因:
// obtain exception reason from pException
std::string DumpCollector::GetWin32ExceptionInfo()
{
EXCEPTION_POINTERS* exceptionPtr = static_cast<EXCEPTION_POINTERS*>(DumpCollector::exceptionPtr);
if (exceptionPtr == nullptr || exceptionPtr->ExceptionRecord == nullptr) {
return "";
}
switch (exceptionPtr->ExceptionRecord->ExceptionCode) {
CASE_RETURN(EXCEPTION_ACCESS_VIOLATION)
CASE_RETURN(EXCEPTION_ARRAY_BOUNDS_EXCEEDED)
CASE_RETURN(EXCEPTION_BREAKPOINT)
CASE_RETURN(EXCEPTION_DATATYPE_MISALIGNMENT)
CASE_RETURN(EXCEPTION_FLT_DENORMAL_OPERAND)
CASE_RETURN(EXCEPTION_FLT_DIVIDE_BY_ZERO)
CASE_RETURN(EXCEPTION_FLT_INEXACT_RESULT)
CASE_RETURN(EXCEPTION_FLT_INVALID_OPERATION)
CASE_RETURN(EXCEPTION_FLT_OVERFLOW)
CASE_RETURN(EXCEPTION_FLT_STACK_CHECK)
CASE_RETURN(EXCEPTION_FLT_UNDERFLOW)
CASE_RETURN(EXCEPTION_ILLEGAL_INSTRUCTION)
CASE_RETURN(EXCEPTION_IN_PAGE_ERROR)
CASE_RETURN(EXCEPTION_INT_DIVIDE_BY_ZERO)
CASE_RETURN(EXCEPTION_INT_OVERFLOW)
CASE_RETURN(EXCEPTION_INVALID_DISPOSITION)
CASE_RETURN(EXCEPTION_NONCONTINUABLE_EXCEPTION)
CASE_RETURN(EXCEPTION_PRIV_INSTRUCTION)
CASE_RETURN(EXCEPTION_SINGLE_STEP)
CASE_RETURN(EXCEPTION_STACK_OVERFLOW)
}
return "Unknown Exception";
}从
EXCEPTION_POINTERS* pException
获取堆栈信息:struct StackFrame {
std::string file;
std::string module;
std::string function;
uint64_t address;
uint64_t line;
};
// a util function to reduce complexity of DumpWin32StackTrace
static std::vector<StackFrame> WalkStacks(DWORD machineType, HANDLE hProcess, HANDLE hThread, STACKFRAME* stackframePtr, CONTEXT* contextPtr)
{
bool first = true;
std::vector<StackFrame> stackframes;
while (::StackWalk(machineType, hProcess, hThread, stackframePtr, contextPtr, nullptr, SymFunctionTableAccess, SymGetModuleBase, nullptr)) {
StackFrame f{};
f.address = stackframePtr->AddrPC.Offset;
DWORD64 moduleBase = SymGetModuleBase(hProcess, stackframePtr->AddrPC.Offset);
DWORD moduleBase = SymGetModuleBase(hProcess, stackframePtr->AddrPC.Offset);
char moduelBuff[MAX_PATH];
if (moduleBase && GetModuleFileNameA((HINSTANCE)moduleBase, moduelBuff, MAX_PATH)) {
f.module = moduelBuff;
}
DWORD64 offset = 0;
DWORD offset = 0;
char symbolBuffer[sizeof(IMAGEHLP_SYMBOL) + 255];
PIMAGEHLP_SYMBOL symbol = (PIMAGEHLP_SYMBOL)symbolBuffer;
symbol->SizeOfStruct = (sizeof IMAGEHLP_SYMBOL) + 255;
symbol->MaxNameLength = 254;
if (::SymGetSymFromAddr(hProcess, stackframePtr->AddrPC.Offset, &offset, symbol)) {
f.function = symbol->Name;
} // Failed to resolve address frame.AddrPC.Offset otherwise, default empty
IMAGEHLP_LINE line;
line.SizeOfStruct = sizeof(IMAGEHLP_LINE);
DWORD offsetln = 0;
if (::SymGetLineFromAddr(hProcess, stackframePtr->AddrPC.Offset, &offsetln, &line)) {
f.file = line.FileName;
f.line = line.LineNumber;
} // Failed to resolve line for frame.AddrPC.Offset otherwise, default 0
if (!first) {
stackframes.push_back(f);
}
first = false;
}
return stackframes;
}
std::vector<StackFrame> DumpCollector::DumpWin32StackTrace()
{
DWORD machine = IMAGE_FILE_MACHINE_AMD64;
DWORD machine = IMAGE_FILE_MACHINE_I386;
HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
if (!::SymInitialize(process, nullptr, TRUE)) {
WRITE_ERROR("Failed to call SymInitialize");
return std::vector<StackFrame>();
}
::SymSetOptions(SYMOPT_LOAD_LINES);
CONTEXT context = {};
context.ContextFlags = CONTEXT_FULL;
::RtlCaptureContext(&context);
STACKFRAME frame {};
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Mode = AddrModeFlat;
frame.AddrPC.Offset = context.Rip;
frame.AddrFrame.Offset = context.Rbp;
frame.AddrStack.Offset = context.Rsp;
frame.AddrPC.Offset = context.Eip;
frame.AddrFrame.Offset = context.Ebp;
frame.AddrStack.Offset = context.Esp;
std::vector<StackFrame> stackframes = WalkStacks(machine, process, thread, &frame, &context);
::SymCleanup(process);
return stackframes;
}
执行DumpCollector::Init()
并手动触发一次异常,观察异常捕获结果:/* Coredump happend here */
void FuncCoredump()
{
int a = 10;
int b = 0;
int c = a/b;
std::cout << c << std::endl;
}
void Func2()
{
FuncCoredump();
}
void Func3()
{
Func2();
}
void Func4()
{
Func3();
}
void Func5()
{
Func4();
}
void TriggerCrash()
{
Func5();
}
int main(int argc, char** argv)
{
std::cout << "Init And Register Crash Handler" << std::endl;
DumpCollector::Init();
DumpCollector::SetDumpFileRoot("C:\\Test\\Coredump");
DumpCollector::Win32CrashHandler = Win32ApplicationCrashHandler;
std::cout << "Ready To Triger Crash" << std::endl;
TriggerCrash();
std::cout << "All Is Well, No Crash" << std::endl;
return 0;
}
编译执行,观察输出结果:
PS C:\Users\XUranus\source\repos\MiniDump\build64> .\Debug\dumpcollector.exe |
可以看到异常原因为EXCEPTION_INT_DIVIDE_BY_ZERO
,捕获到堆栈:C:\Users\XUranus\source\repos\MiniDump\build64\Debug\dumpcollector.exe,FuncCoredump[0x7ff6e4a7e92b]C:\Users\XUranus\source\repos\MiniDump\Demo.:11
C:\Users\XUranus\source\repos\MiniDump\build64\Debug\dumpcollector.exe,Func2[0x7ff6e4a7e96b]C:\Users\XUranus\source\repos\MiniDump\Demo.:18
C:\Users\XUranus\source\repos\MiniDump\build64\Debug\dumpcollector.exe,Func3[0x7ff6e4a7e98b]C:\Users\XUranus\source\repos\MiniDump\Demo.:23
C:\Users\XUranus\source\repos\MiniDump\build64\Debug\dumpcollector.exe,Func4[0x7ff6e4a7e9ab]C:\Users\XUranus\source\repos\MiniDump\Demo.:28
C:\Users\XUranus\source\repos\MiniDump\build64\Debug\dumpcollector.exe,Func5[0x7ff6e4a7e9cb]C:\Users\XUranus\source\repos\MiniDump\Demo.:33
C:\Users\XUranus\source\repos\MiniDump\build64\Debug\dumpcollector.exe,TriggerCrash[0x7ff6e4a7e9eb]C:\Users\XUranus\source\repos\MiniDump\Demo.:38
C:\Users\XUranus\source\repos\MiniDump\build64\Debug\dumpcollector.exe,main[0x7ff6e4a7ec77]C:\Users\XUranus\source\repos\MiniDump\Demo.:93
注意:生成堆栈必须要在编译的时候保留符号表(
*.pdb
)文件,如果缺少符号表将不能获得函数名、文件名和行号等信息。如果开启了llvm编译后端优化,编译后的堆栈和符号表中的堆栈可能不一致,会导致收集到的堆栈信息和实际堆栈不一致,要获取实际堆栈信息还得用VS处理生成的MiniDump文件。上述程序中生成MiniDump文件需要对应体系结构(x86/x64)的imagehlp.lib
库,如果system32
中不包含imagehlp.dll
则需要手动指定。
Linux捕获Crash
Linux通过信号机制抛出异常,Linux下可以通过捕获信号来捕获异常:
void DumpCollector::Init() |
从信号推测可能的异常原因:std::string DumpCollector::GetPosixExceptionInfo()
{
int sig = DumpCollector::sig;
siginfo_t *siginfo = static_cast<siginfo_t*>(DumpCollector::siginfo);
switch (sig){
case SIGSEGV: return "Caught SIGSEGV: Segmentation Fault";
case SIGINT: return "Caught SIGINT: Interactive attention signal, (usually ctrl+c)";
case SIGFPE: {
switch (siginfo->si_code) {
case FPE_INTDIV: return "Caught SIGFPE: (integer divide by zero)";
case FPE_INTOVF: return "Caught SIGFPE: (integer overflow)";
case FPE_FLTDIV: return "Caught SIGFPE: (floating-point divide by zero)";
case FPE_FLTOVF: return "Caught SIGFPE: (floating-point overflow)";
case FPE_FLTUND: return "Caught SIGFPE: (floating-point underflow)";
case FPE_FLTRES: return "Caught SIGFPE: (floating-point inexact result)";
case FPE_FLTINV: return "Caught SIGFPE: (floating-point invalid operation)";
case FPE_FLTSUB: return "Caught SIGFPE: (subscript out of range)";
default: return "Caught SIGFPE: Arithmetic Exception";
}
}
case SIGILL: {
switch (siginfo->si_code) {
case ILL_ILLOPC: return "Caught SIGILL: (illegal opcode)";
case ILL_ILLOPN: return "Caught SIGILL: (illegal operand)";
case ILL_ILLADR: return "Caught SIGILL: (illegal addressing mode)";
case ILL_ILLTRP: return "Caught SIGILL: (illegal trap)";
case ILL_PRVOPC: return "Caught SIGILL: (privileged opcode)";
case ILL_PRVREG: return "Caught SIGILL: (privileged register)";
case ILL_COPROC: return "Caught SIGILL: (coprocessor error)";
case ILL_BADSTK: return "Caught SIGILL: (internal stack error)";
default: return "Caught SIGILL: Illegal Instruction";
}
}
case SIGTERM: return "Caught SIGTERM: a termination request was sent to the program";
case SIGABRT: return "Caught SIGABRT: usually caused by an abort() or assert()";
default: return "Unknown";
}
return "Unknown";
}
获取堆栈信息:std::vector<StackFrame> DumpCollector::DumpPosixStackTrace()
{
std::vector<StackFrame> stacktrace;
static void* stacktraces[POSIX_MAX_STACK_FRAMES];
int traceSize = ::backtrace(stacktraces, POSIX_MAX_STACK_FRAMES);
char** messages = ::backtrace_symbols(stacktraces, traceSize);
for (int i = 0; i < traceSize; ++i) {
//if (::addr2line(icky_global_program_name, stacktraces[i]) != 0) {
// ::printf(" error determining line # for: %s\n", messages[i]);
//}
printf("[%d]%s\n", i, messages[i]);
}
if (messages) {
::free(messages);
}
return stacktrace;
}