Contents
  1. 1. C#
  2. 2. C++

在上一篇博客调试内存泄漏问题的一些经验中提到过通过gflags设置“Create user mode stack trace database”可以存储生成对象的调用栈信息(call stack trace),这样以后可以很方便的看到每个泄漏的对象是怎么产生的。

假设我们不用gflags的这个设置,自己在代码中得到栈回溯信息,怎么做呢?

C#

C#实现这个功能很简单,直接使用System.Diagnostics.StackTrace就行了,唯一要注意的是在创建StackTrace时传true作为参数。示例代码如下:

1
2
var stackTrace = new StackTrace(true); // true means capturing source information
Console.WriteLine(stackTrace);

C++

C++中获取栈回溯信息需要使用CaptureStackBackTrace

如果只是在Visual Studio中看的话,Visual Studio 2013已经能自动把指令地址转换成函数名,源代码行数显示在Watch窗口中了。也可以写Natvis来使它显示的更加友好。关于Natvis的使用方法,可以参加我的博客用Natvis定制C++对象在Visual Studio调试时如何显示

如果我们想自己得到函数名和源代码行数,相对来说就有点麻烦了,需要使用DbgHelp中的下面几个API:

  1. SymSetOptions:设置Symbol选项,我们需要设置SYMOPT_LOAD_LINES来获取源文件行数信息。
  2. SymInitialize:初始化Symbol,它的构造函数有3个参数。第一个是进程句柄。第二个是Symbol Path,如果这个参数为NULL,那会从当前工作目录,环境变量的_NT_SYMBOL_PATH_NT_ALTERNATE_SYMBOL_PATH中加载Symbo。第三个参数是fInvadeProcess,如果这个参数是TURE会对所有module自动调用SymLoadModule64.aspx)。
  3. SymCleanup:释放所有资源。
  4. SymFromAddr:针对给定地址查询Symbol信息。
  5. SymGetLineFromAddr64:针对给定地址查询源代码行数。
    注意DbgHelp的所有函数都是单线程的,所以最好在进程启动的时候调用SymInitialize,进程退出的时候调用SymCleanup。其它所有函数调用都要做好同步。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#pragma comment(lib, "dbghelp.lib")
#include <windows.h>
#include <DbgHelp.h>
#include <iostream>

class StackTrace {
private:
static const int m_MaxFrameCount = 62;
int m_FrameCount;
void* m_Frames[m_MaxFrameCount];
public:
StackTrace() {
m_FrameCount = CaptureStackBackTrace(1, m_MaxFrameCount, m_Frames, NULL);
}
void Print()
{
HANDLE process = GetCurrentProcess();

SymSetOptions(SYMOPT_LOAD_LINES); // set symbol option to load the source code lines
SymInitialize(process, NULL, TRUE); // init symbol

char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; // define the PSYMBOL_INFO
PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = MAX_SYM_NAME;

IMAGEHLP_LINE64 line; // define the IMAGEHLP_LINE64
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);

for (int i = 0; i < m_FrameCount; i++)
{
DWORD64 dw64Displacement;
SymFromAddr(process, (DWORD64)(m_Frames[i]), &dw64Displacement, symbol); // get symbol info
std::cout << symbol->Name;
DWORD dwDisplacement;
if (SymGetLineFromAddr64(process, (DWORD64)(m_Frames[i]), &dwDisplacement, &line)) // get line info
{
std::cout << symbol->Name << ", " << line.FileName << ", line " << line.LineNumber;
}
std::cout << std::endl;
}

SymCleanup(process);
}
};

会打印如下的输出:

1
2
3
4
5
6
7
8
MyObject::DosomethingMyObject::Dosomething, d:\documents\visual studio 2013\projects\demo\cppdemo\cppdemo.cpp, line 69
wmainwmain, d:\documents\visual studio 2013\projects\demo\cppdemo\cppdemo.cpp, line 147
__tmainCRTStartup__tmainCRTStartup, f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c,line 623
wmainCRTStartupwmainCRTStartup, f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c, line 466
BaseThreadInitThunk
RtlInitializeExceptionChain
RtlInitializeExceptionChain
Press any key to continue . . .

Contents
  1. 1. C#
  2. 2. C++