1 /** 2 * ... 3 * 4 * Copyright: Copyright Benjamin Thaut 2010 - 2013. 5 * License: Distributed under the 6 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). 7 * (See accompanying file LICENSE) 8 * Authors: Benjamin Thaut, Sean Kelly 9 * Source: $(DRUNTIMESRC core/sys/windows/_stacktrace.d) 10 */ 11 12 module core.sys.windows.stacktrace; 13 version (Windows): 14 15 import core.demangle; 16 import core.runtime; 17 import core.stdc.stdlib; 18 import core.stdc.string; 19 import core.sys.windows.dbghelp; 20 import core.sys.windows.imagehlp /+: ADDRESS_MODE+/; 21 import core.sys.windows.winbase; 22 import core.sys.windows.windef; 23 24 //debug=PRINTF; 25 debug(PRINTF) import core.stdc.stdio; 26 27 28 extern(Windows) void RtlCaptureContext(CONTEXT* ContextRecord); 29 extern(Windows) DWORD GetEnvironmentVariableA(LPCSTR lpName, LPSTR pBuffer, DWORD nSize); 30 31 extern(Windows) alias USHORT function(ULONG FramesToSkip, ULONG FramesToCapture, PVOID *BackTrace, PULONG BackTraceHash) RtlCaptureStackBackTraceFunc; 32 33 private __gshared RtlCaptureStackBackTraceFunc RtlCaptureStackBackTrace; 34 private __gshared immutable bool initialized; 35 36 37 class StackTrace : Throwable.TraceInfo 38 { 39 public: 40 /** 41 * Constructor 42 * Params: 43 * skip = The number of stack frames to skip. 44 * context = The context to receive the stack trace from. Can be null. 45 */ 46 this(size_t skip, CONTEXT* context) 47 { 48 if (context is null) 49 { 50 version (Win64) 51 static enum INTERNALFRAMES = 3; 52 else version (Win32) 53 static enum INTERNALFRAMES = 2; 54 55 skip += INTERNALFRAMES; //skip the stack frames within the StackTrace class 56 } 57 else 58 { 59 //When a exception context is given the first stack frame is repeated for some reason 60 version (Win64) 61 static enum INTERNALFRAMES = 1; 62 else version (Win32) 63 static enum INTERNALFRAMES = 1; 64 65 skip += INTERNALFRAMES; 66 } 67 if ( initialized ) 68 m_trace = trace(skip, context); 69 } 70 71 int opApply( scope int delegate(ref const(char[])) dg ) const 72 { 73 return opApply( (ref size_t, ref const(char[]) buf) 74 { 75 return dg( buf ); 76 }); 77 } 78 79 80 int opApply( scope int delegate(ref size_t, ref const(char[])) dg ) const 81 { 82 int result; 83 foreach ( i, e; resolve(m_trace) ) 84 { 85 if ( (result = dg( i, e )) != 0 ) 86 break; 87 } 88 return result; 89 } 90 91 92 @trusted override string toString() const 93 { 94 string result; 95 96 foreach ( e; this ) 97 { 98 result ~= e ~ "\n"; 99 } 100 return result; 101 } 102 103 /** 104 * Receive a stack trace in the form of an address list. 105 * Params: 106 * skip = How many stack frames should be skipped. 107 * context = The context that should be used. If null the current context is used. 108 * Returns: 109 * A list of addresses that can be passed to resolve at a later point in time. 110 */ 111 static ulong[] trace(size_t skip = 0, CONTEXT* context = null) 112 { 113 synchronized( typeid(StackTrace) ) 114 { 115 return traceNoSync(skip, context); 116 } 117 } 118 119 /** 120 * Resolve a stack trace. 121 * Params: 122 * addresses = A list of addresses to resolve. 123 * Returns: 124 * An array of strings with the results. 125 */ 126 @trusted static char[][] resolve(const(ulong)[] addresses) 127 { 128 synchronized( typeid(StackTrace) ) 129 { 130 return resolveNoSync(addresses); 131 } 132 } 133 134 private: 135 ulong[] m_trace; 136 137 138 static ulong[] traceNoSync(size_t skip, CONTEXT* context) 139 { 140 auto dbghelp = DbgHelp.get(); 141 if (dbghelp is null) 142 return []; // dbghelp.dll not available 143 144 if (RtlCaptureStackBackTrace !is null && context is null) 145 { 146 size_t[63] buffer = void; // On windows xp the sum of "frames to skip" and "frames to capture" can't be greater then 63 147 auto backtraceLength = RtlCaptureStackBackTrace(cast(ULONG)skip, cast(ULONG)(buffer.length - skip), cast(void**)buffer.ptr, null); 148 149 // If we get a backtrace and it does not have the maximum length use it. 150 // Otherwise rely on tracing through StackWalk64 which is slower but works when no frame pointers are available. 151 if (backtraceLength > 1 && backtraceLength < buffer.length - skip) 152 { 153 debug(PRINTF) printf("Using result from RtlCaptureStackBackTrace\n"); 154 version (Win64) 155 { 156 return buffer[0..backtraceLength].dup; 157 } 158 else version (Win32) 159 { 160 auto result = new ulong[backtraceLength]; 161 foreach (i, ref e; result) 162 { 163 e = buffer[i]; 164 } 165 return result; 166 } 167 } 168 } 169 170 HANDLE hThread = GetCurrentThread(); 171 HANDLE hProcess = GetCurrentProcess(); 172 CONTEXT ctxt; 173 174 if (context is null) 175 { 176 ctxt.ContextFlags = CONTEXT_FULL; 177 RtlCaptureContext(&ctxt); 178 } 179 else 180 { 181 ctxt = *context; 182 } 183 184 //x86 185 STACKFRAME64 stackframe; 186 with (stackframe) 187 { 188 version (X86) 189 { 190 enum Flat = ADDRESS_MODE.AddrModeFlat; 191 AddrPC.Offset = ctxt.Eip; 192 AddrPC.Mode = Flat; 193 AddrFrame.Offset = ctxt.Ebp; 194 AddrFrame.Mode = Flat; 195 AddrStack.Offset = ctxt.Esp; 196 AddrStack.Mode = Flat; 197 } 198 else version (X86_64) 199 { 200 enum Flat = ADDRESS_MODE.AddrModeFlat; 201 AddrPC.Offset = ctxt.Rip; 202 AddrPC.Mode = Flat; 203 AddrFrame.Offset = ctxt.Rbp; 204 AddrFrame.Mode = Flat; 205 AddrStack.Offset = ctxt.Rsp; 206 AddrStack.Mode = Flat; 207 } 208 } 209 210 version (X86) enum imageType = IMAGE_FILE_MACHINE_I386; 211 else version (X86_64) enum imageType = IMAGE_FILE_MACHINE_AMD64; 212 else static assert(0, "unimplemented"); 213 214 ulong[] result; 215 size_t frameNum = 0; 216 217 // do ... while so that we don't skip the first stackframe 218 do 219 { 220 if ( stackframe.AddrPC.Offset == stackframe.AddrReturn.Offset ) 221 { 222 debug(PRINTF) printf("Endless callstack\n"); 223 break; 224 } 225 if (frameNum >= skip) 226 { 227 result ~= stackframe.AddrPC.Offset; 228 } 229 frameNum++; 230 } 231 while (dbghelp.StackWalk64(imageType, hProcess, hThread, &stackframe, 232 &ctxt, null, null, null, null)); 233 return result; 234 } 235 236 static char[][] resolveNoSync(const(ulong)[] addresses) 237 { 238 auto dbghelp = DbgHelp.get(); 239 if (dbghelp is null) 240 return []; // dbghelp.dll not available 241 242 HANDLE hProcess = GetCurrentProcess(); 243 244 static struct BufSymbol 245 { 246 align(1): 247 IMAGEHLP_SYMBOLA64 _base; 248 TCHAR[1024] _buf = void; 249 } 250 BufSymbol bufSymbol=void; 251 IMAGEHLP_SYMBOLA64* symbol = &bufSymbol._base; 252 symbol.SizeOfStruct = IMAGEHLP_SYMBOLA64.sizeof; 253 symbol.MaxNameLength = bufSymbol._buf.length; 254 255 char[][] trace; 256 foreach (pc; addresses) 257 { 258 if ( pc != 0 ) 259 { 260 char[] res; 261 if (dbghelp.SymGetSymFromAddr64(hProcess, pc, null, symbol) && 262 *symbol.Name.ptr) 263 { 264 DWORD disp; 265 IMAGEHLP_LINEA64 line=void; 266 line.SizeOfStruct = IMAGEHLP_LINEA64.sizeof; 267 268 if (dbghelp.SymGetLineFromAddr64(hProcess, pc, &disp, &line)) 269 res = formatStackFrame(cast(void*)pc, symbol.Name.ptr, 270 line.FileName, line.LineNumber); 271 else 272 res = formatStackFrame(cast(void*)pc, symbol.Name.ptr); 273 } 274 else 275 res = formatStackFrame(cast(void*)pc); 276 trace ~= res; 277 } 278 } 279 return trace; 280 } 281 282 static char[] formatStackFrame(void* pc) 283 { 284 import core.stdc.stdio : snprintf; 285 char[2+2*size_t.sizeof+1] buf=void; 286 287 immutable len = snprintf(buf.ptr, buf.length, "0x%p", pc); 288 cast(uint)len < buf.length || assert(0); 289 return buf[0 .. len].dup; 290 } 291 292 static char[] formatStackFrame(void* pc, char* symName) 293 { 294 char[2048] demangleBuf=void; 295 296 auto res = formatStackFrame(pc); 297 res ~= " in "; 298 const(char)[] tempSymName = symName[0 .. strlen(symName)]; 299 //Deal with dmd mangling of long names 300 version (CRuntime_DigitalMars) 301 { 302 size_t decodeIndex = 0; 303 tempSymName = decodeDmdString(tempSymName, decodeIndex); 304 } 305 res ~= demangle(tempSymName, demangleBuf); 306 return res; 307 } 308 309 static char[] formatStackFrame(void* pc, char* symName, 310 in char* fileName, uint lineNum) 311 { 312 import core.stdc.stdio : snprintf; 313 char[11] buf=void; 314 315 auto res = formatStackFrame(pc, symName); 316 res ~= " at "; 317 res ~= fileName[0 .. strlen(fileName)]; 318 res ~= "("; 319 immutable len = snprintf(buf.ptr, buf.length, "%u", lineNum); 320 cast(uint)len < buf.length || assert(0); 321 res ~= buf[0 .. len]; 322 res ~= ")"; 323 return res; 324 } 325 } 326 327 328 // Workaround OPTLINK bug (Bugzilla 8263) 329 extern(Windows) BOOL FixupDebugHeader(HANDLE hProcess, ULONG ActionCode, 330 ulong CallbackContext, ulong UserContext) 331 { 332 if (ActionCode == CBA_READ_MEMORY) 333 { 334 auto p = cast(IMAGEHLP_CBA_READ_MEMORY*)CallbackContext; 335 if (!(p.addr & 0xFF) && p.bytes == 0x1C && 336 // IMAGE_DEBUG_DIRECTORY.PointerToRawData 337 (*cast(DWORD*)(p.addr + 24) & 0xFF) == 0x20) 338 { 339 immutable base = DbgHelp.get().SymGetModuleBase64(hProcess, p.addr); 340 // IMAGE_DEBUG_DIRECTORY.AddressOfRawData 341 if (base + *cast(DWORD*)(p.addr + 20) == p.addr + 0x1C && 342 *cast(DWORD*)(p.addr + 0x1C) == 0 && 343 *cast(DWORD*)(p.addr + 0x20) == ('N'|'B'<<8|'0'<<16|'9'<<24)) 344 { 345 debug(PRINTF) printf("fixup IMAGE_DEBUG_DIRECTORY.AddressOfRawData\n"); 346 memcpy(p.buf, cast(void*)p.addr, 0x1C); 347 *cast(DWORD*)(p.buf + 20) = cast(DWORD)(p.addr - base) + 0x20; 348 *p.bytesread = 0x1C; 349 return TRUE; 350 } 351 } 352 } 353 return FALSE; 354 } 355 356 private string generateSearchPath() 357 { 358 __gshared string[3] defaultPathList = ["_NT_SYMBOL_PATH", 359 "_NT_ALTERNATE_SYMBOL_PATH", 360 "SYSTEMROOT"]; 361 362 string path; 363 char[2048] temp = void; 364 DWORD len; 365 366 foreach ( e; defaultPathList ) 367 { 368 if ( (len = GetEnvironmentVariableA( e.ptr, temp.ptr, temp.length )) > 0 ) 369 { 370 path ~= temp[0 .. len]; 371 path ~= ";"; 372 } 373 } 374 path ~= "\0"; 375 return path; 376 } 377 378 379 shared static this() 380 { 381 auto dbghelp = DbgHelp.get(); 382 383 if ( dbghelp is null ) 384 return; // dbghelp.dll not available 385 386 auto kernel32Handle = LoadLibraryA( "kernel32.dll" ); 387 if (kernel32Handle !is null) 388 { 389 RtlCaptureStackBackTrace = cast(RtlCaptureStackBackTraceFunc) GetProcAddress(kernel32Handle, "RtlCaptureStackBackTrace"); 390 debug(PRINTF) 391 { 392 if (RtlCaptureStackBackTrace !is null) 393 printf("Found RtlCaptureStackBackTrace\n"); 394 } 395 } 396 397 debug(PRINTF) 398 { 399 API_VERSION* dbghelpVersion = dbghelp.ImagehlpApiVersion(); 400 printf("DbgHelp Version %d.%d.%d\n", dbghelpVersion.MajorVersion, dbghelpVersion.MinorVersion, dbghelpVersion.Revision); 401 } 402 403 HANDLE hProcess = GetCurrentProcess(); 404 405 DWORD symOptions = dbghelp.SymGetOptions(); 406 symOptions |= SYMOPT_LOAD_LINES; 407 symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS; 408 symOptions |= SYMOPT_DEFERRED_LOAD; 409 symOptions = dbghelp.SymSetOptions( symOptions ); 410 411 debug(PRINTF) printf("Search paths: %s\n", generateSearchPath().ptr); 412 413 if (!dbghelp.SymInitialize(hProcess, generateSearchPath().ptr, TRUE)) 414 return; 415 416 dbghelp.SymRegisterCallback64(hProcess, &FixupDebugHeader, 0); 417 418 initialized = true; 419 } 420