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 }