前言
提示:本文为描述windows平台下的dump文件生成:
windows程序当遇到异常,没有try-catch或者try-catch也无法捕获到的异常时,程序就会自动退出。
windows系统默认是不产生程序dmp文件的。dump文件是C++程序发生异常时,保存当时程序运行状态的文件。 是调试异常程序重要的方法。
一、什么是dump文件?
概述
简单来说
就是程序崩溃时,产生的文件,它记录着程序崩溃时侯的一些信息、片段。
复杂来说
Dump文件是内存转储文件,也称为内存快照文件(内存镜像)。它是一个进程或系统在某一给定的时间的快照。比如在进程崩溃时或则进程有其他问题时(比如蓝屏),甚至是任何时候,我们都可以通过工具将系统或某进程的内存备份出来供调试分析用。dump文件中包含了程序运行的模块信息、线程信息、堆栈调用信息、异常信息等数据,方便系统技术人员进行错误排查。
dump分类
分类:
Windows下Dump文件分为两大类:内核模式Dump和用户模式Dump。
内核模式Dump
是操作系统创建的崩溃转储,最经典的就是系统蓝屏,这时候会自动创建内核模式的Dump。
如果你抓取整个系统的内存dump文件, 那么你抓取的是内核态的dump文件。
用户模式Dump
如果你抓一个进程的dump文件, 那么你抓取的是用户态的dump文件。
进一步可以分为完整Dump(Full Dump)和迷你Dump(Minidump)。
Full Dump:包含了某个进程完整的地址空间数据,以及许多用于调试的信息
Minidump:随着Windows XP,微软发布了一组新的被称为“minidump”的崩溃转存技术。Minidump很容易定制。按照最常用的配置,一个minidump只包括了最必要的信息,用于恢复故障进程的所有线程的调用堆栈,以及查看故障时刻局部变量的值。这样的dump文件(.dmp)通常很小(只有几KB)。所以,很容易通过电子方式发送给软件开发人员。一旦需要,minidump甚至可以包含比原来的crash dump更多的信息。minidump可以定制,给我们带来了一个问题,保存多少应用程序状态信息才能既保证调试有效,又能够尽量保证minidump文件尽可能小?尽管调试简单的异常访问只需要调用堆栈和局部变量的信息,但是解决更复杂的问题需要更多的信息。例如,我们可能需要查看全局变量的值、检查堆的完整性和分析进程虚拟内存的布局。同时,可执行程序的代码段往往是多余的,开发用的机器上可以很容易找到这些执行程序。
二、dump生成方式
实现方法
方法一:修改注册表
使用管理员权限,执行一下脚本内容,运行后: 任何程序崩溃都会在C:\xxx 产生dmp文件(full dmp)。
[bat脚本示例]:
@echo 启用Dump
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps"
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpFolder /t REG_EXPAND_SZ /d "C:\xxx" /f
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpType /t REG_DWORD /d 2 /f
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpCount /t REG_DWORD /d 10 /f
@echo Dump已经启用
参数说明
DumpFolder
dump文件生成路径 eg:C:\xxx
DumpType
0 = Create a custom dump //自定义dump
1 = Mini dump //mini核心dump文件
2 = Full dum //所有dump文件,产生的文件较大
DumpCount
默认:10
方法二:生动创建转储文件
当windows应用程序出现无响应时,可以打开任务管理器,找到无响应的进程,右键选择创建转储文件即可,即生成崩溃对应的dmp文件,该文件一般位于 C:\Users\happy\AppData\Local\Temp目录(显然这需要用户自己动手,不合适)
方法三:通过代码设置异常回调函数
使用windows系统api,程序中加入存储Dump的代码。通过SetUnhandledExceptionFilter
设置捕获dump的入口,然后通过MiniDumpWriteDump生成dump文件
设计一个记录dump功能的类
代码如下(示例):
dumpFileManager.h
#ifndef __DUMPFILEMANAGER_H__
#define __DUMPFILEMANAGER_H__
#include <string>
//#include "safeQuit.h" //自定义的事件不需要可以不加
#include <Windows.h>
namespace koal {
namespace ztaClient {
namespace dump {
class DumpFileManager {
public:
static DumpFileManager* Instance() {
static DumpFileManager ins;
return &ins;
}
public:
DumpFileManager();
~DumpFileManager();
public:
// 设置dump文件路径名
void* setDumpFilePath(const char* rootPath, const char* pAppName);
// 注册异常处理回调函数
void installDumpCollect(void* pInstallEvent = NULL);
// 注册程序安全退出回调函数
void installProcessQuit();
public:
// 判断是否为需要的数据区域
BOOL isDataSectionNeeded(const WCHAR* pModuleName);
// 创建小转储文件
BOOL createMiniDump(PEXCEPTION_POINTERS pep, LPCTSTR strFileName);
// 回调函数
static LONG CALLBACK unhandledExceptionFilterEx(PEXCEPTION_POINTERS pException);
private:
// 要生成的小转储文件文件名称
std::string exeDumpFilePath;
std::string dumpRootPath;
// 事件指针
void* pEvent;
};
} // namespace dump
} // namespace ztaClient
} // namespace koal
#endif
dumpFileManager.cpp
#include "dumpFileManager.h"
#include "io.h"
#include <DbgHelp.h>
#include <time.h>
#pragma comment(lib, "dbghelp.lib")
namespace koal {
namespace ztaClient {
namespace dump {
BOOL CALLBACK miniDumpCallback(PVOID pParam, const PMINIDUMP_CALLBACK_INPUT pInput, PMINIDUMP_CALLBACK_OUTPUT pOutput) {
if (pInput == 0 || pOutput == 0) {
return FALSE;
}
switch (pInput->CallbackType) {
case ModuleCallback:
if (pOutput->ModuleWriteFlags & ModuleWriteDataSeg) {
if (!(DumpFileManager::Instance()->isDataSectionNeeded(pInput->Module.FullPath))) {
pOutput->ModuleWriteFlags &= (~ModuleWriteDataSeg);
}
}
case IncludeModuleCallback:
case IncludeThreadCallback:
case ThreadCallback:
case ThreadExCallback:
return TRUE;
default:
break;
}
return FALSE;
}
LONG CALLBACK DumpFileManager::unhandledExceptionFilterEx(PEXCEPTION_POINTERS pException) {
if (NULL == pException) {
return EXCEPTION_CONTINUE_SEARCH;
}
int ret = access(Instance()->dumpRootPath.c_str(), 0);
if (-1 == ret) {
CreateDirectory(LPCSTR(Instance()->dumpRootPath.c_str()), NULL);
}
if (Instance()->createMiniDump(pException, Instance()->exeDumpFilePath.c_str())) {
MessageBox(NULL, LPCSTR("程序出错,创建小转储文件成功"), LPCSTR("致命错误"), MB_OK | MB_ICONINFORMATION);
} else {
MessageBox(NULL, LPCSTR("程序出错,创建小转储文件成功"), LPCSTR("致命错误"), MB_OK | MB_ICONINFORMATION);
}
if (Instance()->pEvent) {
Instance()->installProcessQuit();
}
return EXCEPTION_CONTINUE_SEARCH;
}
DumpFileManager::DumpFileManager() { pEvent = NULL; }
DumpFileManager::~DumpFileManager() {}
BOOL DumpFileManager::isDataSectionNeeded(const WCHAR* pModuleName) {
WCHAR szFileName[_MAX_FNAME] = L"";
if (NULL == pModuleName) {
return FALSE;
}
_wsplitpath(pModuleName, NULL, NULL, szFileName, NULL);
if (_wcsicmp(szFileName, L"ntdll") == 0) {
return TRUE;
}
return FALSE;
}
BOOL DumpFileManager::createMiniDump(PEXCEPTION_POINTERS pep, LPCTSTR strFileName) {
MINIDUMP_EXCEPTION_INFORMATION mdei;
MINIDUMP_CALLBACK_INFORMATION mci;
HANDLE hFile = NULL;
if (NULL == pep || NULL == strFileName) {
return FALSE;
}
hFile = CreateFile(strFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if ((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) {
mdei.ThreadId = GetCurrentThreadId();
mdei.ExceptionPointers = pep;
mdei.ClientPointers = FALSE;
mci.CallbackRoutine = (MINIDUMP_CALLBACK_ROUTINE)miniDumpCallback;
mci.CallbackParam = NULL;
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &mdei, NULL, &mci);
CloseHandle(hFile);
return TRUE;
}
return FALSE;
}
void DumpFileManager::installDumpCollect(void* pInstallEvent) {
if (pInstallEvent) { // 注册了事件
pEvent = pInstallEvent;
}
SetUnhandledExceptionFilter(unhandledExceptionFilterEx);
}
void* DumpFileManager::setDumpFilePath(const char* rootPath, const char* pAppName) {
if (NULL == rootPath || NULL == pAppName) {
return FALSE;
}
char TIME[64] = {0};
char DAY[64] = {0};
SYSTEMTIME sys;
GetLocalTime(&sys);
sprintf(TIME, "%02d_%02d_%02d", sys.wHour, sys.wMinute, sys.wSecond);
time_t curTm;
time(&curTm);
struct tm pTm = *localtime(&curTm);
sprintf(DAY, "%04d%02d%02d", pTm.tm_year + 1900, pTm.tm_mon + 1, pTm.tm_mday);
char exeName[256] = {0};
sprintf(exeName, "%s.mini_%s_%s.dmp", pAppName, DAY, TIME);
std::string dumpFilePath = rootPath;
std::string fileName = exeName;
dumpRootPath = dumpFilePath;
exeDumpFilePath = dumpFilePath + "\\" + fileName;
return (void*)this;
}
//程序崩溃后需要做的事情,自定义,不需要可以不加
void DumpFileManager::installProcessQuit() {
//koal::ztaClient::safeQuit* reg = (koal::ztaClient::safeQuit*)pEvent;
//reg->sendMsg();
}
} // namespace dump
} // namespace ztaClient
} // namespace koal
SetUnhandledExceptionFilter函数说明
简单使用SetUnhandledExceptionFilter()函数让程序优雅崩溃
最后网上查了一番,发现SetUnhandledExceptionFilter这个函数解决了一切。
设置异常捕获函数:
当异常没有处理的时候,系统就会调用SetUnhandledExceptionFilter所设置异常处理函数。例如一些程序
在出错的时候,会向用户报告说程序那出错就是利用这个.例如QQ..异常处理中的一部分
当发生异常时,比如内存访问违例时,CPU硬件会发现此问题,并产生一个异常(你可以把它理解为中断)
然后CPU会把代码流程切换到异常处理服务例程。操作系统异常处理服务例程会查看当前进程是否处于调试状态
如果是,则通知调试器发生了异常,如果不是则操作系统会查看当前线程是否安装了的异常帧链(FS[0])
如果安装了SEH(try.... catch....),则调用SEH,并根据返回结果决定是否全局展开或局部展开。
如果异常链中所有的SEH都没有处理此异常,而且此进程还处于调试状态,则操作系统会再次通知调试器发生异常
(二次异常)。如果还没人处理,则调用操作系统的默认异常处理代码UnhandledExceptionHandler
不过操作系统允许你Hook这个函数,就是通过SetUnhandledExceptionFilter函数来设置。
大部分异常通过此种方法都能捕获,不过栈溢出、覆盖的有可能捕获不到。
总结了下搜到的资料,这个函数的返回值有三种情况:
EXCEPTION_EXECUTE_HANDLER equ 1 表示我已经处理了异常,可以优雅地结束了
EXCEPTION_CONTINUE_SEARCH equ 0 表示我不处理,其他人来吧,于是windows调用默认的处理程序显示一个错误框,并结束
EXCEPTION_CONTINUE_EXECUTION equ -1 表示错误已经被修复,请从异常发生处继续执行
方法使用
在需要需用的cpp里,直接调用就行
代码如下(示例):
main.cpp
#include "dumpFileManager.h"
#define KOAL_ZTA_DUMP koal::ztaClient::dump::DumpFileManager
int main(int argc, char *argv[]){
//dump文件生成的路径
std::string dumpPath = "D:\\test";//根据自己需求改(注意windows下目录为\\双斜杠)
// 崩溃记录
((KOAL_ZTA_DUMP *)KOAL_ZTA_DUMP::Instance()->setDumpFilePath(dumpPath.c_str(), "test.exe"))->installDumpCollect(/*(void *)reg 不需要注册事件可以不传入回调函数的地址,函数默参数为NULL*/);
//休眠五秒,五秒后程序崩溃。
Sleep(5000);
//这段代码可以使程序崩溃,作为测试用
_asm int 3;
}
技术说明
知识点:
- windows的api函数
- 回调函数
- 指针函数
开发环境
- vs2010
编码格式
- GB2312
(记得在vscode中或者记事本中设置代码的编码格式为GB2312,用UTF-8的话,弹窗中文会乱码)
如果编译报错,可以试着修改一下vs的配置。我这边代码是没有问题的。文章来源:https://www.toymoban.com/news/detail-403456.html
总结
例如:以上就是今天要讲的内容,本文仅仅简单介绍了windows下dump的生成,其中用到了大量的windows的api函数。可以多研究研究。文章来源地址https://www.toymoban.com/news/detail-403456.html
到了这里,关于Windows下生成dump文件的三种方式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!