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