FS2_Open
Open source remastering of the Freespace 2 engine
mspdb_callstack.cpp
Go to the documentation of this file.
1 /* This file contains debugging utilities used only under VC2005+
2  * Maintained by: portej05 (please run patches past him before committing here!)
3  */
4 
5 /* Based on the ATL headers, however, updated to fix one or two bugs in the ATL headers
6  * and moved to the Sym*64 functions
7  */
8 
9 #if defined(PDB_DEBUGGING)
10 
11 /* Windows */
12 #include <windows.h>
13 #include <dbghelp.h>
14 
15 /* SCP */
16 #include "globalincs/pstypes.h"
18 
19 /* Link the library that we need */
20 #pragma comment( lib, "dbghelp.lib" )
21 
22 HRESULT SCP_DumpStack( SCP_IDumpHandler* pISH );
23 BOOL SCP_mspdbcs_ResolveSymbol( HANDLE hProcess, UINT_PTR dwAddress, SCP_mspdbcs_SDumpStackSymbolInfo& siSymbol );
24 LPVOID __stdcall SCP_mspdbcs_FunctionTableAccess( HANDLE hProcess, DWORD64 dwPCAddress );
25 DWORD64 __stdcall SCP_mspdbcs_GetModuleBase( HANDLE hProcess, DWORD64 returnAddress );
26 DWORD WINAPI SCP_mspdbcs_DumpStackThread( LPVOID pv );
28 void SCP_mspdbcs_Cleanup( );
29 
30 static bool SCP_mspdbcs_initialised = false;
31 static CRITICAL_SECTION SCP_mspdbcs_cs;
32 
33 BOOL SCP_mspdbcs_ResolveSymbol( HANDLE hProcess, UINT_PTR dwAddress, SCP_mspdbcs_SDumpStackSymbolInfo& siSymbol )
34 {
35  BOOL retVal = TRUE;
36 
37  char szUndec[ SCP_MSPDBCS_MAX_SYMBOL_LENGTH ];
38  char szWithOffset[ SCP_MSPDBCS_MAX_SYMBOL_LENGTH ];
39  char* pszSymbol = NULL;
40  IMAGEHLP_MODULE64 mi;
41 
42  memset( &siSymbol, 0, sizeof( SCP_mspdbcs_SDumpStackSymbolInfo ) );
43  mi.SizeOfStruct = sizeof( IMAGEHLP_MODULE64 );
44 
45  siSymbol.dwAddress = dwAddress;
46 
47  if ( !SymGetModuleInfo64( hProcess, dwAddress, &mi ) )
48  {
49  /* Unneeded */
50  /*printf("Error: %x\n", HRESULT_FROM_WIN32( GetLastError( ) ));*/
51  strcpy_s( siSymbol.szModule, "<no module>" );
52  }
53  else
54  {
55  char* pszLastModule = strchr( mi.ImageName, '\\' );
56 
57  if ( pszLastModule == NULL )
58  pszLastModule = mi.ImageName;
59  else
60  pszLastModule++; /* move off the backslash */
61 
62  strncpy_s( siSymbol.szModule, pszLastModule, _TRUNCATE );
63  }
64 
65  __try
66  {
67  union
68  {
69  CHAR rgchSymbol[ sizeof(IMAGEHLP_SYMBOL64) + SCP_MSPDBCS_MAX_SYMBOL_LENGTH ];
70  IMAGEHLP_SYMBOL64 sym;
71  } sym;
72 
73  memset( &sym.sym, 0, sizeof( sym.sym ) );
74  sym.sym.SizeOfStruct = sizeof( IMAGEHLP_SYMBOL64 );
75 
76 #ifdef _WIN64
77  sym.sym.Address = dwAddress;
78 #else
79  sym.sym.Address = (DWORD)dwAddress;
80 #endif
81  sym.sym.MaxNameLength = SCP_MSPDBCS_MAX_SYMBOL_LENGTH;
82 
83 #ifdef _WIN64
84  if ( SymGetSymFromAddr64( hProcess, dwAddress, &(siSymbol.dwOffset), &sym.sym ) )
85 #else
86  if ( SymGetSymFromAddr64( hProcess, (DWORD)dwAddress, &(siSymbol.dwOffset), &sym.sym ) )
87 #endif
88  {
89  pszSymbol = sym.sym.Name;
90 
91  if ( UnDecorateSymbolName( sym.sym.Name, szUndec, sizeof( szUndec )/sizeof( szUndec[0] ),
92  UNDNAME_NO_MS_KEYWORDS | UNDNAME_NO_ACCESS_SPECIFIERS ) )
93  {
94  pszSymbol = szUndec;
95  }
96  else if ( SymUnDName64( &sym.sym, szUndec, sizeof( szUndec )/sizeof( szUndec[0] ) ) )
97  {
98  pszSymbol = szUndec;
99  }
100 
101  if ( siSymbol.dwOffset != 0 )
102  {
103  sprintf_s( szWithOffset, SCP_MSPDBCS_MAX_SYMBOL_LENGTH, "%s + %d bytes", pszSymbol, siSymbol.dwOffset );
104  szWithOffset[ SCP_MSPDBCS_MAX_SYMBOL_LENGTH - 1 ] = '\0'; /* Because sprintf doesn't guarantee NULL terminating */
105  pszSymbol = szWithOffset;
106  }
107  }
108  else
109  pszSymbol = "<no symbol>";
110  }
111  __except( EXCEPTION_ACCESS_VIOLATION == GetExceptionCode( ) )
112  {
113  pszSymbol = "<EX: no symbol>";
114  siSymbol.dwOffset = dwAddress - mi.BaseOfImage;
115  }
116 
117  strncpy_s( siSymbol.szSymbol, pszSymbol, _TRUNCATE );
118 
119  return retVal;
120 }
121 
122 LPVOID __stdcall SCP_mspdbcs_FunctionTableAccess( HANDLE hProcess, DWORD64 dwPCAddress )
123 {
124  return SymFunctionTableAccess64( hProcess, dwPCAddress );
125 }
126 
127 DWORD64 __stdcall SCP_mspdbcs_GetModuleBase( HANDLE hProcess, DWORD64 returnAddress )
128 {
129  IMAGEHLP_MODULE moduleInfo;
130  moduleInfo.SizeOfStruct = sizeof( IMAGEHLP_MODULE );
131 
132  /* The ATL headers do it this way */
133 #ifdef _WIN64
134  if ( SymGetModuleInfo( hProcess, returnAddress, &moduleInfo ) )
135 #else
136  if ( SymGetModuleInfo( hProcess, (ULONG)returnAddress, &moduleInfo ) )
137 #endif
138  {
139  return moduleInfo.BaseOfImage;
140  }
141  else
142  {
143  MEMORY_BASIC_INFORMATION memoryBasicInfo;
144 
145  if ( VirtualQueryEx( hProcess, (LPVOID)returnAddress,
146  &memoryBasicInfo, sizeof( MEMORY_BASIC_INFORMATION ) ) )
147  {
148  DWORD cch = 0;
149  char szFile[ _MAX_PATH ] = {0}; /* Initialise the file path */
150  cch = GetModuleFileNameA( (HINSTANCE)memoryBasicInfo.AllocationBase, szFile, MAX_PATH );
151  SymLoadModule( hProcess, NULL, ((cch)?szFile:NULL),
152 #ifdef _WIN64
153  NULL, (DWORD_PTR)memoryBasicInfo.AllocationBase, 0 );
154 #else
155  NULL, (DWORD)(DWORD_PTR)memoryBasicInfo.AllocationBase, 0 );
156 #endif
157  return (DWORD_PTR)memoryBasicInfo.AllocationBase;
158  }
159  }
160 
161  return NULL;
162 }
163 
164 DWORD WINAPI SCP_mspdbcs_DumpStackThread( LPVOID pv )
165 {
166  CONTEXT context;
167  memset( &context, 0, sizeof( CONTEXT ) );
168  context.ContextFlags = CONTEXT_FULL;
169  SCP_mspdbcs_SDumpStackThreadInfo* pdsti =
170  reinterpret_cast< SCP_mspdbcs_SDumpStackThreadInfo* >( pv );
171 
172  /* We're going to walk the stack here */
173 
174  /* Suspend the running thread */
175  SuspendThread( pdsti->hThread );
176 
177  pdsti->pIDS->OnBegin( );
178 
179  /* Retrieve the context (processor state) of the suspended thread */
180  GetThreadContext( pdsti->hThread, &context );
181 
182  /* Set name decoration handling */
183  DWORD dw = SymGetOptions( );
184  dw = dw & ~SYMOPT_UNDNAME;
185  SymSetOptions( dw );
186 
187  /* Initialise the stackframe */
188  STACKFRAME64 stackFrame;
189  memset( &stackFrame, 0, sizeof( STACKFRAME64 ) );
190  stackFrame.AddrPC.Mode = AddrModeFlat;
191  stackFrame.AddrFrame.Mode = AddrModeFlat;
192  stackFrame.AddrStack.Mode = AddrModeFlat;
193  stackFrame.AddrReturn.Mode = AddrModeFlat;
194  stackFrame.AddrBStore.Mode = AddrModeFlat;
195 
196  DWORD dwMachType;
197 
198  /* Determine the machine type based on the compilation.
199  * Initialise stackFrame accordingly
200  * We will only handle the types of _M_IX86 or _M_AMD64
201  * We're not intending to be compatible with IA64 or any other architectures
202  * under windows
203  */
204 #if defined(_M_IX86)
205  dwMachType = IMAGE_FILE_MACHINE_I386;
206  stackFrame.AddrPC.Offset = context.Eip;
207  stackFrame.AddrStack.Offset = context.Esp;
208  stackFrame.AddrFrame.Offset = context.Ebp;
209 #elif defined(_M_AMD64)
210  dwMachType = IMAGE_FILE_MACHINE_AMD64;
211  stackFrame.AddrPC.Offset = context.Rip;
212  stackFrame.AddrStack = context.Rsp;
213 #else
214 # error UNKNOWN ARCHITECTURE
215 #endif
216 
217  /* All the discovered addresses will be stored in an array */
218  SCP_vector< void* > addresses;
219 
220  /* Walk the stack */
221  for ( int currFrame = 0; currFrame < SCP_MSPDBCS_MAX_STACK_FRAMES; currFrame++ )
222  {
223  if ( !StackWalk64( dwMachType, pdsti->hProcess, pdsti->hThread,
224  &stackFrame, &context, NULL,
225  SCP_mspdbcs_FunctionTableAccess, SCP_mspdbcs_GetModuleBase, NULL ) )
226  {
227  break; /* No more stack frames to walk */
228  }
229 
230  /* Found a useful address? */
231  if ( stackFrame.AddrPC.Offset != 0 )
232  {
233  if ( pdsti->pIDS->ResolveSymbols( ) )
234  addresses.push_back( (void*)(DWORD_PTR)stackFrame.AddrPC.Offset );
235  else
236  pdsti->pIDS->OnEntry( (void*)(DWORD_PTR)stackFrame.AddrPC.Offset, NULL, NULL );
237  }
238  }
239 
240  if ( pdsti->pIDS->ResolveSymbols( ) )
241  {
242  /* Dump the stack information that we have */
243  for ( size_t i = 0; i < addresses.size( ); i++ )
244  {
245  SCP_mspdbcs_SDumpStackSymbolInfo info;
246  const char* szModule = NULL;
247  const char* szSymbol = NULL;
248 
249  if ( SCP_mspdbcs_ResolveSymbol( pdsti->hProcess, (UINT_PTR)addresses[ i ], info ) )
250  {
251  szModule = info.szModule;
252  szSymbol = info.szSymbol;
253  }
254 
255  pdsti->pIDS->OnEntry( addresses[ i ], szModule, szSymbol );
256  }
257  }
258 
259  pdsti->pIDS->OnEnd( );
260  ResumeThread( pdsti->hThread );
261 
262  return 0;
263 }
264 
265 /* Entry point */
266 HRESULT SCP_DumpStack( SCP_IDumpHandler* pIDH )
267 {
268  if ( !pIDH )
269  return E_POINTER;
270 
271  /* Retrieve pseudo handles to the current thread and process */
272  HANDLE hPseudoThread = GetCurrentThread( );
273  HANDLE hPseudoProcess = GetCurrentProcess( );
274 
275  /* Retrieve the real handle of this thread */
276  HANDLE hThread = NULL;
277  if ( !DuplicateHandle( hPseudoProcess, hPseudoThread, /* Source process and thread */
278  hPseudoProcess, &hThread, /* Target process and thread */
279  0, FALSE, DUPLICATE_SAME_ACCESS ) ) /* Non-Inheritable, same access as current process/thread */
280  return HRESULT_FROM_WIN32( GetLastError( ) ); /* Bugger */
281 
282  DWORD dwID;
283 
284  SCP_mspdbcs_SDumpStackThreadInfo info;
285  info.hProcess = hPseudoProcess;
286  info.hThread = hThread;
287  info.pIDS = pIDH;
288 
289  /* Calls to dbghelp.dll must be synchronised :( */
290  EnterCriticalSection( &SCP_mspdbcs_cs );
291 
292  /* This will fail if SymInitialize hasn't been called, so
293  * this protects against uninitialised state */
294  if ( !SCP_mspdbcs_initialised )
295  {
296  LeaveCriticalSection( &SCP_mspdbcs_cs );
297  mprintf( ("Symbols not initialised\n") );
298  return E_UNEXPECTED;
299  }
300 
301  HANDLE workerThread = CreateThread( NULL, 0, SCP_mspdbcs_DumpStackThread,
302  &info, 0, &dwID );
303 
304  LeaveCriticalSection( &SCP_mspdbcs_cs );
305 
306  if ( workerThread == NULL )
307  return HRESULT_FROM_WIN32( GetLastError( ) ); /* Bugger */
308 
309  while ( WaitForSingleObject( workerThread, 0 ) != WAIT_OBJECT_0 );
310 
311  CloseHandle( workerThread );
312  return S_OK;
313 }
314 
315 #endif // PDB_DEBUGGING
316 
318 {
319 #ifdef PDB_DEBUGGING
320  HANDLE hPseudoProcess = GetCurrentProcess( );
321  if ( !SymInitialize( hPseudoProcess, NULL, TRUE ) )
322  {
323  mprintf( ("Could not initialise symbols - callstacks will fail: %x\n", HRESULT_FROM_WIN32( GetLastError( ) ) ) );
324  }
325  else
326  {
327  InitializeCriticalSection( &SCP_mspdbcs_cs );
328  SCP_mspdbcs_initialised = true;
329  }
330 #endif
331 }
332 
334 {
335 #ifdef PDB_DEBUGGING
336  /* We enter the critical section to synchronise the check of
337  * SCP_mspdbcs_initialised. Failure to do that could cause
338  * SymCleanup to be called before SCP_dump_stack is finished
339  */
340  EnterCriticalSection( &SCP_mspdbcs_cs );
341  SCP_mspdbcs_initialised = false; /* stop problems at end of execution */
342  LeaveCriticalSection( &SCP_mspdbcs_cs );
343 
344  DeleteCriticalSection( &SCP_mspdbcs_cs );
345 
346  HANDLE hPseudoProcess = GetCurrentProcess( );
347  SymCleanup( hPseudoProcess );
348 #endif
349 }
int i
Definition: multi_pxo.cpp:466
#define WINAPI
Definition: config.h:76
#define MAX_PATH
void * HINSTANCE
Definition: config.h:105
#define mprintf(args)
Definition: pstypes.h:238
#define _MAX_PATH
Definition: config.h:221
#define TRUE
Definition: pstypes.h:399
SDL_mutex * CRITICAL_SECTION
Definition: config.h:213
void SCP_mspdbcs_Initialise()
void SCP_mspdbcs_Cleanup()
unsigned long DWORD
Definition: config.h:90
long HRESULT
Definition: vddraw.h:115
int BOOL
Definition: config.h:80
typedef LPVOID
Definition: vddraw.h:119
#define __stdcall
Definition: config.h:73
void * HANDLE
Definition: config.h:106
#define FALSE
Definition: pstypes.h:400
#define strcpy_s(...)
Definition: safe_strings.h:67