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"
17 #include "globalincs/mspdb_callstack.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 );
27 void SCP_mspdbcs_Initialise( );
28 void SCP_mspdbcs_Cleanup( );
29
30 static bool SCP_mspdbcs_initialised = false;
31 static CRITICAL_SECTION SCP_mspdbcs_cs;
32
SCP_mspdbcs_ResolveSymbol(HANDLE hProcess,UINT_PTR dwAddress,SCP_mspdbcs_SDumpStackSymbolInfo & siSymbol)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
SCP_mspdbcs_FunctionTableAccess(HANDLE hProcess,DWORD64 dwPCAddress)122 LPVOID __stdcall SCP_mspdbcs_FunctionTableAccess( HANDLE hProcess, DWORD64 dwPCAddress )
123 {
124 return SymFunctionTableAccess64( hProcess, dwPCAddress );
125 }
126
SCP_mspdbcs_GetModuleBase(HANDLE hProcess,DWORD64 returnAddress)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
SCP_mspdbcs_DumpStackThread(LPVOID pv)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 */
SCP_DumpStack(SCP_IDumpHandler * pIDH)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
SCP_mspdbcs_Initialise()317 void SCP_mspdbcs_Initialise( )
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
SCP_mspdbcs_Cleanup()333 void SCP_mspdbcs_Cleanup( )
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 }