Developer Documentation
StackWalker.hh
1 /**********************************************************************
2  *
3  * StackWalker.h
4  *
5  *
6  *
7  * LICENSE (http://www.opensource.org/licenses/bsd-license.php)
8  *
9  * Copyright (c) 2005-2009, Jochen Kalmbach
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without modification,
13  * are permitted provided that the following conditions are met:
14  *
15  * Redistributions of source code must retain the above copyright notice,
16  * this list of conditions and the following disclaimer.
17  * Redistributions in binary form must reproduce the above copyright notice,
18  * this list of conditions and the following disclaimer in the documentation
19  * and/or other materials provided with the distribution.
20  * Neither the name of Jochen Kalmbach nor the names of its contributors may be
21  * used to endorse or promote products derived from this software without
22  * specific prior written permission.
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
30  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33  *
34  * **********************************************************************/
35 // #pragma once is supported starting with _MCS_VER 1000,
36 // so we need not to check the version (because we only support _MSC_VER >= 1100)!
37 #pragma once
38 
39 #include <windows.h>
40 
41 // special defines for VC5/6 (if no actual PSDK is installed):
42 #if _MSC_VER < 1300
43 typedef unsigned __int64 DWORD64, *PDWORD64;
44 #if defined(_WIN64)
45 typedef unsigned __int64 SIZE_T, *PSIZE_T;
46 #else
47 typedef unsigned long SIZE_T, *PSIZE_T;
48 #endif
49 #endif // _MSC_VER < 1300
50 
51 class StackWalkerInternal; // forward
53 {
54 private:
55  // copy ops are private to prevent copying
56  StackWalker(const StackWalker&); // no implementation
57  StackWalker& operator=(const StackWalker&); // no implementation
58 public:
59  typedef enum StackWalkOptions
60  {
61  // No addition info will be retrived
62  // (only the address is available)
63  RetrieveNone = 0,
64 
65  // Try to get the symbol-name
66  RetrieveSymbol = 1,
67 
68  // Try to get the line for this symbol
69  RetrieveLine = 2,
70 
71  // Try to retrieve the module-infos
72  RetrieveModuleInfo = 4,
73 
74  // Also retrieve the version for the DLL/EXE
75  RetrieveFileVersion = 8,
76 
77  // Contains all the abouve
78  RetrieveVerbose = 0xF,
79 
80  // Generate a "good" symbol-search-path
81  SymBuildPath = 0x10,
82 
83  // Also use the public Microsoft-Symbol-Server
84  SymUseSymSrv = 0x20,
85 
86  // Contains all the abouve "Sym"-options
87  SymAll = 0x30,
88 
89  // Contains all options (default)
90  OptionsAll = 0x3F
91  } StackWalkOptions;
92 
94  int options = OptionsAll, // 'int' is by design, to combine the enum-flags
95  LPCSTR szSymPath = NULL,
96  DWORD dwProcessId = GetCurrentProcessId(),
97  HANDLE hProcess = GetCurrentProcess()
98  );
99  StackWalker(DWORD dwProcessId, HANDLE hProcess);
100  virtual ~StackWalker();
101 
102  typedef BOOL (__stdcall *PReadProcessMemoryRoutine)(
103  HANDLE hProcess,
104  DWORD64 qwBaseAddress,
105  PVOID lpBuffer,
106  DWORD nSize,
107  LPDWORD lpNumberOfBytesRead,
108  LPVOID pUserData // optional data, which was passed in "ShowCallstack"
109  );
110 
111  BOOL LoadModules();
112 
113  BOOL ShowCallstack(
114  HANDLE hThread = GetCurrentThread(),
115  const CONTEXT *context = NULL,
116  PReadProcessMemoryRoutine readMemoryFunction = NULL,
117  LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback
118  );
119 
120 #if _MSC_VER >= 1300
121 // due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public"
122 // in older compilers in order to use it... starting with VC7 we can declare it as "protected"
123 protected:
124 #endif
125  enum { STACKWALK_MAX_NAMELEN = 1024 }; // max name length for found symbols
126 
127 protected:
128  // Entry for each Callstack-Entry
129  typedef struct CallstackEntry
130  {
131  DWORD64 offset; // if 0, we have no valid entry
132  CHAR name[STACKWALK_MAX_NAMELEN];
133  CHAR undName[STACKWALK_MAX_NAMELEN];
134  CHAR undFullName[STACKWALK_MAX_NAMELEN];
135  DWORD64 offsetFromSmybol;
136  DWORD offsetFromLine;
137  DWORD lineNumber;
138  CHAR lineFileName[STACKWALK_MAX_NAMELEN];
139  DWORD symType;
140  LPCSTR symTypeString;
141  CHAR moduleName[STACKWALK_MAX_NAMELEN];
142  DWORD64 baseOfImage;
143  CHAR loadedImageName[STACKWALK_MAX_NAMELEN];
144  } CallstackEntry;
145 
146  typedef enum CallstackEntryType {firstEntry, nextEntry, lastEntry};
147 
148  virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
149  virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion);
150  virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry);
151  virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr);
152  virtual void OnOutput(LPCSTR szText);
153 
154  StackWalkerInternal *m_sw;
155  HANDLE m_hProcess;
156  DWORD m_dwProcessId;
157  BOOL m_modulesLoaded;
158  LPSTR m_szSymPath;
159 
160  int m_options;
161  int m_MaxRecursionCount;
162 
163  static BOOL __stdcall myReadProcMem(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead);
164 
165  friend StackWalkerInternal;
166 }; // class StackWalker
167 
168 
169 // The "ugly" assembler-implementation is needed for systems before XP
170 // If you have a new PSDK and you only compile for XP and later, then you can use
171 // the "RtlCaptureContext"
172 // Currently there is no define which determines the PSDK-Version...
173 // So we just use the compiler-version (and assumes that the PSDK is
174 // the one which was installed by the VS-IDE)
175 
176 // INFO: If you want, you can use the RtlCaptureContext if you only target XP and later...
177 // But I currently use it in x64/IA64 environments...
178 //#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400)
179 
180 #if defined(_M_IX86)
181 #ifdef CURRENT_THREAD_VIA_EXCEPTION
182 // TODO: The following is not a "good" implementation,
183 // because the callstack is only valid in the "__except" block...
184 #define GET_CURRENT_CONTEXT(c, contextFlags) \
185  do { \
186  memset(&c, 0, sizeof(CONTEXT)); \
187  EXCEPTION_POINTERS *pExp = NULL; \
188  __try { \
189  throw 0; \
190  } __except( ( (pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER)) {} \
191  if (pExp != NULL) \
192  memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \
193  c.ContextFlags = contextFlags; \
194  } while(0);
195 #else
196 // The following should be enough for walking the callstack...
197 #define GET_CURRENT_CONTEXT(c, contextFlags) \
198  do { \
199  memset(&c, 0, sizeof(CONTEXT)); \
200  c.ContextFlags = contextFlags; \
201  __asm call x \
202  __asm x: pop eax \
203  __asm mov c.Eip, eax \
204  __asm mov c.Ebp, ebp \
205  __asm mov c.Esp, esp \
206  } while(0);
207 #endif
208 
209 #else
210 
211 // The following is defined for x86 (XP and higher), x64 and IA64:
212 #define GET_CURRENT_CONTEXT(c, contextFlags) \
213  do { \
214  memset(&c, 0, sizeof(CONTEXT)); \
215  c.ContextFlags = contextFlags; \
216  RtlCaptureContext(&c); \
217 } while(0);
218 #endif