]>
Commit | Line | Data |
---|---|---|
b4c522fa IB |
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; | |
b4c522fa IB |
16 | import core.stdc.stdlib; |
17 | import core.stdc.string; | |
18 | import core.sys.windows.dbghelp; | |
0da83a16 IB |
19 | import core.sys.windows.imagehlp /+: ADDRESS_MODE+/; |
20 | import core.sys.windows.winbase; | |
21 | import core.sys.windows.windef; | |
b4c522fa IB |
22 | |
23 | //debug=PRINTF; | |
24 | debug(PRINTF) import core.stdc.stdio; | |
25 | ||
26 | ||
f99303eb | 27 | extern(Windows) void RtlCaptureContext(CONTEXT* ContextRecord) @nogc; |
b4c522fa IB |
28 | extern(Windows) DWORD GetEnvironmentVariableA(LPCSTR lpName, LPSTR pBuffer, DWORD nSize); |
29 | ||
f99303eb | 30 | extern(Windows) alias USHORT function(ULONG FramesToSkip, ULONG FramesToCapture, PVOID *BackTrace, PULONG BackTraceHash) @nogc RtlCaptureStackBackTraceFunc; |
b4c522fa IB |
31 | |
32 | private __gshared RtlCaptureStackBackTraceFunc RtlCaptureStackBackTrace; | |
8da8c7d3 | 33 | private __gshared CRITICAL_SECTION mutex; // cannot use core.sync.mutex.Mutex unfortunately (cyclic dependency...) |
b4c522fa IB |
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 | */ | |
f99303eb | 46 | this(size_t skip, CONTEXT* context) @nogc |
b4c522fa IB |
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 | } | |
8da8c7d3 | 67 | if (initialized) |
f99303eb | 68 | m_trace = trace(tracebuf[], skip, context); |
b4c522fa IB |
69 | } |
70 | ||
8da8c7d3 | 71 | override int opApply( scope int delegate(ref const(char[])) dg ) const |
b4c522fa IB |
72 | { |
73 | return opApply( (ref size_t, ref const(char[]) buf) | |
74 | { | |
75 | return dg( buf ); | |
76 | }); | |
77 | } | |
78 | ||
79 | ||
8da8c7d3 | 80 | override int opApply( scope int delegate(ref size_t, ref const(char[])) dg ) const |
b4c522fa IB |
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 | /** | |
f99303eb IB |
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 | * | |
b4c522fa IB |
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. | |
f99303eb | 110 | * buffer = The buffer to use for the trace. This should be at least 63 elements. |
b4c522fa IB |
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) | |
f99303eb IB |
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 | |
b4c522fa | 121 | { |
8da8c7d3 IB |
122 | EnterCriticalSection(&mutex); |
123 | scope(exit) LeaveCriticalSection(&mutex); | |
124 | ||
125 | return traceNoSync(buffer, skip, context); | |
b4c522fa IB |
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 | { | |
8da8c7d3 IB |
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); | |
b4c522fa IB |
146 | } |
147 | ||
148 | private: | |
f99303eb | 149 | ulong[128] tracebuf; |
b4c522fa IB |
150 | ulong[] m_trace; |
151 | ||
152 | ||
f99303eb | 153 | static ulong[] traceNoSync(ulong[] buffer, size_t skip, CONTEXT* context) @nogc |
b4c522fa IB |
154 | { |
155 | auto dbghelp = DbgHelp.get(); | |
156 | if (dbghelp is null) | |
157 | return []; // dbghelp.dll not available | |
158 | ||
f99303eb IB |
159 | if (buffer.length >= 63 && RtlCaptureStackBackTrace !is null && |
160 | context is null) | |
b4c522fa | 161 | { |
f99303eb IB |
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); | |
b4c522fa IB |
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. | |
f99303eb | 175 | if (backtraceLength > 1 && backtraceLength < 63 - skip) |
b4c522fa IB |
176 | { |
177 | debug(PRINTF) printf("Using result from RtlCaptureStackBackTrace\n"); | |
f99303eb | 178 | version (Win32) |
b4c522fa | 179 | { |
f99303eb | 180 | foreach (i, ref e; buffer[0 .. backtraceLength]) |
b4c522fa | 181 | { |
f99303eb | 182 | e = bufstorage[i]; |
b4c522fa | 183 | } |
b4c522fa | 184 | } |
f99303eb | 185 | return buffer[0..backtraceLength]; |
b4c522fa IB |
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 | ||
b4c522fa | 233 | size_t frameNum = 0; |
f99303eb | 234 | size_t nframes = 0; |
b4c522fa IB |
235 | |
236 | // do ... while so that we don't skip the first stackframe | |
237 | do | |
238 | { | |
b4c522fa IB |
239 | if (frameNum >= skip) |
240 | { | |
f99303eb | 241 | buffer[nframes++] = stackframe.AddrPC.Offset; |
b4c522fa IB |
242 | } |
243 | frameNum++; | |
244 | } | |
245 | while (dbghelp.StackWalk64(imageType, hProcess, hThread, &stackframe, | |
246 | &ctxt, null, null, null, null)); | |
f99303eb | 247 | return buffer[0 .. nframes]; |
b4c522fa IB |
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; | |
0da83a16 | 262 | TCHAR[1024] _buf = void; |
b4c522fa IB |
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 | { | |
92dd3e71 IB |
272 | char[] res; |
273 | if (dbghelp.SymGetSymFromAddr64(hProcess, pc, null, symbol) && | |
274 | *symbol.Name.ptr) | |
b4c522fa | 275 | { |
92dd3e71 IB |
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); | |
b4c522fa | 283 | else |
92dd3e71 | 284 | res = formatStackFrame(cast(void*)pc, symbol.Name.ptr); |
b4c522fa | 285 | } |
92dd3e71 IB |
286 | else |
287 | res = formatStackFrame(cast(void*)pc); | |
288 | trace ~= res; | |
b4c522fa IB |
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)]; | |
c8dfa79c IB |
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 | |
b4c522fa IB |
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, | |
92dd3e71 | 322 | const scope char* fileName, uint lineNum) |
b4c522fa IB |
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; | |
0da83a16 | 375 | char[2048] temp = void; |
b4c522fa IB |
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 | ||
8da8c7d3 | 430 | InitializeCriticalSection(&mutex); |
b4c522fa IB |
431 | initialized = true; |
432 | } |