1. C++dump编程
1. dump文件介绍
1.1 什么是dump文件
Dump 文件是进程的内存镜像 , 可以把程序的执行状态通过调试器保存到dump文件中 ;
Dump 文件是用来给驱动程序编写人员调试驱动程序用的 , 这种文件必须用专用工具软件打开 , 比如使用 WinDbg , VisualStudio 打开 ;
当我们的程序发布出去之后 , 在客户机上是无法跟踪自己代码的 BUG 的 , 所以 Dump 文件对于我们来说特别有用 ; 我们可以通过 .dmp 文件把出现 BUG 的情况再现 , 然后再现客户环境 (包括堆栈调用等情况) , 设置源码调试路径 , 可以找到出现 BUG 的语句 。
1.2 dump文件发展
在过去几年里,崩溃转储(crash dump)成为了调试工作的一个重要部分。第一代的crash dump通常被称为“全用户转储(full user dump)”,它包含了进程的虚拟内存的全部内容。但是,这样的dump经常非常大,使得通过电子方式发送给开发者非常困难,甚至没法完成。
随着Windows XP,微软发布了一组新的被称为“minidump”的崩溃转存技术。Minidump很容易定制。按照最常用的配置,一个minidump只包括了最必要的信息,用于恢复故障进程的所有线程的调用堆栈,以及查看故障时刻局部变量的值。这样的dump文件通常很小(只有几K字节)。所以,很容易通过电子方式发送给软件开发人员。一旦需要,minidump甚至可以包含比原来的crash dump更多的信息。例如,可以包含进程使用的内核对象的信息。另外,DbgHelp.dll提供了通过编程创建minidump的公开API。而且,它是可以重新发布的。我们可以不再依赖于外部工具。
minidump可以定制,也给我们带来了一个问题:保存多少应用程序状态信息才能既保证调试有效,又能够尽量保证minidump文件尽可能小?幸运的是我们可以通过DbgHelp函数组(MiniDumpWriteDump和MiniDumpCallback)来控制这些功能,甚至可以更复杂。在这篇文章里面,我们会解释怎么样使用这些函数来创建mindump,保证文件足够小但是又能有效调试。也会讲解minidump中应该包括哪些数据,并且如何使用通用调试器(WinDbg和VS.NET)来看这些信息。
2. Minidump类型
MiniDumpWriteDump的函数声明
BOOL MiniDumpWriteDump(HANDLE hProcess, //inDWORD ProcessId, //inHANDLE hFile, //inMINIDUMP_TYPE DumpType, //inPMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, //inPMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, //inPMINIDUMP_CALLBACK_INFORMATION CallbackParam //in
);
使用这个函数创建简单的minidump
void CreateMiniDump(EXCEPTION_POINTERS* pep )
{//首先,打开或者创建文件HANDLE hFile = CreateFile(_T("MiniDump.dmp"), GENERIC_READ | GENERIC_WRITE,0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );if( ( hFile != NULL ) && ( hFile != INVALID_HANDLE_VALUE ) ){ //然后,创建minidumpMINIDUMP_EXCEPTION_INFORMATION mdei;//一个结构体mdei.ThreadId = GetCurrentThreadId();mdei.ExceptionPointers = pep;mdei.ClientPointers = FALSE;MINIDUMP_TYPE mdt = MiniDumpNormal;BOOL rv = MiniDumpWriteDump( GetCurrentProcess(), //获取当前进程的一个伪句柄GetCurrentProcessId(),hFile, //打开的文件mdt, (pep != 0) ? &mdei : 0, 0, 0 );if( !rv )_tprintf( _T("MiniDumpWriteDump failed. Error: %u \n"), GetLastError() );else_tprintf( _T("Minidump created.\n") );// 关闭文件CloseHandle( hFile );}else_tprintf( _T("CreateFile failed. Error: %u \n"), GetLastError() );
}
在这个例子里面,我们如何指定minidump应该包括哪些数据呢?主要取决于MiniDumpWriteDump的第四个参数MINIDUMP_TYPE。如下:
typedef enum _MINIDUMP_TYPE {MiniDumpNormal = 0x00000000,MiniDumpWithDataSegs = 0x00000001,MiniDumpWithFullMemory = 0x00000002,MiniDumpWithHandleData = 0x00000004,MiniDumpFilterMemory = 0x00000008,MiniDumpScanMemory = 0x00000010,MiniDumpWithUnloadedModules = 0x00000020,MiniDumpWithIndirectlyReferencedMemory = 0x00000040,MiniDumpFilterModulePaths = 0x00000080,MiniDumpWithProcessThreadData = 0x00000100,MiniDumpWithPrivateReadWriteMemory = 0x00000200,MiniDumpWithoutOptionalData = 0x00000400,MiniDumpWithFullMemoryInfo = 0x00000800,MiniDumpWithThreadInfo = 0x00001000,MiniDumpWithCodeSegs = 0x00002000,MiniDumpWithoutManagedState = 0x00004000,
} MINIDUMP_TYPE;
MINIDUMP_TYPE枚举是一些标志,允许我们来控制minidump包含哪些内容。我们来看一下这些值的内容,以及如何使用它们。
MiniDumpNormal
MiniDumpNormal是一个特别的标志。它的值是0,意味着这个值永远隐含存在,甚至不需要显示指定。因此,我们可以假定这个标记代表了minidump中永远存在的一组基础数据集合。通过指定用户自定义的回调函数,可以过滤这些值。
下面表格显示了数据基础数据集合中的数据类型。
确实,MiniDumpNormal指定的基础信息集合非常有用。我们可以定位出现问题的指令,检查线程怎么样进入到这种状态。甚至可以产看到函数参数和局部变量的值。另外,这些信息也可以用来调试死锁,因为我们可以看到所有线程的调用栈,并且知道他们在等待什么。
同时,所有这些信息的代价非常小,minidump的大小通常不超过20KB。主要影响大小的因素的线程栈的大小-他们占用的内存越多,minidump的文件越大。
但是,如果需要调试的问题比较复杂,而不是像非法访问或者死锁这样的简单问题,我们就会发现MiniDumpNormal标记收集的信息还不够。我们有可能需要查看全局变量,但是里面没有。也有可能需要查看堆里面分配的结构体的内容,minidump也没有包括相应的堆信息。当我们需要更多的minidump数据时,就需要研究MINIDUMP_TYPE的其他成员了。
MiniDumpWithFullMemory
这可能是除了MiniDumpNormal以外使用最多的标志了。如果指定了这个标志,minidump会包含进程地址空间中所有可读页面的内容。我们可以看到应用程序分配的所有内存,这使我们有很多的调试方法。可以查看存储在栈上、堆上、模块数据段的所有数据。甚至还可以看到线程和进程环境块(Process Environment Block和Thread Environment Bolck, PEB和TEB)的数据。这些没有公开的数据结构可以给我们的调试提供无价的帮助。
使用这个标记的唯一问题是会使minidump变得很大,至少有几MByte。另外,minidump的内容里面包含了冗余信息,所有可执行模块的代码段都包含在了里面。但是很多时候,我们很容易从其他地方获得可执行代码。让我们一起来看看MINIDUMP_TYPE,是否能够找到更好的选项。
MiniDumpWithPrivateReadWriteMemory
如果指定这个标志,minidump会包括所有可读和可写的私有内存页的内容。这使我们可以察看栈、堆甚至TLS的数据。PEB和TEB也包括在里面。
这时候,minidump没有包括共享内存也的内容。也就是说,我们不能查看内存映射文件的内容。同样,可执行模块的代码和数据段也没有包括进来。不包括代码段意味着dump没有占用不需要的空间。但是,我们也没有办法查看全局变量的值。
无论如何,通过组合其他一些选项,MiniDumpWithPrivateReadWriteMemory是一个非常有用的选项。我们会在后面看到。
MiniDumpWithIndirectlyReferencedMemory
如果指定这个标志,MiniDumpWriteDump检查线程栈内存中的每一个指针。这些指针可能指向线程地址空间的其他可读内存页。一旦发现这样的指针,程序会读取指针附近1024字节的内容存到minidump中(指针前的256字节和指针后的768字节)。
3. C++生成dump文件
当程序遇到未处理的异常(主要指非指针造成)导致程序崩溃,如果在异常发生之前调用了SetUnhandledExceptionFilter()函数,异常交给函数处理。因而,在程序开始处增加SetUnhandledExceptionFilter()函数,并在函数中利用适当的方法生成Dump文件,即可实现需要的功能。
3.1 开启生成dump文件
int main()
{// 加入崩溃dump文件功能SetUnhandledExceptionFilter(ExceptionFilter);//添加回调// 使程序崩溃产生 Dump 文件int *p = NULL;*p = 1;
}
对于SetUnhandledExceptionFilter()函数,返回值有三种情况
EXCEPTION_EXECUTE_HANDLER equ 1 表示我已经处理了异常,可以优雅地结束了
EXCEPTION_CONTINUE_SEARCH equ 0 表示我不处理,于是windows调用默认的处理程序显示一个错误框,并结束
EXCEPTION_CONTINUE_EXECUTION equ -1 表示错误已经被修复,请从异常发生处继续执行
3.2 使用举例
#include "stdafx.h"
#include "Windows.h"
#include "DbgHelp.h"int GenerateMiniDump(PEXCEPTION_POINTERS pExceptionPointers)
{// 定义函数指针typedef BOOL(WINAPI * MiniDumpWriteDumpT)(HANDLE,DWORD,HANDLE,MINIDUMP_TYPE,PMINIDUMP_EXCEPTION_INFORMATION,PMINIDUMP_USER_STREAM_INFORMATION,PMINIDUMP_CALLBACK_INFORMATION);// 从 "DbgHelp.dll" 库中获取 "MiniDumpWriteDump" 函数MiniDumpWriteDumpT pfnMiniDumpWriteDump = NULL;HMODULE hDbgHelp = LoadLibrary(_T("DbgHelp.dll"));if (NULL == hDbgHelp){return EXCEPTION_CONTINUE_EXECUTION;}pfnMiniDumpWriteDump = (MiniDumpWriteDumpT)GetProcAddress(hDbgHelp, "MiniDumpWriteDump");if (NULL == pfnMiniDumpWriteDump){FreeLibrary(hDbgHelp);return EXCEPTION_CONTINUE_EXECUTION;}// 创建 dmp 文件件TCHAR szFileName[MAX_PATH] = { 0 };TCHAR* szVersion = _T("DumpDemo_v1.0");SYSTEMTIME stLocalTime;GetLocalTime(&stLocalTime);wsprintf(szFileName, L"%s-%04d%02d%02d-%02d%02d%02d.dmp",szVersion, stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond);HANDLE hDumpFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE,FILE_SHARE_WRITE | FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);if (INVALID_HANDLE_VALUE == hDumpFile){FreeLibrary(hDbgHelp);return EXCEPTION_CONTINUE_EXECUTION;}// 写入 dmp 文件MINIDUMP_EXCEPTION_INFORMATION expParam;expParam.ThreadId = GetCurrentThreadId();expParam.ExceptionPointers = pExceptionPointers;expParam.ClientPointers = FALSE;pfnMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),hDumpFile, MiniDumpWithDataSegs, (pExceptionPointers ? &expParam : NULL), NULL, NULL);// 释放文件CloseHandle(hDumpFile);FreeLibrary(hDbgHelp);return EXCEPTION_EXECUTE_HANDLER;
}LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS lpExceptionInfo)
{// 这里做一些异常的过滤或提示if (IsDebuggerPresent()){return EXCEPTION_CONTINUE_SEARCH;}return GenerateMiniDump(lpExceptionInfo);
}int main()
{// 加入崩溃dump文件功能SetUnhandledExceptionFilter(ExceptionFilter);// 使程序崩溃产生 Dump 文件int *p = NULL;*p = 1;
}
1. C++dump编程相关推荐
- dump文件_零基础编程——Python文件、JSON数据存储
摘要 书接前文: 01-<零基础编程--起步并搭建Python环境> 02-<零基础编程--变量与数据类型> 03-<零基础编程--Python循环.函数.类> 教 ...
- [Windows编程] 如何捕捉程序异常/crash 并生成 dump 文件
前面介绍如如何用WinDBG 生成crash dump <WinDBG 技巧:如何生成Dump 文件(.dump 命令) >,但是用户机器上通常不安装WinDBG, 而且多数用户也不知道怎 ...
- Linux系统编程30:进程信号之产生信号的四种方式(Core Dump,kill,raise)
文章目录 (1)通过按键产生信号-Core Dump (2)调用系统函数向进程发送信号 A:kill B:raise C:abort (3)由软件条件产生信号 (4)硬件异常产生信号 总结: 为了方便 ...
- Linux Wi-Fi 编程API介绍
本文为<深入理解Android Wi-Fi.NFC和GPS卷>读书笔记,Android源码为Android 5.1 Linux平台上目前常用的专门针对无线网络设备编程的API有两套 最早的 ...
- Java并发编程71道面试题及答案
Java并发编程71道面试题及答案 1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方 ...
- Java并发编程73道面试题及答案——稳了
点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户 ...
- 网络编程中的关键问题总结
网络编程中的关键问题总结 总结下网络编程中关键的细节问题,包含连接建立.连接断开.消息到达.发送消息等等: 连接建立 包括服务端接受 (accept) 新连接和客户端成功发起 (connect) 连接 ...
- 自学入门不在困难,初学者挑战学习Python编程30天 (三)
坚持坚持再坚持!!! 如果文章对于你有帮助,欢迎收藏.关注.点赞与订阅专栏 有任何疑问欢迎私信 目录 第 11 天 - 函数 定义函数 声明和调用函数 无参数函数 返回值的函数 - 第 1 部分 带参 ...
- 初探可编程网关 Pipy
有幸参加了 Flomesh[1] 组织的workshop,了解了他们的 Pipy 网络代理,以及围绕 Pipy 构建起来的生态.Pipy 在生态中,不止是代理的角色,还是 Flomesh 服务网格中的 ...
- HBase眼高手低从Shell到IDEA编程、心路笔记、踩坑过程
HBase眼高手低从Shell到IDEA编程.心路笔记.踩坑过程 HBase眼高手低 通过shell操作Hbase Foundation 在terminal中输入hbase,就可以查看hbase命令的 ...
最新文章
- 深入浅出Redis五种基本数据类型
- mac os x常用快捷键及用法
- php 单一职责,PHP之单例模式(职责模式)
- 形态数轴上的反密码子和氨基酸
- Python教程:序列的增量赋值
- Python tab 补全
- java jdbc reparecall_Java Connection.prepareCall方法代碼示例
- 拥抱.NET Core,跨平台的轻量级RPC:Rabbit.Rpc
- java安全(四) JNDI
- JS闭包的简单理解。优缺点以及垃圾回收机制
- [Python] L1-026. I Love GPLT-PAT团体程序设计天梯赛GPLT
- 去除本机利用ssh协议登陆远程机器的痕迹
- 【水果识别】基于matalb GUI水果分类系统【含Matlab源码 174期】
- SD敢达服务器源码,《sd高达wars》金手指代码 SD高达G世纪WARS金手指
- Unity 预编译选项
- STP配置 HSRP配置 端口追踪
- Kubernetes核心原理(二)之Controller Manager
- mysql对韵母分组,基于MySQL的中文发音查询的元级实现
- easyPOI和导入复杂excel的各种坑
- 前端工程化实践总结 | QQ音乐商业化Web团队
热门文章
- NHibernate初学者指南(3):创建Model
- Intel汇编与程序设计第五版3.4定义数据小节
- iSCSI存储的3种连接方式
- Android中常用适配器理解及使用
- html加载本地pdf,WkHTMLtoPDF不加载本地CSS和图像
- c语言删除文件remove_「Python」 - 文件基本操作
- Python基础--02
- 简述linux命令的,简述linux系统以及一些简单的命令
- php facebook授权登录获取头像_IdentityServer4从数据库获取User登录并对Claims授权验证(五)...
- php 计算两点地理坐标的距离