VC++给软件添加异常捕获模块生成dump文件(附源码)

Posted dvlinker

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了VC++给软件添加异常捕获模块生成dump文件(附源码)相关的知识,希望对你有一定的参考价值。

       软件在运行过程中会时常发生内存越界、内存访问为例、stack overflow线程栈溢出、空指针与野指针等异常崩溃,仅仅是依靠Debug和Release下的调试是远远不够的,因为有些崩溃不是必现的,或者是Debug下很难出现的。所以我们需要在软件中添加异常捕获的模块,在捕获到异常时生成包含异常上下文的dump文件,以供事后使用windbg进行分析。本文我们就来简单介绍一下如何设置异常回调函数,如何生成保存异常信息的dump文件。

1、API函数SetUnhandledExceptionFilter介绍

        Windows系统提供设置异常处理函数的API函数SetUnhandledExceptionFilter,其声明如下:

LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(
  [in] LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);

该函数的参数就是设置的异常处理函数,该API返回的是系统默认的异常处理函数的地址。当软件发生异常时,会回调之前设置的异常处理函数,异常处理函数的原型如下:

LONG WINAPI SEHUnhandledExceptionFilter(PEXCEPTION_POINTERS pExInfo)

该函数的参数是存放异常信息的结构体EXCEPTION_POINTERS指针,如下:

//
// Typedef for pointer returned by exception_info()
//
typedef struct _EXCEPTION_POINTERS 
    PEXCEPTION_RECORD ExceptionRecord;
    PCONTEXT ContextRecord;
 EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

其中,EXCEPTION_RECORD结构体定义如下:

//
// Exception record definition.
//
typedef struct _EXCEPTION_RECORD 
    DWORD    ExceptionCode;
    DWORD ExceptionFlags;
    struct _EXCEPTION_RECORD *ExceptionRecord;
    PVOID ExceptionAddress;
    DWORD NumberParameters;
    ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
     EXCEPTION_RECORD;

typedef EXCEPTION_RECORD *PEXCEPTION_RECORD;

上述结构体中的ExceptionCode字段对应异常类型的异常码,常见的异常码有:

#define STATUS_ACCESS_VIOLATION          ((DWORD   )0xC0000005L)    // 内存访问违例
#define STATUS_IN_PAGE_ERROR             ((DWORD   )0xC0000006L)    
#define STATUS_INVALID_HANDLE            ((DWORD   )0xC0000008L)    
#define STATUS_INVALID_PARAMETER         ((DWORD   )0xC000000DL)    // 无效参数
#define STATUS_NO_MEMORY                 ((DWORD   )0xC0000017L)    
#define STATUS_ILLEGAL_INSTRUCTION       ((DWORD   )0xC000001DL)    
#define STATUS_NONCONTINUABLE_EXCEPTION  ((DWORD   )0xC0000025L)    
#define STATUS_INVALID_DISPOSITION       ((DWORD   )0xC0000026L)    
#define STATUS_ARRAY_BOUNDS_EXCEEDED     ((DWORD   )0xC000008CL)    // 数组越界
#define STATUS_FLOAT_DENORMAL_OPERAND    ((DWORD   )0xC000008DL)    
#define STATUS_FLOAT_DIVIDE_BY_ZERO      ((DWORD   )0xC000008EL)    
#define STATUS_FLOAT_INEXACT_RESULT      ((DWORD   )0xC000008FL)    
#define STATUS_FLOAT_INVALID_OPERATION   ((DWORD   )0xC0000090L)    
#define STATUS_FLOAT_OVERFLOW            ((DWORD   )0xC0000091L)    
#define STATUS_FLOAT_STACK_CHECK         ((DWORD   )0xC0000092L)    
#define STATUS_FLOAT_UNDERFLOW           ((DWORD   )0xC0000093L)    
#define STATUS_INTEGER_DIVIDE_BY_ZERO    ((DWORD   )0xC0000094L)    // 除0崩溃
#define STATUS_INTEGER_OVERFLOW          ((DWORD   )0xC0000095L)    
#define STATUS_PRIVILEGED_INSTRUCTION    ((DWORD   )0xC0000096L)    
#define STATUS_STACK_OVERFLOW            ((DWORD   )0xC00000FDL)    // 线程栈溢出
#define STATUS_DLL_NOT_FOUND             ((DWORD   )0xC0000135L)    
#define STATUS_ORDINAL_NOT_FOUND         ((DWORD   )0xC0000138L)    
#define STATUS_ENTRYPOINT_NOT_FOUND      ((DWORD   )0xC0000139L)    
#define STATUS_CONTROL_C_EXIT            ((DWORD   )0xC000013AL)    
#define STATUS_DLL_INIT_FAILED           ((DWORD   )0xC0000142L)    
#define STATUS_FLOAT_MULTIPLE_FAULTS     ((DWORD   )0xC00002B4L)    
#define STATUS_FLOAT_MULTIPLE_TRAPS      ((DWORD   )0xC00002B5L)    
#define STATUS_REG_NAT_CONSUMPTION       ((DWORD   )0xC00002C9L)    
#define STATUS_STACK_BUFFER_OVERRUN      ((DWORD   )0xC0000409L)    // 栈内存buffer越界
#define STATUS_INVALID_CRUNTIME_PARAMETER ((DWORD   )0xC0000417L) 

此外,存放异常上下文信息的结构体Context的定义如下:

//
// Context Frame
//
//  This frame has a several purposes: 1) it is used as an argument to
//  NtContinue, 2) is is used to constuct a call frame for APC delivery,
//  and 3) it is used in the user level thread creation routines.
//
//  The layout of the record conforms to a standard call frame.
//

typedef struct _CONTEXT 

    //
    // The flags values within this flag control the contents of
    // a CONTEXT record.
    //
    // If the context record is used as an input parameter, then
    // for each portion of the context record controlled by a flag
    // whose value is set, it is assumed that that portion of the
    // context record contains valid context. If the context record
    // is being used to modify a threads context, then only that
    // portion of the threads context will be modified.
    //
    // If the context record is used as an IN OUT parameter to capture
    // the context of a thread, then only those portions of the thread's
    // context corresponding to set flags will be returned.
    //
    // The context record is never used as an OUT only parameter.
    //

    DWORD ContextFlags;

    //
    // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
    // set in ContextFlags.  Note that CONTEXT_DEBUG_REGISTERS is NOT
    // included in CONTEXT_FULL.
    //

    DWORD   Dr0;
    DWORD   Dr1;
    DWORD   Dr2;
    DWORD   Dr3;
    DWORD   Dr6;
    DWORD   Dr7;

    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
    //

    FLOATING_SAVE_AREA FloatSave;

    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_SEGMENTS.
    //

    DWORD   SegGs;
    DWORD   SegFs;
    DWORD   SegEs;
    DWORD   SegDs;

    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_INTEGER.
    //

    DWORD   Edi;
    DWORD   Esi;
    DWORD   Ebx;
    DWORD   Edx;
    DWORD   Ecx;
    DWORD   Eax;

    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_CONTROL.
    //

    DWORD   Ebp;
    DWORD   Eip;
    DWORD   SegCs;              // MUST BE SANITIZED
    DWORD   EFlags;             // MUST BE SANITIZED
    DWORD   Esp;
    DWORD   SegSs;

    //
    // This section is specified/returned if the ContextFlags word
    // contains the flag CONTEXT_EXTENDED_REGISTERS.
    // The format and contexts are processor specific
    //

    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

 CONTEXT;

该结构体中主要存放各个寄存器的信息。

2、调用SetUnhandledExceptionFilter设置异常处理函数

       设置异常处理函数的示例代码如下:

// 异常处理函数(回调函数)
LONG WINAPI SEHUnhandledExceptionFilter( PEXCEPTION_POINTERS pExInfo )

    // 处理异常的代码
	// ......
	
    return EXCEPTION_EXECUTE_HANDLER;


// 调用SetUnhandledExceptionFilter设置异常处理函数
LPTOP_LEVEL_EXCEPTION_FILTER oldExceptionFilter = SetUnhandledExceptionFilter( SEHUnhandledExceptionFilter );

调用SetUnhandledExceptionFilter设置异常处理函数,只是给所在的线程设置异常处理函数。如果有多个线程,则需要给每个线程分别设置异常处理函数。推荐大家使用开源的CrashReport库去捕获异常。

3、调用MiniDumpWriteDump函数导出包含异常上下文的dump文件

       生成dump文件的代码如下:

void CreateDump(struct _EXCEPTION_POINTERS *pExceptionPointers) 

    CString strDumpFile = _T("E:\\\\0328.dump");
	
	HANDLE hDumpFile;
	hDumpFile = CreateFile(strDumpFile, GENERIC_READ|GENERIC_WRITE, 
		FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);

	MINIDUMP_EXCEPTION_INFORMATION ExpParam;
	ExpParam.ThreadId = GetCurrentThreadId();
	ExpParam.ExceptionPointers = pExceptionPointers;
	ExpParam.ClientPointers = TRUE;

    // 导出minidump,文件大小在几MB左右,如果要导出全dump,则文件会比较大
	MINIDUMP_TYPE MiniDumpWithDataSegs = (MINIDUMP_TYPE)(MiniDumpNormal 
		| MiniDumpWithHandleData 
		| MiniDumpWithUnloadedModules 
		| MiniDumpWithIndirectlyReferencedMemory 
		| MiniDumpScanMemory 
		| MiniDumpWithProcessThreadData 
		| MiniDumpWithThreadInfo);

	BOOL bMiniDumpSuccessful = FALSE;
	bMiniDumpSuccessful = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), 
		hDumpFile, MiniDumpWithDataSegs, &ExpParam, NULL, NULL);

	return;

上述接口中我们设置的是导出mini dump文件,文件大小大概在几MB左右,如果要导出包含所有信息的全dump文件,则会比较大,大概在几百MB到上GB的大小。我们在自动生成dump文件时,文件不能太大,如果太大的话,会占用大量的磁盘空间。

       mini dump文件和全dump文件的区别在于,全dump文件包含了所有的信息,而mini dump只包含了部分信息。在全dump文件中,使用windbg可以查看所有变量内存中的值,而mini dump中只能查看到部分变量在内存中的值。

以上是关于VC++给软件添加异常捕获模块生成dump文件(附源码)的主要内容,如果未能解决你的问题,请参考以下文章

VC++使用fprintf函数实现写日志文件的功能(附源码)

dump文件类型与dump文件生成方法详解

VC++屏幕捕获并保存成图片(附源码)

Windows 程序 dump 崩溃调试

vc 怎么 生成 dump 文件

jitdump.dump文件怎么分析