写代码路上踩过的坑,特此记录,以免忘记
网上全部都是有 pdb 文件。Release 没有 pdb 文件。本文适用于没有 pdb 文件,如何定位
如何生成 dump 文件
作为一个程序员,必现的 bug 容易排查,最怕遇见非必现 bug,尤其是那种在自己环境上测多少次都没问题,到用户环境偶尔会 crash,无从下手,让人瞬间抓狂。并且像这种非必现 bug 一般很难采用日志的方式来定位。
这个时候就需要保留程序 crash 的环境,最好的方式就是生成 Dump 文件。如果要利用 C++ 代码生成,可以直接采用库函数
MiniDumpWriteDump
头文件
- #include <dbghelp.h>
- #pragma comment(lib, “dbghelp.lib”) 一定要连接这个库,否则无法使用
#include <windows.h>
#include <dbghelp.h>
#pragma comment(lib, "dbghelp.lib")
void exceptionHandler(PEXCEPTION_POINTERS excpInfo) {
std::mutex g_handlerLock;
std::unique_lock<std::mutex> lk(g_handlerLock);
std::string dump_dir = "D:\\code\\crash.dmp";
LONG ret = EXCEPTION_CONTINUE_SEARCH;
HANDLE hFile =
::CreateFile(dump_dir.c_str(), GENERIC_WRITE, FILE_ATTRIBUTE_READONLY,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
MINIDUMP_EXCEPTION_INFORMATION exptInfo;
exptInfo.ThreadId = ::GetCurrentThreadId();
exptInfo.ExceptionPointers = excpInfo;
BOOL bOK =
::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(),
hFile, MiniDumpNormal, &exptInfo, NULL, NULL);
if (bOK) {
ret = EXCEPTION_EXECUTE_HANDLER;
}
}
}
LONG WINAPI unhandledException(PEXCEPTION_POINTERS excpInfo = nullptr) {
if (excpInfo == nullptr) {
__try
{
RaiseException(EXCEPTION_BREAKPOINT, 0, 0, nullptr);
} __except(exceptionHandler(GetExceptionInformation()),
EXCEPTION_EXECUTE_HANDLER) {
}
} else {
exceptionHandler(excpInfo);
}
return 0;
}
int main(int argc, char** argv) {
SetUnhandledExceptionFilter(unhandledException);
}
通过上面的方法,就会生成 dump 文件,如果要生成指定信息的 dump 文件,只需修改 MiniDumpWriteDump() 对应的参数即可
如何生成 map 文件
map 文件中记录是程序的各种地址信息
VS生成
- 打开项目的“属性页”对话框。
- 链接器 —> 所有选项—>生成映射文件—>是
- 就会在程序目录下生成 .map 文件。。如果找不到,直接搜索
cmake 命令
target_link_options(${EXE_NAME_UI} PRIVATE “/MAP:${CMAKE_CURRENT_BINARY_DIR}/test.map”)
其中,EXE_NAME_UI 是自己设置的 exe 的name ,后面可指定生成的文件夹
如果只有这两个文件的话,只能定位到 crash 的函数,而不能精确定位到 行数
如何生成 cod 文件
.cod 文件里面记录了源码已经对应的汇编代码,已经行号
VS 生成
- 打开项目的“属性页”对话框。
- 属性页 —> C/C++ —> 输出文件 —> 汇编程序输出 —> 选择 程序集、机器码和源代码
- 就可以在默认路径下生成 .cod 文件
cmake 命令
set(MSVC_COVERAGE_COMPILE_FLAGS “/FAcs”)
set(CMAKE_CXX_FLAGS “${MSVC_COVERAGE_COMPILE_FLAGS} ${CMAKE_CXX_FLAGS}”)
这个目前没有找到可以指定输出路径的方法,知道的大佬可以告知一下
找 crash 地址
有了这三个文件,就可以定位到 crash 的地方了
写了一个很简单的例子。。空指针赋值问题,肯定会 crash
#include <iostream>
#include <windows.h>
#include <dbghelp.h>
#include <string>
#pragma comment(lib, "dbghelp.lib")
void exceptionHandler(PEXCEPTION_POINTERS excpInfo) {
std::string dump_dir = "F:\\code\\crash.dmp";
LONG ret = EXCEPTION_CONTINUE_SEARCH;
HANDLE hFile = CreateFileA(dump_dir.c_str(), GENERIC_WRITE, FILE_ATTRIBUTE_READONLY,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
MINIDUMP_EXCEPTION_INFORMATION exptInfo;
exptInfo.ThreadId = ::GetCurrentThreadId();
exptInfo.ExceptionPointers = excpInfo;
BOOL bOK =
::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(),
hFile, MiniDumpNormal, &exptInfo, NULL, NULL);
}
}
LONG WINAPI unhandledException(PEXCEPTION_POINTERS excpInfo = nullptr) {
if (excpInfo == nullptr) {
__try
{
RaiseException(EXCEPTION_BREAKPOINT, 0, 0, nullptr);
}
__except (exceptionHandler(GetExceptionInformation()),
EXCEPTION_EXECUTE_HANDLER) {
}
}
else {
exceptionHandler(excpInfo);
}
return 0;
}
void errorFun()
{
int* p = nullptr;
*p = 1;
}
void testfun1()
{
}
void testfun2()
{
}
void testfun3()
{
}
int main()
{
SetUnhandledExceptionFilter(unhandledException);
testfun1();
testfun2();
testfun3();
errorFun();
return 0;
}
编译运行。。dump 文件的生成一定要在外面独立运行 exe,不要在 VS 里面执行,因为 VS 会在 crash 的地方中断
step 1
用 windbg 工具打开 dump 文件。。我用的是 windbg preview
点击红框即可,windbg 会自动帮你输入命令进行分析
crash 的地址以及出现了。就会得到 0x000012ed。。记住这个值
step2
打开 .map 文件
会是一堆这样的信息。。具体含义我就不介绍了,可以玩网上搜,有介绍。。往下拉
这四个东西是我们需要相关注的。。
Address 基地址
publics by value 就是函数名
Rva+Base 经过计算后的地址
Lib:Object 文件名
这时候就需要拿第一步得到的地址 0x000012ed 和 Rva+Base 进行比较。。判断位于哪两个地址之间
就会发现 crash 的地方是在 main 函数。。
这时候你肯定会疑惑,明明 crash 的地方是在 errorfun 函数里面。但是不是在 main 里面调用的 errorfun()
说实话。。这块按理来说 map 文件中会存储调用函数的地址的,就可以很准确的找到是 errorfun 函数的问题。我做的项目就有,不知道这个测试例子为啥没有。。
但这并不影响,接着往下看
我们第二步已经精确定位到函数了。接下来就是行数了
step3
再回到 windbg 工具中
拿到这个地址,然后去反汇编里面搜索
复制后面的汇编语句。打开 cod 文件,进行搜索
阿西吧!!!!!
大功告成。。找到了具体的位置。。
有人说,,为什么不直接在 cod 中搜索,要第二步干什么。。。
如果你是在做一个大项目的话,汇编语句肯定会有重复的,所以说一定要先定位到具体的函数,再去找具体的行数
附
如果你有 pdb 文件的话,就会轻松很多很多