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