《Walking the callstack(转载)》
本文转载自:https://www.codeproject.com/articles/11132/walking-the-callstack

Introduction
In some cases you need to display the callstack of the current thread or your are just interested in the callstack of other threads / processes. Therefore I wrote this project.
The goal for this project was the following:
- Simple interface to generate a callstack
- C++ based to allow overwrites of several methods
- Hiding the implementation details (API) from the class' interface
- Support of x86, x64 and IA64 architecture
- Default output to debugger-output window (but can be customized)
- Support of user-provided read-memory-function
- Support of the widest range of development-IDEs (VC5-VC8)
- Most portable solution to walk the callstack
Background
To walk the callstack there is a documented interface: StackWalk64. Starting with Win9x/W2K, this interface is in the dbghelp.dll library (on NT, it is in imagehlp.dll). But the function name (StackWalk64) has changed starting with W2K (before it was called StackWalk (without the 64))! This project only supports the newer Xxx64-funtions. If you need to use it on older systems, you can download the redistributable for NT/W9x.
The latest dbghelp.dll can always be downloaded with the Debugging Tools for Windows. This also contains the symsrv.dll which enables the use of the public Microsoft symbols-server (can be used to retrieve debugging information for system-files; see below).
Using the code
The usage of the class is very simple. For example if you want to display the callstack of the current thread, just instantiate a StackWalk object and call the ShowCallstack member:
#include <windows.h>
#include "StackWalker.h" void Func5() { StackWalker sw; sw.ShowCallstack(); }
void Func4() { Func5(); }
void Func3() { Func4(); }
void Func2() { Func3(); }
void Func1() { Func2(); } int main()
{
Func1();
return 0;
}
This produces the following output in the debugger-output window:
[...] (output stripped)
d:\privat\Articles\stackwalker\stackwalker.cpp (736): StackWalker::ShowCallstack
d:\privat\Articles\stackwalker\main.cpp (4): Func5
d:\privat\Articles\stackwalker\main.cpp (5): Func4
d:\privat\Articles\stackwalker\main.cpp (6): Func3
d:\privat\Articles\stackwalker\main.cpp (7): Func2
d:\privat\Articles\stackwalker\main.cpp (8): Func1
d:\privat\Articles\stackwalker\main.cpp (13): main
f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c (259): mainCRTStartup
77E614C7 (kernel32): (filename not available): _BaseProcessStart@4
You can now double-click on a line and the IDE will automatically jump to the desired file/line.
Providing own output-mechanism
If you want to direct the output to a file or want to use some other output-mechanism, you simply need to derive from the StackWalker class. You have two options to do this: only overwrite the OnOutput method or overwrite each OnXxx-function. The first solution (OnOutput) is very easy and uses the default-implementation of the other OnXxx-functions (which should be enough for most of the cases). To output also to the console, you need to do the following:
class MyStackWalker : public StackWalker
{
public:
MyStackWalker() : StackWalker() {}
protected:
virtual void OnOutput(LPCSTR szText)
{ printf(szText); StackWalker::OnOutput(szText); }
};
Retrieving detailed callstack info
If you want detailed info about the callstack (like loaded-modules, addresses, errors, ...) you can overwrite the corresponding methods. The following methods are provided:
class StackWalker
{
protected:
virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size,
DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion);
virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry);
virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr);
};
These methods are called during the generation of the callstack.
Various kinds of callstacks
In the constructor of the class, you need to specify if you want to generate callstacks for the current process or for another process. The following constructors are available:
class StackWalker
{
public:
StackWalker(
int options = OptionsAll,
LPCSTR szSymPath = NULL,
DWORD dwProcessId = GetCurrentProcessId(),
HANDLE hProcess = GetCurrentProcess()
);
// Just for other processes with
// default-values for options and symPath
StackWalker(
DWORD dwProcessId,
HANDLE hProcess
);
};
To do the actual stack-walking you need to call the following functions:
class StackWalker
{
public:
BOOL ShowCallstack(
HANDLE hThread = GetCurrentThread(),
CONTEXT *context = NULL,
PReadProcessMemoryRoutine readMemoryFunction = NULL,
LPVOID pUserData = NULL
);
};
Displaying the callstack of an exception
With this StackWalker you can also display the callstack inside an exception handler. You only need to write a filter-function which does the stack-walking:
// The exception filter function:
LONG WINAPI ExpFilter(EXCEPTION_POINTERS* pExp, DWORD dwExpCode)
{
StackWalker sw;
sw.ShowCallstack(GetCurrentThread(), pExp->ContextRecord);
return EXCEPTION_EXECUTE_HANDLER;
} // This is how to catch an exception:
__try
{
// do some ugly stuff...
}
__except (ExpFilter(GetExceptionInformation(), GetExceptionCode()))
{
}
Points of Interest
Context and callstack
To walk the callstack of a given thread, you need at least two facts:
- The context of the thread
The context is used to retrieve the current Instruction Pointer and the values for the Stack Pointer (SP) and sometimes the Frame Pointer (FP). The difference between SP and FP is in short: SP points to the latest address on the stack. FP is used to reference the arguments for a function. See also Difference Between Stack Pointer and Frame Pointer. But only the SP is essential for the processor. The FP is only used by the compiler. You can also disable the usage of FP (see: /Oy (Frame-Pointer Omission)).
- The callstack
The callstack is a memory-region which contains all the data/addresses of the callers. This data must be used to retrieve the callstack (as the name says). The most important part is: this data-region must not change until the stack-walking is finished! This is also the reason why the thread must be in the Suspendedstate to retrieve a valid callstack. If you want to do a stack-walking for the current thread, then you must not change the callstack memory after the addresses which are declared in the context record.
Initializing the STACKFRAME64-structure
To successfully walk the callstack with StackWalk64, you need to initialize the STACKFRAME64-structure with meaningful values. In the documentation of StackWalk64 there is only a small note about this structure:
- The first call to this function will fail if the
AddrPCandAddrFramemembers of theSTACKFRAME64structure passed in theStackFrameparameter are not initialized.
According to this documentation, most programs only initialize AddrPC and AddrFrame; and this had worked until the newest dbhhelp.dll (v5.6.3.7). Now, you also need to initialize AddrStack. After having some trouble with this (and other problems) I talked to the dbghelp-team and got the following answer (2005-08-02; my own comments are written in italics!):
AddrStackshould always be set to the stack pointer value for all platforms. You can certainly publish thatAddrStackshould be set. You're also welcome to say that new releases of dbghelp are now requiring this.- Given a current dbghelp, your code should:
- Always use
StackWalk64 - Always set
AddrPCto the current instruction pointer (Eipon x86,Ripon x64 andStIIPon IA64) - Always set
AddrStackto the current stack pointer (Espon x86,Rspon x64 andIntSpon IA64) - Set
AddrFrameto the current frame pointer when meaningful. On x86 this isEbp, on x64 you can useRbp(but is not used by VC2005B2; instead it usesRdi!) and on IA64 you can useRsBSP.StackWalk64will ignore the value when it isn't needed for unwinding. - Set
AddrBStoretoRsBSPfor IA64.
- Always use
Walking the callstack of the current thread
On x86 systems (prior to XP), there is no direct supported function to retrieve the context of the current thread. The recommended way is to throw an exception and catch it. Now you will have a valid context-record. The default way of capturing the context of the current thread is by doing some inline-assembler to retrieve EIP, ESPand EBP. If you want to use the documented way, then you need to define CURRENT_THREAD_VIA_EXCEPTIONfor the project. But you should be aware of the fact, that GET_CURRENT_CONTEXT is a macro which internally uses <CODE lang=mc++>__try __except. Your function must be able to contain these statements.
Starting with XP and on x64 and IA64 systems, there is a documented function to retrieve the context of the current thread: RtlCaptureContext.
To do a stack-walking of the current thread, you simply need to do:
StackWalker sw;
sw.ShowCallstack();
Walking the callstack of other threads in the same process
To walk the callstack of another thread inside the same process, you need to suspend the target thread (so the callstack will not change during the stack-walking). But you should be aware that suspending a thread in the same process might lead to dead-locks! (See: Why you never should call Suspend/TerminateThread (Part I, Part II, Part III))
If you have the handle to the thread, you can do the following to retrieve the callstack:
MyStackWalker sw;
sw.ShowCallstack(hThread);
For a complete sample to retrieve the callstack of another thread, you can take a look at the demo-project.
Walking the callstack of other threads in other processes
The approach is almost the same as for walking the callstack for the current process. You only need to provide the ProcessID and a handle to the process (hProcess). Then you also need to suspend the thread to do the stack-walking. A complete sample to retrieve the callstack of another process is in the demo-project.
Reusing the StackWalk instance
It is no problem to reuse the StackWalk instance, as long as you want to do the stack-walking for the same process. If you want to do a lot of stack-walking it is recommended to reuse the instance. The reason is simple: if you create a new instance, then the symbol-files must be re-loaded for each instance. And this is really time-consuming. Also it is not allowed to access the StackWalk functions from different threads (the dbghelp.dll is not thread-safe!). Therefore it makes no sense to create more than one instance...
Symbol-Search-Path
By default, a symbol-search path (SymBuildPath and SymUseSymSrv) is provided to the dbghelp.dll. This path contains the following directories:
- The optional provided
szSymPath. If this parameter is provided, the optionSymBuildPathis automatically set. Each path must be separated with a ";" - The current directory
- The directory of the EXE
- The environment variable
_NT_SYMBOL_PATH - The environment variable
_NT_ALTERNATE_SYMBOL_PATH - The environment variable
SYSTEMROOT - The environment variable
SYSTEMROOTappended with "\system32" - The public Microsoft symbol-server: SRV*%SYSTEMDRIVE%\websymbols*http://msdl.microsoft.com/download/symbols
Symbol-Server
If you want to use the public symbols for the OS-files from the Microsoft-Symbol-Server, you either need the Debugging Tools for Windows (then symsrv.dll and the latest dbghelp.dll will be found automatically) or you need to redistribute "dbghelp.dll" and "smysrv.dll" from this package!
Loading the modules and symbols
To successfully walk the callstack of a thread, dbghelp.dll requires that the modules are known by the library. Therefore you need to "register" each module of the process via SymLoadModule64. To accomplish this you need to enumerate the modules of the given process.
Starting with Win9x and W2K, it is possible to use the ToolHelp32-API. You need to make a snapshot (CreateToolhelp32Snapshot) of the process and then you can enumerate the modules via Module32Firstand Module32Next. Normally the ToolHelp functions are located in the kernel32.dll but on Win9x it is located in a separate DLL: tlhelp32.dll. Therefore we need to check the functions in both DLLs.
If you have NT4, then the ToolHelp32-API is not available. But in NT4 you can use the PSAPI. To enumerate all modules you need to call EnumProcessModules, but you only get the handles to the modules. To feed SymLoadModule64 you also need to query the ModuleBaseAddr, SizeOfImage (via GetModuleInformation), ModuleBaseName (via GetModuleBaseName) and ModuleFileName(Path) (via GetModuleFileNameEx).
dbghelp.dll
There are a couple of issues with dbghelp.dll.
- The first is, there are two "teams" at Microsoft which redistribute the dbghelp.dll. One team is the OS-team, the other is the Debugging-Tools-Team (I don't know the real names...). In general you can say: The dbghelp.dll provided with the Debugging Tools for Windows is the most recent version. One problem of this two teams is the different versioning of the dbghelp.dll. For example, for XP-SP1 the version is 5.1.2600.1106 dated 2002-08-29. The version 6.0.0017.0 which was redistributed from the debug-team is dated 2002-04-31. So there is at least a conflict in the date (the newer version is older). And it is even harder to decide which version is "better" (or has more functionality).
- Starting with Me/W2K, the dbghelp.dll-file in the system32 is protected by the System File Protection. So if you want to use a newer dbghelp.dll you need to redistribute the version from the Debugging Tools for Windows (put it in the same directory as your EXE). This leads to a problem on W2K if you want to walk the callstack for an app which was built using VC7 or later. The VC7 compiler generates a new PDB-format (called DIA). This PDB-format cannot be read with the dbghelp.dll which is installed with the OS. Therefore you will not get very useful callstacks (or at least with no debugging info like filename, line, function name, ...). To overcome this problem, you need to redistribute a newer dbghelp.dll.
- The dbghelp.dll version 6.5.3.7 has a bug or at least a documentation change of the
StackWalk64function. In the documentation you can read:The first call to this function will fail if the
AddrPCandAddrFramemembers of theSTACKFRAME64structure passed in theStackFrameparameter are not initialized.and
[The
ContextRecord] parameter is required only when theMachineTypeparameter is notIMAGE_FILE_MACHINE_I386.But this is not true anymore. Now the callstack on x86-systems cannot be retrieved if you pass
NULLasContextRecord. From my point of view this is a major documentation change. Now you either need to initialize theAddrStackas well, or provide a validContextRecordwhich contains theEIP,EBPandESPregisters! - See also comments in the Initializing the STACKFRAME64-structure chapter...
Options
To do some kind of modification of the behaviour, you can optionally specify some options. Here is the list of the available options:
Copy Codetypedef enum StackWalkOptions
{
// No addition info will be retrived
// (only the address is available)
RetrieveNone = 0, // Try to get the symbol-name
RetrieveSymbol = 1, // Try to get the line for this symbol
RetrieveLine = 2, // Try to retrieve the module-infos
RetrieveModuleInfo = 4, // Also retrieve the version for the DLL/EXE
RetrieveFileVersion = 8, // Contains all the abouve
RetrieveVerbose = 0xF, // Generate a "good" symbol-search-path
SymBuildPath = 0x10, // Also use the public Microsoft-Symbol-Server
SymUseSymSrv = 0x20, // Contains all the abouve "Sym"-options
SymAll = 0x30, // Contains all options (default)
OptionsAll = 0x3F
} StackWalkOptions;
Known issues
- NT/Win9x: This project only support the
StackWalk64function. If you need to use it on NT4/Win9x, you need to redistribute the dbghelp.dll for this platform. - Currently only supports ANSI-names in callbacks (of course, the project can be compiled with UNICODE...).
- To open a remote thread I used "
OpenThread" which is not available on NT4/W9x. To have an example of doing this in NT4/Win9x please refer to Remote Library. - Walking mixed-mode callstacks (managed/unmanaged) does only return the unmanaged functions.
History
- 2005-07-27
- First public release.
- Supports x86, x64 and IA64.
- 2005-07-28
- Changed the description, so it does not make the impression that this is the only documented way to walk the callstack...
ShowCallstack(hThread, ...)now accepts aNULLCONTEXT(this forces to capture the context of this thread); it also was simplified (now only one function for all situations).- Added chapters: Symbol-Search-Path, Loading the modules and symbols and dbghelp.dll.
- 2005-08-01
- Added VC7.0 project/solution files.
- Added more comment to the
GET_CURRENT_CONTEXTdefine regarding the use ofRtlCaptureContextfunction. - Problem with uninitialized
cntvariable solved. - Workaround for wrongly defined
GetFileVersionInfoSizeandGetFileVersionInfo(on older PSDK-Version (VC7 and before), the first parameter was declared asLPTSTRinstead ofLPCTSTR). - Changed the used
ContextFlagsparameter fromCONTEX_ALLtoCONTEXT_FULL(this also works correctly and is supported in older PSDK-versions). - Now compiles on VC5/6 without installing a newer Platform-SDK (all missing declarations are now embedded).
- Added VC6 project/solution files.
- Added a
pUserDatamember to theShowCallstackfunction and thePReadProcessMemoryRoutinedeclaration (to pass some user-defined data, which can be used in thereadMemoryFunction-callback).
- 2005-08-02
OnSymInitnow also outputs the OS-version by default.- Added example for doing an exception-callstack-walking in main.cpp (thanks to owillebo).
- Correction in article about
RtlCaptureContext. This function is also available starting with XP (thanks to Dan Moulding). - Added chapters: Initializing the STACKFRAME64-structure, Displaying the callstack of an exception.
- 2005-08-05
- Removed some Lint issues... thanks to Okko Willeboordse!
- Removed all warnings with /analyze switch from VC8.
- 2005-09-06
- Fixed a small bug regarding VC5/6 and old PSDK version (
OSVERSIONINFOEXis not present).
- Fixed a small bug regarding VC5/6 and old PSDK version (
- 2005-11-07
- Detects an "endless-callstack" and terminates the stackwalking.
- Usage of
RtlCaptureContextonly on x64 and IA64 platforms.
License
This article, along with any associated source code and files, is licensed under The BSD License
《Walking the callstack(转载)》的更多相关文章
- 简单物联网:外网访问内网路由器下树莓派Flask服务器
最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...
- 利用ssh反向代理以及autossh实现从外网连接内网服务器
前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...
- 外网访问内网Docker容器
外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...
- 外网访问内网SpringBoot
外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...
- 外网访问内网Elasticsearch WEB
外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...
- 怎样从外网访问内网Rails
外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...
- 怎样从外网访问内网Memcached数据库
外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...
- 怎样从外网访问内网CouchDB数据库
外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...
- 怎样从外网访问内网DB2数据库
外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...
- 怎样从外网访问内网OpenLDAP数据库
外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...
随机推荐
- 常用 Gulp 插件汇总 —— 基于 Gulp 的前端集成解决方案(三)
前两篇文章讨论了 Gulp 的安装部署及基本概念,借助于 Gulp 强大的 插件生态 可以完成很多常见的和不常见的任务.本文主要汇总常用的 Gulp 插件及其基本使用,需要读者对 Gulp 有一个基本 ...
- [翻译]开发文档:android Bitmap的高效使用
内容概述 本文内容来自开发文档"Traning > Displaying Bitmaps Efficiently",包括大尺寸Bitmap的高效加载,图片的异步加载和数据缓存 ...
- 前端框架 EasyUI (2)页面布局 Layout
在 Web 程序中,页面布局对应用程序的用户体验至关重要. 在一般的信息管理类的 Web 应用程序中,页面结构通常有一个主工作区,然后在工作区上下左右靠近边界的区域设置一些边栏,用于显示信息或放置一些 ...
- Matlab 绘制三维立体图(以地质异常体为例)
前言:在地球物理勘探,流体空间分布等多种场景中,定位空间点P(x,y,x)的物理属性值Q,并绘制三维空间分布图,对我们洞察空间场景有十分重要的意义. 1. 三维立体图的基本要件: 全空间网格化 网格节 ...
- IIC驱动移植在linux3.14.78上的实现和在linux2.6.29上实现对比(deep dive)
首先说明下为什么写这篇文章,网上有许多博客也是介绍I2C驱动在linux上移植的实现,但是笔者认为他们相当一部分没有分清所写的驱动时的驱动模型,是基于device tree, 还是基于传统的Platf ...
- MyBatis基础入门--知识点总结
对原生态jdbc程序的问题总结 下面是一个传统的jdbc连接oracle数据库的标准代码: public static void main(String[] args) throws Exceptio ...
- javascript之闭包理解以及应用场景
半个月没写博文了,最近一直在弄小程序,感觉也没啥好写的. 之前读了js权威指南,也写了篇博文,但是实话实说当初看闭包确实还是一头雾水.现在时隔一个多月(当然这一段时间还是一直有在看闭包的相关知识)理解 ...
- 【C#公共帮助类】 Utils 10年代码,最全的系统帮助类
为大家分享一下个人的一个Utils系统帮助类,可能有些现在有新的技术替代,自行修改哈~ 这个帮助类主要包含:对象转换处理 .分割字符串.截取字符串.删除最后结尾的一个逗号. 删除最后结尾的指定字符后的 ...
- WebLogic的安装和配置以及MyEclipse中配置WebLogic
WebLogic 中间件: 是基础软件的一大类,属于可复用软件的范畴,顾名思义,中间件属于操作系统软件与应用软件的中间,比如:JDK,框架,weblogic. weblogic与tomcat区别 : ...
- 简单酷炫的canvas动画
作为一个新人怀着激动而紧张的心情写了第一篇帖子还请大家多多支持,小弟在次拜谢. 驯鹿拉圣诞老人动画效果图如下 html如下: <div style="width:400px;heigh ...