MiniDump文件的创建、分析堆栈信息、定位错误、查看异常处理信息

本文详细介绍了如何创建MiniDump文件,分析堆栈信息以定位错误,以及查看异常处理信息。通过MiniDumpCreateDumpWin32Api创建MiniDump,使用异常处理函数获取堆栈信息,利用StackWalk64等API解析堆栈,通过SymGetSymFromAddr64等函数获取错误信息。此外,文章还提到了符号表绑定和MiniDump的分析流程,包括使用WinDbg工具进行调试。

1、MiniDump文件的创建:

        创建miniDump的方法有很多。可以通过MiniDumpCreateDumpWin32Api创建。必要参数为EXCEPTION_POINTERS结构,获取这个结构可以通过在异常出通过GetExceptionInfomation函数API获取这个结构,也可以通过SetUnhandleExceptionFilter函数设置自定义异常处理函数(注意:这个函数是处理系统无法处理的异常时触发的),这个自定义异常处理函数的函数原型为
typedef LONG (WINAPI *PTOP_LEVEL_EXCEPTION_FILTER)(_In_ struct _EXCEPTION_POINTERS *ExceptionInfo);

这个里面的参数为系统传入的,其中包括了异常信息,以及上下文句柄。
而写Dump的函数原型为:

BOOL MiniDumpWriteDump( HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam );

其中需要主要的是第三个参数和 第四个参数:第三个参数表示需要在dump文件中记录什么信息。详细解释见MSDN,第四个参数从众多网上例子来看很多都没有在意这个参数。这个参数的结构体原型如下:

typedef struct _MINIDUMP_EXCEPTION_INFORMATION {
    DWORD ThreadId;
    PEXCEPTION_POINTERS ExceptionPointers;
    BOOL ClientPointers;
} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION;

ClientPointers指针这个参数到底什么时候为TRUE什么时候为FALSE呢?
MSDN的解释为如果调试进程在内存中为true,否则为false,如果错误地址所在进程在内存中,应该为false,否则为true,总结一句话为,如果要记录内存中的地址相关信息(包括堆栈,偏移地址,断片地址,节表信息等等)应该为FALSE,其他为true。这个参数决定着dump文件的价值。

分析堆栈信息

   堆栈信息可以帮助我们快速定位程序所谓位置以及错误发生的原因。有利于程序的错误修复,加快工作效率。如果获取异常程序的堆栈信息呢?
    在设置自定义异常处理函数时,传入的异常信息结构中包括了一个CONTEXT结构体的指针。这个指针就是上下文句柄。

   这个结构体的原型如下:

      

typedef struct _CONTEXT {


    RD ContextFlags;

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

    FLOATING_SAVE_AREA FloatSave;

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

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

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

    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

} CONTEXT;

这个结构体的详细解释见MSDN。

通过这个结构我们里面的相关信息就可以获取异常程序的堆栈信息。获取堆栈信息的API 函数原型如下:

BOOL IMAGEAPI StackWalk64( DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress );

第一个参数:表示本机芯片类型,芯片不同也表明取值不一样。

第四个参数表示获取堆栈信息所需要的一些关键信息。

其他参数详情见MSDN。

举例如下:这个例子网上很多,详细信息请将CONTEXT结构和STACKFRAME64结构结合学习。

unsigned long dwImageType=IMAGE_FILE_MACHINE_I386;//设置默认芯片类型为X86

 

#ifdef _M_IX86
    sf.AddrPC.Offset = c.Eip;//当芯片类型为X86是,程序计数器地址的偏移量(或者首地址)取值为CONTEXT结构的Eip成员
    sf.AddrPC.Mode = AddrModeFlat;//平面寻址//详细解释见MSDN
    sf.AddrStack.Offset = c.Esp;//堆栈偏移
    sf.AddrStack.Mode = AddrModeFlat;
    sf.AddrFrame.Offset = c.Ebp;//帧偏移
    sf.AddrFrame.Mode = AddrModeFlat;
#elif _M_X64
    dwImageType = IMAGE_FILE_MACHINE_AMD64;
    sf.AddrPC.Offset = c.Rip;
    sf.AddrPC.Mode = AddrModeFlat;
    sf.AddrFrame.Offset = c.Rbp;
    sf.AddrFrame.Mode = AddrModeFlat;
    sf.AddrStack.Offset = c.Rsp;
    sf.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
    dwImageType = IMAGE_FILE_MACHINE_IA64;
    sf.AddrPC.Offset = c.StIIP;
    sf.AddrPC.Mode = AddrModeFlat;
    sf.AddrFrame.Offset = c.IntSp;
    sf.AddrFrame.Mode = AddrModeFlat;
    sf.AddrBStore.Offset = c.RsBSP;
    sf.AddrBStore.Mode = AddrModeFlat;
    sf.AddrStack.Offset = c.IntSp;
    sf.AddrStack.Mode = AddrModeFlat;

#endif

获取错误函数API

SymGetSymFromAddr64(

_In_ HANDLE hProcess,

_In_ DWORD64 qwAddr,

_Out_opt_ PDWORD64 pdwDisplacement,

_Inout_ PIMAGEHLP_SYMBOL64 Symbol

);

获取错误错误行的API

BOOL
IMAGEAPI
SymGetLineFromAddr64(
    _In_ HANDLE hProcess,
    _In_ DWORD64 qwAddr,
    _Out_ PDWORD pdwDisplacement,
    _Out_ PIMAGEHLP_LINE64 Line64
    );

//获取错误模块

BOOL
IMAGEAPI
SymGetModuleInfo64(
    _In_ HANDLE hProcess,
    _In_ DWORD64 qwAddr,
    _Out_ PIMAGEHLP_MODULE64 ModuleInfo
    );

这三个API的参数详细解释见MSDN,

第二个参数的取值为为当前程序的程序计时器偏移地址,表示从当前程序的主线程开始获取错误信息。

符号表:

symbolTable是分析dump文件的关键所在,symbol可以帮助更加准确的定位程序错误。因此,在获取堆栈信息获取生成dump文件之前绑定符号表是必须的。绑定的符号表的API如下:

 BOOL IMAGEAPI SymInitialize( HANDLE hProcess, PCSTR UserSearchPath, BOOL fInvadeProcess );

详细解释见MSDN,这里详细讲解第二个参数,符号表路径,如何设置符号表路径呢?可以设置为NULL,具体解释见MSDN,

但如果不为空如何加载呢?

示例如下:

#define SYMPATH "%SYSTEMROOT%;.\\Symbols\\;..\\Symbols\\;..\\..\\Symbols\\;..\\..\\..\\Symbols\\"

ExpandEnvironmentStringsA(SYMPATH, symPath, sizeof(symPath));

这个API的功能是扩展环境变量的取值,通俗一点将就是获取变量值。

至于字符串是什么含义,请查询环境变量相关变量含义。

查询异常处理信息:

首先查询异常信息需要一定的windebug基础。

https://blog.csdn.net/sunboyhch/article/details/37914727

查询具体异常的一般流程为:

miniDump的具体操作流程是这样:
1、打开异常dump文件
2、导入pdb符号文件
3、输入!analyze -v 命令分析出错原因,堆栈信息、出错位置,以及包含错误位置的文件。
4、然后通过文件菜单打开文件。
5、输入.ecxr 关联
6、kp命令。输出详细的错误信息。
第二种方法是
1、直接输入.excr直接通过dump文件的记录的索引关联源文件。
2、输入kp命令查看详细的堆栈错误信息

在异常处理的过程中。还需要了解一些其他的知识,如dos文件头、PE文件格式等等。推荐博客链接如下:

https://blog.csdn.net/chenlycly/article/details/53378196

bool CrashHandleCls::GetLogicalAddress(PVOID pLinearAddress,PTSTR pszModule,DWORD dwModuleNameLen,DWORD_PTR& dwSection,DWORD_PTR& dwOffset){

MEMORY_BASIC_INFORMATION mbi;

if(!::VirtualQuery(pLinearAddress,&mbi,sizeof(mbi)))return false;

DWORD_PTR hModule=(DWORD_PTR)mbi.AllocationBase;//获取页面起始地址

//验证访问权限

if(IsBadReadPtr((const void *)hModule,sizeof(IMAGE_DOS_HEADER))) {

_tcsncpy(pszModule,_T("Unknown"),dwModuleNameLen);

dwSection=0;

dwOffset=0;

return false;

}

//获取完整的路径

if(!::GetModuleFileNameW((HMODULE)hModule,pszModule,dwModuleNameLen))return false;

PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hModule;//转换为DOS文件结构

PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hModule + pDosHdr->e_lfanew); //获取PE文件的文件头 PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHdr);//定位区块表(节表)

DWORD_PTR dwRVA = (DWORD_PTR)pLinearAddress - hModule;//获取偏移地址

//pNtHdr->FileHeader.NumberOfSections 获取节表总数

for(UINT i = 0; i < pNtHdr->FileHeader.NumberOfSections; i++, pSection++)

{

DWORD_PTR dwSectionStart = pSection->VirtualAddress;//获取目标在内存中的开始地址(节区的 RVA 地址)

DWORD_PTR dwSectionEnd = dwSectionStart +

max(pSection->SizeOfRawData, pSection->Misc.VirtualSize);//获取目标在内存中的结束地

if((dwRVA >= dwSectionStart) && (dwRVA <= dwSectionEnd)) {

dwSection = i + 1;

dwOffset = dwRVA - dwSectionStart;//获取偏移量

return TRUE;

}

}

dwSection = 0;

dwOffset = 0;

return FALSE;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值