exe文件、dmp文件和pdb文件必须保持一致!exe文件和pdb文件同时生成,dmp文件是由当前exe生成的。
调试器是如何来判别EXE、DLL等是否和一个pdb文件匹配呢?
每次我们链接EXE或者DLL或者SYS的时候,链接器都将产生一个唯一的GUID,然后将其写入到PDB和可执行文件。调试器加载的时候将检查两者的GUID,如果一致就表示他们匹配。
如果我们需要调试,我们需要查dmp文件,那么请妥善保管好自己的代码和pdb。每次重新编译,即使所有代码均没有变化,他们的GUID也不同。
所以在发布版的可执行程序,保管备份好代码和pdb.这样就算程序崩溃了,也可以调试定位问题。
设置生成 pdb
-
选择 项目 -> 工程名+属性
-
之后选择 配置属性 -> 连接器 -> 调试
上图中,“生成调试信息”为pdb文件生成与否的使能开关,“生成程序数据库文件”为该pdb文件的名字,默认即可。
PDB就是程序数据库文件并且包含了符号文件。符号文件包含的信息包括:函数,局部以及全局变量,以及用来把汇编代码和源代码关联起来的行号信息。
DUMP文件
生成dump文件的办法有下面4种,一般release使用第三种,debug使用第四种办法。
1.手动设置生成。
在代码调试过程中,知道会出现bug的情况下,手动点击工具栏的调试->将转储另存为,生成dump文件。这种办法太傻蛋了。
2.自己写封装一个函数,调用MiniDumpWriteDump函数实现。主动检查,主动生成。
调用时机:当异常发生时,且程序不处于调试模式,则首先调用该函数。即如果在调试模式下,不会调用该函数,也就不会生成dump文件。
MiniDumpWriteDump是MS DbgHelp.dll 中一个API, 用于导出当前运行的程序的Dump。具体见:设置C++崩溃时生成Dump文件_EnskDeCode-CSDN博客_c++ dump。
MFC程序
#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;
}
qt程序
在QtCreator中默认不支持生成dump文件,且发行版release模式不含调试信息,因此这里需要进行以下两步设置。
1.在.pro文件中添加如下语句,来产生调试信息以及pdb文件
win32{
QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO
LIBS += -lDbgHelp
}
2.程序文件中添加可以产生dump文件的代码
#include "dialog.h"
#include <QApplication>
#include <QDateTime>
#include <QDebug>
#include <QDir>
static QString g_strDumpPath = "";
#ifdef Q_OS_WIN
#include <Windows.h> // Windows.h必须放在DbgHelp.h前,否则编译会报错
#include <DbgHelp.h>
LONG crashHandler(EXCEPTION_POINTERS *pException)
{
QString curDataTime = QDateTime::currentDateTime().toString("yyyyMMddhhmmss");
QString dumpName = g_strDumpPath + curDataTime + ".dmp";
HANDLE dumpFile = CreateFile((LPCWSTR)QString(dumpName).utf16(),GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if(dumpFile != INVALID_HANDLE_VALUE)
{
MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
dumpInfo.ExceptionPointers = pException;
dumpInfo.ThreadId = GetCurrentThreadId();
dumpInfo.ClientPointers = TRUE;
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),dumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);
CloseHandle(dumpFile);
}
else
{
qDebug() << "dumpFile not vaild";
}
return EXCEPTION_EXECUTE_HANDLER;
}
//防止CRT(C runtime)函数报错可能捕捉不到
void DisableSetUnhandledExceptionFilter()
{
void* addr = (void*)GetProcAddress(LoadLibrary(L"kernel32.dll"), "SetUnhandledExceptionFilter");
if(addr)
{
unsigned char code[16];
int size = 0;
code[size++] = 0x33;
code[size++] = 0xC0;
code[size++] = 0xC2;
code[size++] = 0x04;
code[size++] = 0x00;
DWORD dwOldFlag, dwTempFlag;
VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
}
}
#endif
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
#ifdef Q_OS_WIN /// 创建dump
QString strdmpFile =QApplication::applicationDirPath();
strdmpFile = strdmpFile +"/testdump/";
g_strDumpPath = strdmpFile;
QDir dir(strdmpFile);
if(!dir.exists())
{
if (!dir.mkpath(strdmpFile))
qDebug() << QStringLiteral("创建dump文件目录失败");
}
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)crashHandler);
DisableSetUnhandledExceptionFilter();
#endif
Dialog w;
w.show();
return a.exec();
}
3.通过任务管理器:
这种适用在exe程序挂了(crash)的时候进程还未退出,比如我运行程序,出现了下面的错:
此时打开任务管理器,右击相应进程,点击"Create Dump File“:
这种办法:dmp文件存放在系统目录。需要自己查找磁盘,可以使用everything 软件更快查找到。
4.改注册表
如果程序crash的时候没有框蹦出来,可以通过改注册表的设置让操作系统在程序crash的时候自动生成dump,并放到特定的目录。
增加注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps
添加项如下图
其中Value Data=1代表的含义是:
设置完成后,crash发生时,操作系统生成dump,路径在dumpfolder设置的目录d:\dump文件下。
加载dump和pdb文件
1.配置源代码目录
打开一个项目解决方案(可新建,可以利用现有的)。右键属性。选择 通用属性 调试源文件 添加程序源代码目录。注意一定是解决方案所在的路径
test是随便一个工程。而memory才是出现崩溃的工程。就是出现崩溃的程序的源码导进来。
但是如果代码已经改过了,恢复不到当时的状态,vs显示不了源码怎么办?只要设置:Tools->Options->Debugging->General->Require source files to exactly match the original version 这个复选框钩掉就可以了.
2.配置pdb信息
将生成的dump文件直接拖入到vs中。
拖入后可看到如上界面。右上方红色框选部分。设置符号路径。点击进入。配置pdb文件步骤如下:
a.在vs的工具菜单->选项->调试->符号 配置pdb文件路径。
pdb的路径就是前面生成exe时,同时生成pdb文件的存放的路径。
具体到某一个.pdb文件或者把所有的pdb文件集中存放到一个公共的目录文件都可以。
b.把运行的exe、dll路径也加载到符号文件的位置中。
这样才能把exe关联起来(不能忽略)。
c.第一次调试pdb时必须也勾选microsotf符号服务器
(目的是加载windows本身的符号),后面在调试就不用勾选了。可以在当前的选项页面看到缓冲符号所在的目录。
3.仅限本机进行调试 。
都设置好了,点击 使用仅限本机进行调试 按钮。然后程序自动调到崩溃的代码
效果如下
注意的地方:
1.要把可执行程序即exe放到 进程名称对应的路径。不然程序没法启动。