]> git.ipfire.org Git - people/ms/gcc.git/blob - libphobos/libdruntime/core/sys/windows/stacktrace.d
d: Merge upstream dmd, druntime 4ca4140e58, phobos 454dff14d.
[people/ms/gcc.git] / libphobos / libdruntime / core / sys / windows / stacktrace.d
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 }
243 frameNum++;
244 }
245 while (dbghelp.StackWalk64(imageType, hProcess, hThread, &stackframe,
246 &ctxt, null, null, null, null));
247 return buffer[0 .. nframes];
248 }
249
250 static char[][] resolveNoSync(const(ulong)[] addresses)
251 {
252 auto dbghelp = DbgHelp.get();
253 if (dbghelp is null)
254 return []; // dbghelp.dll not available
255
256 HANDLE hProcess = GetCurrentProcess();
257
258 static struct BufSymbol
259 {
260 align(1):
261 IMAGEHLP_SYMBOLA64 _base;
262 TCHAR[1024] _buf = void;
263 }
264 BufSymbol bufSymbol=void;
265 IMAGEHLP_SYMBOLA64* symbol = &bufSymbol._base;
266 symbol.SizeOfStruct = IMAGEHLP_SYMBOLA64.sizeof;
267 symbol.MaxNameLength = bufSymbol._buf.length;
268
269 char[][] trace;
270 foreach (pc; addresses)
271 {
272 char[] res;
273 if (dbghelp.SymGetSymFromAddr64(hProcess, pc, null, symbol) &&
274 *symbol.Name.ptr)
275 {
276 DWORD disp;
277 IMAGEHLP_LINEA64 line=void;
278 line.SizeOfStruct = IMAGEHLP_LINEA64.sizeof;
279
280 if (dbghelp.SymGetLineFromAddr64(hProcess, pc, &disp, &line))
281 res = formatStackFrame(cast(void*)pc, symbol.Name.ptr,
282 line.FileName, line.LineNumber);
283 else
284 res = formatStackFrame(cast(void*)pc, symbol.Name.ptr);
285 }
286 else
287 res = formatStackFrame(cast(void*)pc);
288 trace ~= res;
289 }
290 return trace;
291 }
292
293 static char[] formatStackFrame(void* pc)
294 {
295 import core.stdc.stdio : snprintf;
296 char[2+2*size_t.sizeof+1] buf=void;
297
298 immutable len = snprintf(buf.ptr, buf.length, "0x%p", pc);
299 cast(uint)len < buf.length || assert(0);
300 return buf[0 .. len].dup;
301 }
302
303 static char[] formatStackFrame(void* pc, char* symName)
304 {
305 char[2048] demangleBuf=void;
306
307 auto res = formatStackFrame(pc);
308 res ~= " in ";
309 const(char)[] tempSymName = symName[0 .. strlen(symName)];
310 // Deal with dmd mangling of long names for OMF 32 bits builds
311 // Note that `target.d` only defines `CRuntime_DigitalMars` for OMF builds
312 version (CRuntime_DigitalMars)
313 {
314 size_t decodeIndex = 0;
315 tempSymName = decodeDmdString(tempSymName, decodeIndex);
316 }
317 res ~= demangle(tempSymName, demangleBuf);
318 return res;
319 }
320
321 static char[] formatStackFrame(void* pc, char* symName,
322 const scope char* fileName, uint lineNum)
323 {
324 import core.stdc.stdio : snprintf;
325 char[11] buf=void;
326
327 auto res = formatStackFrame(pc, symName);
328 res ~= " at ";
329 res ~= fileName[0 .. strlen(fileName)];
330 res ~= "(";
331 immutable len = snprintf(buf.ptr, buf.length, "%u", lineNum);
332 cast(uint)len < buf.length || assert(0);
333 res ~= buf[0 .. len];
334 res ~= ")";
335 return res;
336 }
337 }
338
339
340 // Workaround OPTLINK bug (Bugzilla 8263)
341 extern(Windows) BOOL FixupDebugHeader(HANDLE hProcess, ULONG ActionCode,
342 ulong CallbackContext, ulong UserContext)
343 {
344 if (ActionCode == CBA_READ_MEMORY)
345 {
346 auto p = cast(IMAGEHLP_CBA_READ_MEMORY*)CallbackContext;
347 if (!(p.addr & 0xFF) && p.bytes == 0x1C &&
348 // IMAGE_DEBUG_DIRECTORY.PointerToRawData
349 (*cast(DWORD*)(p.addr + 24) & 0xFF) == 0x20)
350 {
351 immutable base = DbgHelp.get().SymGetModuleBase64(hProcess, p.addr);
352 // IMAGE_DEBUG_DIRECTORY.AddressOfRawData
353 if (base + *cast(DWORD*)(p.addr + 20) == p.addr + 0x1C &&
354 *cast(DWORD*)(p.addr + 0x1C) == 0 &&
355 *cast(DWORD*)(p.addr + 0x20) == ('N'|'B'<<8|'0'<<16|'9'<<24))
356 {
357 debug(PRINTF) printf("fixup IMAGE_DEBUG_DIRECTORY.AddressOfRawData\n");
358 memcpy(p.buf, cast(void*)p.addr, 0x1C);
359 *cast(DWORD*)(p.buf + 20) = cast(DWORD)(p.addr - base) + 0x20;
360 *p.bytesread = 0x1C;
361 return TRUE;
362 }
363 }
364 }
365 return FALSE;
366 }
367
368 private string generateSearchPath()
369 {
370 __gshared string[3] defaultPathList = ["_NT_SYMBOL_PATH",
371 "_NT_ALTERNATE_SYMBOL_PATH",
372 "SYSTEMROOT"];
373
374 string path;
375 char[2048] temp = void;
376 DWORD len;
377
378 foreach ( e; defaultPathList )
379 {
380 if ( (len = GetEnvironmentVariableA( e.ptr, temp.ptr, temp.length )) > 0 )
381 {
382 path ~= temp[0 .. len];
383 path ~= ";";
384 }
385 }
386 path ~= "\0";
387 return path;
388 }
389
390
391 shared static this()
392 {
393 auto dbghelp = DbgHelp.get();
394
395 if ( dbghelp is null )
396 return; // dbghelp.dll not available
397
398 auto kernel32Handle = LoadLibraryA( "kernel32.dll" );
399 if (kernel32Handle !is null)
400 {
401 RtlCaptureStackBackTrace = cast(RtlCaptureStackBackTraceFunc) GetProcAddress(kernel32Handle, "RtlCaptureStackBackTrace");
402 debug(PRINTF)
403 {
404 if (RtlCaptureStackBackTrace !is null)
405 printf("Found RtlCaptureStackBackTrace\n");
406 }
407 }
408
409 debug(PRINTF)
410 {
411 API_VERSION* dbghelpVersion = dbghelp.ImagehlpApiVersion();
412 printf("DbgHelp Version %d.%d.%d\n", dbghelpVersion.MajorVersion, dbghelpVersion.MinorVersion, dbghelpVersion.Revision);
413 }
414
415 HANDLE hProcess = GetCurrentProcess();
416
417 DWORD symOptions = dbghelp.SymGetOptions();
418 symOptions |= SYMOPT_LOAD_LINES;
419 symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS;
420 symOptions |= SYMOPT_DEFERRED_LOAD;
421 symOptions = dbghelp.SymSetOptions( symOptions );
422
423 debug(PRINTF) printf("Search paths: %s\n", generateSearchPath().ptr);
424
425 if (!dbghelp.SymInitialize(hProcess, generateSearchPath().ptr, TRUE))
426 return;
427
428 dbghelp.SymRegisterCallback64(hProcess, &FixupDebugHeader, 0);
429
430 InitializeCriticalSection(&mutex);
431 initialized = true;
432 }