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