现在的位置: 首页 > 综合 > 正文

栈回溯(stack trace)原理

2013年09月14日 ⁄ 综合 ⁄ 共 2459字 ⁄ 字号 评论关闭

当使用windbg时,最常用的命令就是'K', 栈回溯。那么是怎么实现栈回溯的呢,下面简单介绍一下。

首先要了解我们所编译出来的EXE或者DLL的调试信息都是包含在PDB文件中的,PDB文件可由编译器来产生。在栈回溯中使用的API都是来自动态库DbgHelp.dll中的,当然你可以显式或者隐式的调用这个DLL,但是这个DLL有着不同的版本,当隐式调用时需要注意。因为我在使用时出现了问题,所以我这里详细介绍一下。当我直接把DbgHelp.dll, DbgHlep.lib和DbgHelp.h直接从windbg目录拷贝到我的工程目录下时,直接编译会出现问题,原因是有些宏和变量没有被定义。当我从前辈的源码中拷贝的这些文件却可以顺利的编译过去。对比一下这些文件,发现很多的不同,是版本的原因造成的。而显式调用却不会存在这些问题,所以我建议显式调用DbgHelp.dll中的文件。

DbgHelp.dll中函数都是以Sym*开头的,为了获得这些调试信息,我们首先要为当前进程初始化符号句柄,利用函数

bool bSuccess = SymInitialize(hProcess, csPath, false)

其中第一个参数就是指当前运行进程的句柄了,第二个参数用分号分隔的几个路径,用来指定那些路径用来搜索符号文件。第三个参数用来表示符号句柄是否为所有的模块自动导入符号文件,这里一般设为false。

接下需要对导入的符号文件进行配置,能够使用的函数是DWORD SymGetOptions()和 DWORD SymSetOptions(DWORD SymOptions), 具体的option可以参考http://msdn.microsoft.com/zh-cn/library/ff558827.aspx,但我们一般包括SYMOPT_LOAD_LINES,里面要包含代码的行的信息。

接下来可以通过

BOOL WINAPI SymGetSearchPath(   __in   HANDLE hProcess,   __out  PTSTR SearchPath,   __in   DWORD SearchPathLength ); 来得到符号文件的搜索路径。在这里我也有个疑问,因为在使用函数SymInitialize()时的第二个参数是用来设置符号文件的搜索路径的,这两个路径是否是一回事呢?从我目前所观察到的是一样的。
下面介绍一下一个重要的结构体 STACKFRAME64,它的定义如下
typedef struct _tagSTACKFRAME64 {
  ADDRESS64 AddrPC;
  ADDRESS64 AddrReturn;
  ADDRESS64 AddrFrame;
  ADDRESS64 AddrStack;
  ADDRESS64 AddrBStore;
  PVOID     FuncTableEntry;
  DWORD64   Params[4];
  BOOL      Far;
  BOOL      Virtual;
  DWORD64   Reserved[3];
  KDHELP64  KdHelp;
} STACKFRAME64, *LPSTACKFRAME64;
它代表的是一个栈桢, 能得到获得栈信息的地址。在使用时需要进行初始化,过程如下
STACKFRAME64 stack={0};

CONTEXT context;
 context.ContextFlags = CONTEXT_FULL;
 GetThreadContext(hThread, &context);

 // Must be like this
 stack.AddrPC.Offset = context.Eip; // EIP - Instruction Pointer
 stack.AddrPC.Mode = AddrModeFlat;
 stack.AddrFrame.Offset = context.Ebp; // EBP
 stack.AddrFrame.Mode = AddrModeFlat;
 stack.AddrStack.Offset = context.Esp; // ESP - Stack Pointer
 stack.AddrStack.Mode = AddrModeFlat;

初始化这个结构主要用于函数StackWalk64. StackWalk64 就是获得栈回溯的,它的定义如下

StackWalk64

BOOL WINAPI 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.dll中的函数SymGetModuleBase64与SymFunctionTableAccess64。 倒数第四个ReadMemoryRoutine ,可以自己定义一个函数,主要调用ReadProcessMemory。这个函数主要得到STACKFRAME64这个栈帧。

然后通过栈帧的成员AddrPC的offset,可由函数SymGetSymFromAddr64得到符号信息,再通过UnDecorateSymbolName(),SymGetLineFromAddr64()()等函数得到具体的函数名,代码行等信息。

大致的栈回溯就是这个过程。

抱歉!评论已关闭.