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 nulib.system.win32.stacktrace; 13 14 15 import core.demangle; 16 import core.stdc.stdlib; 17 import core.stdc.string; 18 import nulib.system.win32.dbghelp; 19 import nulib.system.win32.imagehlp /+: ADDRESS_MODE+/; 20 import nulib.system.win32.winbase; 21 import nulib.system.win32.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 enum INTERNALFRAMES = 0; 52 else version (Win64) 53 enum INTERNALFRAMES = 3; 54 else version (Win32) 55 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 enum INTERNALFRAMES = 1; 64 else version (Win32) 65 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 version (AArch64) enum imageType = IMAGE_FILE_MACHINE_ARM64; 234 else static assert(0, "unimplemented"); 235 236 size_t frameNum = 0; 237 size_t nframes = 0; 238 239 // do ... while so that we don't skip the first stackframe 240 do 241 { 242 if (frameNum >= skip) 243 { 244 buffer[nframes++] = stackframe.AddrPC.Offset; 245 if (nframes >= buffer.length) 246 break; 247 } 248 frameNum++; 249 } 250 while (dbghelp.StackWalk64(imageType, hProcess, hThread, &stackframe, 251 &ctxt, null, null, null, null)); 252 return buffer[0 .. nframes]; 253 } 254 255 static char[][] resolveNoSync(const(ulong)[] addresses) 256 { 257 auto dbghelp = DbgHelp.get(); 258 if (dbghelp is null) 259 return []; // dbghelp.dll not available 260 261 HANDLE hProcess = GetCurrentProcess(); 262 263 static struct BufSymbol 264 { 265 align(1): 266 IMAGEHLP_SYMBOLA64 _base; 267 TCHAR[1024] _buf = void; 268 } 269 BufSymbol bufSymbol=void; 270 IMAGEHLP_SYMBOLA64* symbol = &bufSymbol._base; 271 symbol.SizeOfStruct = IMAGEHLP_SYMBOLA64.sizeof; 272 symbol.MaxNameLength = bufSymbol._buf.length; 273 274 char[][] trace; 275 version (LDC) bool haveEncounteredDThrowException = false; 276 foreach (pc; addresses) 277 { 278 char[] res; 279 if (dbghelp.SymGetSymFromAddr64(hProcess, pc, null, symbol) && 280 *symbol.Name.ptr) 281 { 282 version (LDC) 283 { 284 // when encountering the first `_d_throw_exception()` frame, 285 // clear the current trace and continue with the next frame 286 if (!haveEncounteredDThrowException) 287 { 288 auto len = strlen(symbol.Name.ptr); 289 // ignore missing leading underscore (Win64 release) or additional module prefix (Win64 debug) 290 //import core.stdc.stdio; printf("RAW: %s\n", symbol.Name.ptr); 291 if (len >= 17 && symbol.Name.ptr[len-17 .. len] == "d_throw_exception") 292 { 293 haveEncounteredDThrowException = true; 294 trace.length = 0; 295 continue; 296 } 297 } 298 } 299 300 DWORD disp; 301 IMAGEHLP_LINEA64 line=void; 302 line.SizeOfStruct = IMAGEHLP_LINEA64.sizeof; 303 304 if (dbghelp.SymGetLineFromAddr64(hProcess, pc, &disp, &line)) 305 res = formatStackFrame(cast(void*)pc, symbol.Name.ptr, 306 line.FileName, line.LineNumber); 307 else 308 res = formatStackFrame(cast(void*)pc, symbol.Name.ptr); 309 } 310 else 311 res = formatStackFrame(cast(void*)pc); 312 trace ~= res; 313 } 314 return trace; 315 } 316 317 static char[] formatStackFrame(void* pc) 318 { 319 import core.stdc.stdio : snprintf; 320 char[2+2*size_t.sizeof+1] buf=void; 321 322 immutable len = snprintf(buf.ptr, buf.length, "0x%p", pc); 323 cast(uint)len < buf.length || assert(0); 324 return buf[0 .. len].dup; 325 } 326 327 static char[] formatStackFrame(void* pc, char* symName) 328 { 329 char[2048] demangleBuf=void; 330 331 auto res = formatStackFrame(pc); 332 res ~= " in "; 333 const(char)[] tempSymName = symName[0 .. strlen(symName)]; 334 res ~= demangle(tempSymName, demangleBuf); 335 return res; 336 } 337 338 static char[] formatStackFrame(void* pc, char* symName, 339 const scope char* fileName, uint lineNum) 340 { 341 import core.stdc.stdio : snprintf; 342 char[11] buf=void; 343 344 auto res = formatStackFrame(pc, symName); 345 res ~= " at "; 346 res ~= fileName[0 .. strlen(fileName)]; 347 res ~= "("; 348 immutable len = snprintf(buf.ptr, buf.length, "%u", lineNum); 349 cast(uint)len < buf.length || assert(0); 350 res ~= buf[0 .. len]; 351 res ~= ")"; 352 return res; 353 } 354 } 355 356 357 private string generateSearchPath() 358 { 359 __gshared string[3] defaultPathList = ["_NT_SYMBOL_PATH", 360 "_NT_ALTERNATE_SYMBOL_PATH", 361 "SYSTEMROOT"]; 362 363 string path; 364 char[2048] temp = void; 365 DWORD len; 366 367 foreach ( e; defaultPathList ) 368 { 369 if ( (len = GetEnvironmentVariableA( e.ptr, temp.ptr, temp.length )) > 0 ) 370 { 371 path ~= temp[0 .. len]; 372 path ~= ";"; 373 } 374 } 375 path ~= "\0"; 376 return path; 377 } 378 379 380 shared static this() 381 { 382 auto dbghelp = DbgHelp.get(); 383 384 if ( dbghelp is null ) 385 return; // dbghelp.dll not available 386 387 auto kernel32Handle = LoadLibraryA( "kernel32.dll" ); 388 if (kernel32Handle !is null) 389 { 390 RtlCaptureStackBackTrace = cast(RtlCaptureStackBackTraceFunc) GetProcAddress(kernel32Handle, "RtlCaptureStackBackTrace"); 391 debug(PRINTF) 392 { 393 if (RtlCaptureStackBackTrace !is null) 394 printf("Found RtlCaptureStackBackTrace\n"); 395 } 396 } 397 398 debug(PRINTF) 399 { 400 API_VERSION* dbghelpVersion = dbghelp.ImagehlpApiVersion(); 401 printf("DbgHelp Version %d.%d.%d\n", dbghelpVersion.MajorVersion, dbghelpVersion.MinorVersion, dbghelpVersion.Revision); 402 } 403 404 HANDLE hProcess = GetCurrentProcess(); 405 406 DWORD symOptions = dbghelp.SymGetOptions(); 407 symOptions |= SYMOPT_LOAD_LINES; 408 symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS; 409 symOptions |= SYMOPT_DEFERRED_LOAD; 410 symOptions = dbghelp.SymSetOptions( symOptions ); 411 412 debug(PRINTF) printf("Search paths: %s\n", generateSearchPath().ptr); 413 414 if (!dbghelp.SymInitialize(hProcess, generateSearchPath().ptr, TRUE)) 415 return; 416 417 InitializeCriticalSection(&mutex); 418 initialized = true; 419 }