Bootstrap

利用微软dbghelp.dll 及符号文件获得线程调用堆栈

什么是PDB 文件?

通常我们在生成PE 文件的时候都会随带生成一个对应的pdb 文件,这个文件的含义是什么呢?我们在msdn上找到如下解释

https://msdn.microsoft.com/zh-cn/library/aa292304(v=vs.71).aspx

PDB(程序数据库 )文件保存着调试和项目状态信息,使用这些信息可以对程序的调试配置进行增量链接。
如果使用生成文件创建 C/C++ 应用程序,并指定 /ZI 或 /Zi 而不指定 /Fd 时,则最终将生成两个 PDB 文件
VCx0.PDB(其中 x 表示 Visual C++ 的版本。如vs2010 对应的pdb 文件为vc100.pdb)该文件存储各个 OBJ 文件的所有调试信息并与项目生成文件驻留在同一个目录中(通常是对应的 “项目名\Debug或者Release” 目录)。
project.PDB   该文件存储 .exe 文件的所有调试信息。对于本机代码,它驻留在 \debug 子目录中(与exe同一目录)。对于托管代码,它驻留在 \WINDEBUG 子目录中.

PDB的内容

PDB 包含了如下的信息:

  1. public private 和 static 函数地址
  2. 全局变量的名字和地址
  3. 参数和局部变量的名字和在堆栈中的偏移量
  4. class、struct 和数据的类型定义
  5. Frame Pointer Omission 数据,用来在X86 的native 堆栈的遍历
  6. 源代码文件的名字和行数

通过上面罗列的这些信息,我们就可以实现平时调试时所见到的程序执行与源代码对应的功能。

PDB 的使用

Windows 提供了两种方法来访问调试符号,分别是DbgHelp(Debug Help Library) 和 DIA(Debug Interface Access)。DIA 是基于COM 的,这里我们介绍的是DbgHelp。

利用DbgHelp 获得函数调用堆栈

使用DbgHelp 的程序需要加载DbgHelp.dll 这个动态的库,Windows 自带的这个文件,位于C:\Windows\System32 下。如果我们的计算机上带有编译器,通常每一款编译器都带有DbgHelp.dll

image

为了在程序中使用DbgHelp,需要dbghelp.lib,在需要使用DbgHelp的源文件中,包含Windows.h和DbgHelp.h头文件即可。(Windows.h需要包含在DbgHelp.h的前面)------------或者直接动态加载。

有一点需要注意,DbgHelp使用DBGHELP_TRANSLATE_TCHAR这个预定义标记来决定是否使用Unicode字符串,而不是UNICODE标记。所以,如果你的程序使用Unicode字符串,那就定位到“配置属性”-“C/C++”-“预处理器”,在右边的“预处理器定义”中添加DBGHELP_TRANSLATE_TCHAR。

初始化SymbolHandler(符号处理器)

进程的每个模块对应一个符号文件, 有关符号文件的信息保存在模块的可执行文件中。DbgHelp 通过SymbolHandler 来处理模块的符号文件。SymbolHandler 位于调试器进程中,其实不一定是调试器进程,任何想要使用符号文件的进程都需要SymbolHandler 。创建SymbolHandler  的函数为:

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

第一个参数是被调试进程的句柄,它是符号管理器的标识符,其它的DbgHelp函数都需要这样一个参数值指明使用哪个符号管理器。实际上这个参数不一定是句柄:当fInvadeProcess参数为TRUE时,它必须是一个有效的进程句柄;当fInvadeProcess为FALSE时,它可以是任意一个唯一的数值(只要能唯一标志符号管理器的标识符接口,但是通常我们传入进程句柄)。

fInvadeProcess的作用是指示是否加载进程所有模块的调试符号,如果该参数为FALSE,那么SymInitialize只是创建一个符号处理器,不加载任何模块的调试符号,我们这里就是用的这种方法。此时需要我们自己调用SymLoadModule64函数来加载模块;如果为TRUE,SymInitialize会遍历进程的所有模块,并加载其调试符号,所以在这种情况下hProcess必须是一个有效的进程句柄。

加载符号

DWORD64
IMAGEAPI
SymLoadModule64(
    _In_ HANDLE hProcess,
    _In_opt_ HANDLE hFile,
    _In_opt_ PCSTR ImageName,
    _In_opt_ PCSTR ModuleName,
    _In_ DWORD64 BaseOfDll,
    _In_ DWORD SizeOfDll
    );

1.    hProces,符号处理器的标识符,同SysInitialize。

2.    hFile 是模块文件的句柄,这里我们直接传空。

3.    ImageName用于指定模块文件的路径和名称,当第二个参数为NULL时,SymLoadModule64会通过这里指定的路径和名称去寻找模块文件。一般情况下都不会使用这个参数,因为我们可以使用更可靠的hFile参数。

4.    ModuleName为该模块赋予一个名称,在使用其它DbgHelp函数的时候可以通过这个名称来引用模块。如果该参数为NULL,SymLoadModule64会使用符号文件的文件名作为模块名称。

5.    BaseOfDll是模块加载到进程地址空间之后的基地址。

6.    SizeOfDll,表示模块文件的大小。

上面所需要的信息均可以通过TooHelp 函数或者Psapi 等枚举进程模块的方法得到。

如何判断某个模块是否含有调试信息?或者,如何知道某个模块的符号文件使用哪种格式?

查看模块是否包含符号

BOOL
IMAGEAPI
SymGetModuleInfoW64(
     _In_ HANDLE hProcess,
    _In_ DWORD64 qwAddr,
    _Out_ PIMAGEHLP_MODULEW64 ModuleInfo
    );

第三个参数指向IMAGEHLP_MODULE64 结构体的指针,调用函数完成之后模块的信息将会保存到这个结构体中。

typedef struct _IMAGEHLP_MODULEW64 {

DWORD    SizeOfStruct;           // set to sizeof(IMAGEHLP_MODULE64)

    SYM_TYPE SymType;                // type of symbols loaded
   …
} IMAGEHLP_MODULEW64, *PIMAGEHLP_MODULEW64;

其中的SymType 指示模块使用的是哪种格式的符号文件,可能如下:

SymCoffCOFF格式
SymCvCodeView 格式
SymDeferred调试符号是延迟加载的
SymDiaDIA 格式
SymExport符号是从DLL 文件的导出表中生成的
SymNone没有调试符号
SymPdbPDB 格式
SymSym使用.sym 类型的符号文件
SymVirtual

栈回溯

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

一切的一切都是为了上面的这个函数,DbgHelp 中主要使用StackWalk64函数来进行栈回溯,其实栈回溯的原理我们之前的两个文章已经讲明了。之所以写这篇文章是因为,DbgHelp 能够将获得的调用栈的结果结合符号文件(如果有的话)进行比较人性化的显示,比如我们比较喜欢的,模块名!函数名!偏移值。而不仅仅是一个地址(其实可以做到模块+函数(没有名称,只有地址)+偏移值)。

我们来看参数

1.   MachineType

IMAGE_FILE_MACHINE_I386X86
IMAGE_FILE_MACHINE_IA64IA64
IMAGE_FILE_MACHINE_AMD64X64

2、3 hProcess,hThread

hProcess 必须是符号处理器的标识符。hThread 为线程句柄

4.    StackFrame

typedef struct _tagSTACKFRAME64 {
    ADDRESS64   AddrPC;               // program counter
    ADDRESS64   AddrReturn;           // return address
    ADDRESS64   AddrFrame;            // frame pointer
    ADDRESS64   AddrStack;            // stack pointer
    ADDRESS64   AddrBStore;           // backing store pointer
    PVOID       FuncTableEntry;       // pointer to pdata/fpo or NULL
    DWORD64     Params[4];            // possible arguments to the function
    BOOL        Far;                  // WOW far call
    BOOL        Virtual;              // is this a virtual frame?
    DWORD64     Reserved[3];
    KDHELP64    KdHelp;
} STACKFRAME64, *LPSTACKFRAME64;

调用StackWalk64 之前初始化这个结构体(AddrPC 即EIP,AddrStack 即 ESP ,AddrFrame 即 EBP),调用成功后,前一个栈帧的信息保存到该结构中。循环调用StackWalk64函数即可实现栈回溯。

5.    ContextRecord 指向CONTEXT 结构体的指针,必须使用GetThreadContext初始化,StackWalk64 函数会使用该参数,并进行修改。

6.    ReadMemoryRoutine 是一个回调函数的指针,当StackWalk64 函数需要读取目标进程的内存的时候使用。如果不想提供,设为NULL,此时hProcess 必须为有效的进程句柄。

7.    FunctionTableAccessRoutine 回调函数指针,此函数不为空,一般设置为SymFunctionTableAccess64 函数,此时hProcess 必须是符号处理器的标识符。

8.    GetModuleBaseRoutine 为回调函数指针,用于获取模块的基地址,设为GetModuleBase64 即可,此时hProcess 必须是符号处理器的标识符。

9.    TranslateAddress 回调函数指针,设为NULL即可。

我们可以实现自己的回调函数,这样hProcess就没有这么大的限制,否则hProcess 必须为对方的进程句柄。

获得函数名称

调用SymFromAddr 获取函数的信息

获得模块名称

SymGetModuleInfo64 即可

清理调试符号

在被调试进程结束的时候必须删除与之对应的符号处理器,以及清理它占用的资源。SymCleanup函数就可以完成这个操作,该函数接受一个符号处理器的标识符。

代码实现

#include "StackWalk.h"
#include <stdio.h>
void DebugShow(DWORD dwProcessId)
{
	
	HANDLE hProcess = OpenProcess(
		PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
		FALSE, dwProcessId);

	if (hProcess == NULL)
	{
		printf("OpenProcess With PROCESS_QUERY_INFORMATION | PROCESS_VM_READ error\r\n");
		return;
	}
	StackWalker MyCallStack(NULL,dwProcessId, hProcess);
	if (MyCallStack.GetSymAndInitSym() == FALSE || MyCallStack.LoadModules() == FALSE)
	{
		return;
	}
	MyCallStack.ChooseThreadAndShow();
}
int main()
{
	DWORD	dwProcessId = 0;
	scanf("%d", &dwProcessId);
	DebugShow(dwProcessId);
	getchar();
	getchar();
	return 0;
}
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <DbgHelp.h>
#pragma  comment(lib,"dbghelp.lib")
class StackWalker
{
#define USED_CONTEXT_FLAGS CONTEXT_FULL
#define  STACKWALK_MAX_NAMELEN  1024
public:
	BOOL InitSym(LPCSTR szSymPath);
	BOOL GetSymAndInitSym();
	StackWalker() {};
	StackWalker(
		LPCSTR szSymPath = NULL,
		DWORD dwProcessId = GetCurrentProcessId(),
		HANDLE hProcess = GetCurrentProcess()
	);
	~StackWalker();
	BOOL ChooseThreadAndShow();
	DWORD LoadModule(HANDLE hProcess, LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size);
	BOOL LoadModules();
	BOOL ShowCallStack(HANDLE hProcess, DWORD dwThreadId);
	typedef struct CallstackEntry
	{
		DWORD64 offset;  // if 0, we have no valid entry
		CHAR FuncName[STACKWALK_MAX_NAMELEN];
		DWORD64 offsetFromSmybol;
		CHAR moduleName[STACKWALK_MAX_NAMELEN];
		DWORD64 baseOfImage;
		CHAR loadedImageName[STACKWALK_MAX_NAMELEN];
		CHAR ProcessImageName[MAX_PATH];
		DWORD64	ProcessEntryPoint;
	} CallstackEntry;
	static BOOL __stdcall myReadProcMem(
		HANDLE      hProcess,
		DWORD64     qwBaseAddress,
		PVOID       lpBuffer,
		DWORD       nSize,
		LPDWORD     lpNumberOfBytesRead
	);

	void ShowMessage(CallstackEntry cs);
protected:
	HANDLE	m_hProcess;
	DWORD	m_dwProcessId;
	BOOL	m_modulesLoaded;
	LPSTR	m_szSymPath;
	DWORD	m_dwThreadId;
};

#if defined(_M_IX86)
#ifdef CURRENT_THREAD_VIA_EXCEPTION
// TODO: 下面使用异常的方式得到堆栈
#define GET_CURRENT_CONTEXT(c, contextFlags) \
  do { \
    memset(&c, 0, sizeof(CONTEXT)); \
    EXCEPTION_POINTERS *pExp = NULL; \
    __try { \
      throw 0; \
    } __except( ( (pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER)) {} \
    if (pExp != NULL) \
      memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \
      c.ContextFlags = contextFlags; \
  } while(0);
#else
// 得到当前线程的线程上下文
#define GET_CURRENT_CONTEXT(c, contextFlags) \
  do { \
    memset(&c, 0, sizeof(CONTEXT)); \
    c.ContextFlags = contextFlags; \
    __asm    call x \
    __asm x: pop eax \
    __asm    mov c.Eip, eax \
    __asm    mov c.Ebp, ebp \
    __asm    mov c.Esp, esp \
  } while(0);
#endif

#else

// The following is defined for x86 (XP and higher), x64 and IA64:
#define GET_CURRENT_CONTEXT(c, contextFlags) \
  do { \
    memset(&c, 0, sizeof(CONTEXT)); \
    c.ContextFlags = contextFlags; \
    RtlCaptureContext(&c); \
} while(0);
#endif
  
#include "StackWalk.h"
#include <tchar.h>
#include <stdio.h>
#include <TlHelp32.h>
/**
 * 当SymInitialize 第三个参数为FALSE,手动调用SymLoadModule64 函数加载模块,此时第一个参数为任意非唯一的数值,用于标志符号
 * 否则 第一个 hProcess 必须为进程句柄,自动加载进程的所有模块的调试符号,SymInitialize 使用UserSearchPath 指定的路径找符号文件
 * 多个路径以分号(;)分割
 */
BOOL StackWalker::InitSym(LPCSTR szSymPath)
{
	// SymInitialize
	// 符号文件初始化
	if (szSymPath != NULL)
		m_szSymPath = _strdup(szSymPath);
	if (SymInitialize(m_hProcess, m_szSymPath, FALSE) == FALSE)
	{
		printf("m_szSymPath:%s\r\nGetLastError:%d\r\n", m_szSymPath, GetLastError());
		return FALSE;
	}
	return TRUE;
}
/**
 * 得到符号并初始化符号
 */
BOOL StackWalker::GetSymAndInitSym()
{
	if (m_modulesLoaded != FALSE)
		return TRUE;

	// 建立符号路径
	char *szSymPath = NULL;
	const size_t nSymPathLen = 4096;
	szSymPath = (char*)malloc(nSymPathLen);
	if (szSymPath == NULL)
	{
		SetLastError(ERROR_NOT_ENOUGH_MEMORY);
		return FALSE;
	}
	szSymPath[0] = 0;
	// 首先将用户提供的符号路径添加进来
	if (this->m_szSymPath != NULL)
	{
		strcat_s(szSymPath, nSymPathLen, this->m_szSymPath);
		strcat_s(szSymPath, nSymPathLen, ";");// 路径之间用";" 加以分割
	}

	strcat_s(szSymPath, nSymPathLen, ".;");

	const size_t nTempLen = 1024;
	char szTemp[nTempLen];
	// 得到当前路径
	if (GetCurrentDirectoryA(nTempLen, szTemp) > 0)
	{
		szTemp[nTempLen - 1] = 0;
		strcat_s(szSymPath, nSymPathLen, szTemp);
		strcat_s(szSymPath, nSymPathLen, ";");
	}

	// 得到主模块的路径
	if (GetModuleFileNameA(NULL, szTemp, nTempLen) > 0)
	{
		szTemp[nTempLen - 1] = 0;
		for (char *p = (szTemp + strlen(szTemp) - 1); p >= szTemp; --p)
		{
			// 得到最右路径
			if ((*p == '\\') || (*p == '/') || (*p == ':'))
			{
				*p = 0;
				break;
			}
		}  // for (search for path separator...)
		if (strlen(szTemp) > 0)
		{
			strcat_s(szSymPath, nSymPathLen, szTemp);
			strcat_s(szSymPath, nSymPathLen, ";");
		}
	}
	// 得到环境变量路径 系统符号路径
	if (GetEnvironmentVariableA("_NT_SYMBOL_PATH", szTemp, nTempLen) > 0)
	{
		szTemp[nTempLen - 1] = 0;
		strcat_s(szSymPath, nSymPathLen, szTemp);
		strcat_s(szSymPath, nSymPathLen, ";");
	}
	// 得到 NT 交替符号路径
	if (GetEnvironmentVariableA("_NT_ALTERNATE_SYMBOL_PATH", szTemp, nTempLen) > 0)
	{
		szTemp[nTempLen - 1] = 0;
		strcat_s(szSymPath, nSymPathLen, szTemp);
		strcat_s(szSymPath, nSymPathLen, ";");
	}
	// 得到系统根路径 及其 \\system32 子路径
	if (GetEnvironmentVariableA("SYSTEMROOT", szTemp, nTempLen) > 0)
	{
		szTemp[nTempLen - 1] = 0;
		strcat_s(szSymPath, nSymPathLen, szTemp);
		strcat_s(szSymPath, nSymPathLen, ";");
		// also add the "system32"-directory:
		strcat_s(szTemp, nTempLen, "\\system32");
		strcat_s(szSymPath, nSymPathLen, szTemp);
		strcat_s(szSymPath, nSymPathLen, ";");
	}

	// 得到 系统驱动 环境变量
	if (GetEnvironmentVariableA("SYSTEMDRIVE", szTemp, nTempLen) > 0)
	{
		szTemp[nTempLen - 1] = 0;
		strcat_s(szSymPath, nSymPathLen, "SRV*");
		strcat_s(szSymPath, nSymPathLen, szTemp);
		strcat_s(szSymPath, nSymPathLen, "\\websymbols");
		strcat_s(szSymPath, nSymPathLen, "*http://msdl.microsoft.com/download/symbols;");
	}
	else	// 最后加入微软的官方在线符号表
		strcat_s(szSymPath, nSymPathLen, "SRV*c:\\websymbols*http://msdl.microsoft.com/download/symbols;");

	if (!InitSym(szSymPath))
	{
		return FALSE;
	}

	if (szSymPath != NULL) free(szSymPath); szSymPath = NULL;
	return TRUE;
}

StackWalker::StackWalker(LPCSTR szSymPath, DWORD dwProcessId, HANDLE hProcess)
{
	this->m_modulesLoaded = FALSE;
	this->m_hProcess = hProcess;
	this->m_dwProcessId = dwProcessId;
	this->m_dwThreadId = 0;
	if (szSymPath != NULL)
	{
		this->m_szSymPath = _strdup(szSymPath);
	}
	else
		this->m_szSymPath = NULL;
}

StackWalker::~StackWalker()
{
	if (m_szSymPath != NULL)
		free(m_szSymPath);
	m_szSymPath = NULL;
}

BOOL StackWalker::ChooseThreadAndShow()
{
	DWORD	Threads[100];
	int		i = 0;
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPALL, m_dwProcessId);
	if (hSnap == INVALID_HANDLE_VALUE)
		return FALSE;
	THREADENTRY32 te;
	memset(&te, 0, sizeof(te));
	te.dwSize = sizeof(te);
	if (Thread32First(hSnap, &te) == FALSE)
	{
		CloseHandle(hSnap);
		return FALSE;
	}
	do
	{
		if (te.th32OwnerProcessID != m_dwProcessId)
			continue;
		HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
		if (hThread == NULL)
			continue;
		
		printf("%d:\tThreadID: %d\r\n", i,te.th32ThreadID);
		Threads[i] = te.th32ThreadID;
		CloseHandle(hThread);

		i++;
	} while (Thread32Next(hSnap, &te) != FALSE);

	int iIndex = -1;
	while (iIndex >= i || iIndex < 0)
	{
		printf("input index\r\n");
		scanf("%d", &iIndex);
	}
	m_dwThreadId = Threads[iIndex];

	ShowCallStack(m_hProcess, m_dwThreadId);

	return TRUE;
}
/**
 * 加载模块对应的符号
 */
DWORD StackWalker::LoadModule(HANDLE hProcess, LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size)
{
	CHAR *szImg = _strdup(img);
	CHAR *szMod = _strdup(mod);
	DWORD result = ERROR_SUCCESS;
	if ((szImg == NULL) || (szMod == NULL))
		result = ERROR_NOT_ENOUGH_MEMORY;
	else
	{
		if (SymLoadModule64(hProcess, 0, szImg, szMod, baseAddr, size) == 0)
			result = GetLastError();
	}
	if (szImg != NULL) free(szImg);
	if (szMod != NULL) free(szMod);
	return result;
}
/**
 * 枚举进程模块并为每个模块加载符号
 */
BOOL StackWalker::LoadModules()
{
	DWORD	Threads[50];
	int		i = 0;
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, m_dwProcessId);
	if (hSnap == INVALID_HANDLE_VALUE)
		return FALSE;
	MODULEENTRY32 me;
	me.dwSize = sizeof(me);
	if (Module32First(hSnap, &me) == FALSE)
	{
		CloseHandle(hSnap);
		return FALSE;
	}
	do
	{
		LoadModule(m_hProcess, me.szExePath, me.szModule, (DWORD64)me.modBaseAddr, me.modBaseSize);
	} while (Module32Next(hSnap, &me) != FALSE);
	m_modulesLoaded = TRUE;
	return TRUE;
}

BOOL StackWalker::ShowCallStack(HANDLE hProcess, DWORD dwThreadId)
{
	CONTEXT c;
	memset(&c, 0, sizeof(c));
	IMAGEHLP_SYMBOL64 *pSym = NULL;
	int frameNum;

	if (m_modulesLoaded == FALSE)
		this->LoadModules();
	HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwThreadId);

	if (dwThreadId == GetCurrentThreadId())
	{
		GET_CURRENT_CONTEXT(c, USED_CONTEXT_FLAGS);
	}
	else
	{
		SuspendThread(hThread);
		c.ContextFlags = USED_CONTEXT_FLAGS;
		if (GetThreadContext(hThread, &c) == FALSE)
		{
			ResumeThread(hThread);
			hThread = 0;
			return FALSE;
		}
	}

	// init STACKFRAME for first call
	// 为第一次调用 初始化 STACKFRAME64 结构
	/**
	* 应该做的初始化:
	* 1.	AddrPC 当前指令指针(Eip in X86,Rip in X64,StIIP in IA 64)
	* 2.	AddrStack	当前堆栈指针(Esp,Rsp,IntSp)
	* 3.	AddrFrame	当前帧指针有意义时(Ebg,Rsp(但是不适用,VC2005B2,Rdi),RsBSP),StackWalk64将在不需要展开时忽略该值
	* 4. 设置 AddrBStore 到 RsBSP	IA64
	*/
	STACKFRAME64 s; // in/out stackframe
	memset(&s, 0, sizeof(s));
	DWORD imageType;
#ifdef _M_IX86
	imageType = IMAGE_FILE_MACHINE_I386;
	s.AddrPC.Offset = c.Eip;
	s.AddrPC.Mode = AddrModeFlat;
	s.AddrFrame.Offset = c.Ebp;
	s.AddrFrame.Mode = AddrModeFlat;
	s.AddrStack.Offset = c.Esp;
	s.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
	imageType = IMAGE_FILE_MACHINE_AMD64;
	s.AddrPC.Offset = c.Rip;
	s.AddrPC.Mode = AddrModeFlat;
	s.AddrFrame.Offset = c.Rsp;
	s.AddrFrame.Mode = AddrModeFlat;
	s.AddrStack.Offset = c.Rsp;
	s.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
	imageType = IMAGE_FILE_MACHINE_IA64;
	s.AddrPC.Offset = c.StIIP;
	s.AddrPC.Mode = AddrModeFlat;
	s.AddrFrame.Offset = c.IntSp;
	s.AddrFrame.Mode = AddrModeFlat;
	s.AddrBStore.Offset = c.RsBSP;
	s.AddrBStore.Mode = AddrModeFlat;
	s.AddrStack.Offset = c.IntSp;
	s.AddrStack.Mode = AddrModeFlat;
#else
#error "Platform not supported!"
#endif

	pSym = (IMAGEHLP_SYMBOL64 *)malloc(sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN);
	if (!pSym) goto cleanup;  // 内存不够
	memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN);
	pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
	pSym->MaxNameLength = STACKWALK_MAX_NAMELEN;
	IMAGEHLP_MODULE64 Module;
	memset(&Module, 0, sizeof(Module));
	Module.SizeOfStruct = sizeof(Module);

	for (frameNum = 0; ; ++frameNum)
	{
		// 取下一个栈帧(StackWalk64(), SymFunctionTableAccess64(), SymGetModuleBase64())
		// 函数的三个回调函数可以传入自己的回调函数,也可以传入DbgHelp.dll 自己的函数
		if (!StackWalk64(imageType, this->m_hProcess, hThread, &s, &c, myReadProcMem, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
		{
			//printf("StackWalk64\t%d\r\n", GetLastError());
			break;
		}
		CallstackEntry csEntry;
		csEntry.offset = s.AddrPC.Offset;// 这里的AddrPC(程序计数器) 就是EIP/RIP,即调用位置
		csEntry.FuncName[0] = 0;
		csEntry.offsetFromSmybol = 0;
		csEntry.loadedImageName[0] = 0;
		csEntry.moduleName[0] = 0;
		if (s.AddrPC.Offset != 0)
		{
			// 得到了合法的IP
			// 显示程序符号信息(SymGetSymFromAddr64())
			if (SymGetSymFromAddr64(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromSmybol), pSym) != FALSE)
			{
				strcpy(csEntry.FuncName, pSym->Name);
			}
			else
			{
				//printf("SymGetSymFromAddr64 Error:%d\r\n",GetLastError());
			}


			  // 得到模块信息 (SymGetModuleInfo64())
			if (SymGetModuleInfo64(this->m_hProcess, s.AddrPC.Offset, &Module) != FALSE)
			{
				strcpy_s(csEntry.moduleName, Module.ModuleName);
				csEntry.baseOfImage = Module.BaseOfImage;
				strcpy_s(csEntry.loadedImageName, Module.LoadedImageName);
			}
			else
			{
				//printf("SymGetModuleInfo64 Error:%d\r\n", GetLastError());
			}
		}

		ShowMessage(csEntry);
		if (s.AddrReturn.Offset == 0)
		{
			// 成功
			break;
		}
	}

cleanup:
	if (pSym) free(pSym);

	if (hThread != NULL && hThread != INVALID_HANDLE_VALUE)
		ResumeThread(hThread);

	return TRUE;
}

BOOL StackWalker::myReadProcMem(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead)
{
	SIZE_T st;
	BOOL bRet = ReadProcessMemory(hProcess, (LPVOID)qwBaseAddress, lpBuffer, nSize, &st);
	*lpNumberOfBytesRead = (DWORD)st;
	return bRet;
}

void StackWalker::ShowMessage(CallstackEntry entry)
{
	if (entry.FuncName[0] != 0)
	{
#ifndef _M_X64
		printf("call address: %8p  ", entry.offset - 5);
		printf("%s", entry.moduleName);
		printf("!%s", entry.FuncName);
		printf("+0x%8p\r\n", entry.offsetFromSmybol);
#else
		printf("call address:%p  %s!%s+0x%x\r\n", entry.offset-5,entry.moduleName, entry.FuncName, entry.offsetFromSmybol);
#endif
	}
	else
	{
#ifndef _M_X64
		printf("call address:%8p\t%s+0x%8p\r\n", entry.offset-5, entry.moduleName, entry.offset - entry.baseOfImage);
#else // !_M_X64
		printf("call address:%p\t%s+0x%x\r\n", entry.offset-5, entry.moduleName, entry.offset - entry.baseOfImage);
#endif
	}
}

有符号

image

无符号

image

参考链接

http://www.cnblogs.com/zplutor/archive/2011/03/20/1989783.html

https://www.codeproject.com/Articles/11132/Walking-the-callstack

http://www.cnblogs.com/itech/archive/2011/08/15/2136522.html

;