1 module runtime.system.windows.dbghelp;
2 
3 
4 version(Windows):
5 @nogc nothrow:
6 
7 import libd.threading.locks, runtime.system.windows;
8 
9 __gshared Lockable!DebugHelp g_debugHelp;
10 
11 private alias SymInitializeT = extern(Windows) BOOL function(
12     HANDLE hProcess,
13     PCSTR  UserSearchPath,
14     BOOL   fInvadeProcess
15 );
16 
17 private alias CaptureStackBackTraceT = extern(Windows) USHORT function(
18     @_In_      ULONG  FramesToSkip,
19     @_In_      ULONG  FramesToCapture,
20     @_Out_     PVOID  *BackTrace,
21     @_Out_opt_ PULONG BackTraceHash
22 );
23 
24 private alias SymGetLineFromAddr64 = extern(Windows) BOOL function(
25     HANDLE           hProcess,
26     DWORD64          qwAddr,
27     PDWORD           pdwDisplacement,
28     PIMAGEHLP_LINE64 Line64
29 );
30 
31 private alias SymFromAddrT = extern(Windows) BOOL function(
32     HANDLE       hProcess,
33     DWORD64      Address,
34     PDWORD64     Displacement,
35     PSYMBOL_INFO Symbol
36 );
37 
38 private alias SymSetOptions = extern(Windows) DWORD function(
39     DWORD SymOptions
40 );
41 
42 private alias SymGetOptions = extern(Windows) DWORD function();
43 
44 private struct IMAGEHLP_LINE64 {
45     DWORD   SizeOfStruct;
46     PVOID   Key;
47     DWORD   LineNumber;
48     PCHAR   FileName;
49     DWORD64 Address;
50 }
51 private alias PIMAGEHLP_LINE64 = IMAGEHLP_LINE64*;
52 
53 private struct SYMBOL_INFO {
54     ULONG   SizeOfStruct;
55     ULONG   TypeIndex;
56     ULONG64[2] Reserved;
57     ULONG   Index;
58     ULONG   Size;
59     ULONG64 ModBase;
60     ULONG   Flags;
61     ULONG64 Value;
62     ULONG64 Address;
63     ULONG   Register;
64     ULONG   Scope;
65     ULONG   Tag;
66     ULONG   NameLen;
67     ULONG   MaxNameLen;
68     CHAR[1]    Name;
69 }
70 alias PSYMBOL_INFO = SYMBOL_INFO*;
71 
72 struct DebugHelpStackTrace
73 {
74     String symbol;
75     String file;
76     ulong symbolAddress;
77     uint line;
78     ulong lineAddress;
79 }
80 
81 private struct DebugHelp
82 {
83     enum MAX_SYMBOL_NAME = 1024; // D symbols are about as long as the average node_modules list.
84 
85     bool isAvailable;
86     SymInitializeT SymInitialize;
87     CaptureStackBackTraceT CaptureStackBackTrace;
88     SymGetLineFromAddr64 SymGetLineFromAddr;
89     SymFromAddrT SymFromAddr;
90 
91     @nogc nothrow:
92 
93     DebugHelpStackTrace[Amount] backtrace(size_t Amount)(ULONG framesToSkip, out size_t count)
94     {
95         typeof(return) traces;
96 
97         void*[Amount] frames;
98         count = this.CaptureStackBackTrace(
99             framesToSkip+1,
100             Amount,
101             frames.ptr,
102             null
103         );
104 
105         ubyte[SYMBOL_INFO.sizeof + MAX_SYMBOL_NAME] buffer;
106         auto asInfo = cast(PSYMBOL_INFO)buffer.ptr;
107         asInfo.SizeOfStruct = SYMBOL_INFO.sizeof;
108         asInfo.MaxNameLen = MAX_SYMBOL_NAME;
109 
110         auto process = GetCurrentProcess();
111 
112         foreach(i; 0..count)
113         {
114             const address = frames[i];
115             const symFound = this.SymFromAddr(
116                 process,
117                 cast(DWORD64)address,
118                 null,
119                 asInfo
120             );
121 
122             if(!symFound)
123             {
124                 traces[i] = DebugHelpStackTrace(
125                     String("[COULD NOT FIND]"),
126                     String("???"),
127                     0,
128                     0,
129                     0,
130                 );
131                 continue;
132             }
133 
134             IMAGEHLP_LINE64 line;
135             line.SizeOfStruct = IMAGEHLP_LINE64.sizeof;
136 
137             DWORD thisParamIsntOptionalBtw;            
138             const lineFound = this.SymGetLineFromAddr(
139                 process, 
140                 cast(DWORD64)address, 
141                 &thisParamIsntOptionalBtw, 
142                 &line
143             );
144 
145             version(unittest)
146             {
147                 if(!lineFound && GetLastError() != 487) // invalid address
148                 {
149                     import libd.console;
150                     consoleWriteln(
151                         "[unittest-only][libd-runtime] Could not find line information: "
152                         .ansi.fg(Ansi4BitColour.red),
153                         asInfo.Name.ptr[0..asInfo.NameLen],
154                         " -> ",
155                         GetLastError(),
156                         ' ',
157                         GetLastErrorAsString()
158                     );
159                 }
160             }
161 
162             traces[i] = DebugHelpStackTrace(
163                 String(asInfo.Name.ptr[0..asInfo.NameLen]),
164                 String(line.FileName),
165                 asInfo.Address,
166                 line.LineNumber,
167                 line.Address,
168             );
169         }
170         return traces;
171     }
172 }
173 
174 @nogc nothrow
175 void _d_init_dbghlp()
176 {
177     import libd.console;
178 
179     auto dll = LoadLibraryA("dbghelp.dll");
180     auto nt  = LoadLibraryA("NtDll.dll");
181     if(!dll)
182     {
183         debug consoleWriteln(
184             "[debug-only][libd-runtime] Could not load dbghelp.dll - Stack trace disabled."
185             .ansi.fg(Ansi4BitColour.yellow)
186         );
187         return;
188     }
189     if(!nt)
190     {
191         debug consoleWriteln(
192             "[debug-only][libd-runtime] Could not load NtDll.dll - Stack trace disabled."
193             .ansi.fg(Ansi4BitColour.yellow)
194         );
195         return;
196     }
197 
198     g_debugHelp.access((scope ref help)
199     {
200         auto process = GetCurrentProcess();
201         
202         static T loadFunc(T)(HMODULE dll, LPCSTR name, ref bool wasFailure)
203         {
204             auto ptr = cast(T)GetProcAddress(dll, name);
205             if(ptr is null)
206             {
207                 wasFailure = true;
208                 debug consoleWriteln(
209                     "[debug-only][libd-runtime] Could not load dbghelp.dll function - Stack trace disabled: "
210                     .ansi.fg(Ansi4BitColour.yellow),
211                     String(name)
212                 );
213             }
214 
215             return ptr;
216         }
217 
218         bool wasFailure;
219         auto symInit  = loadFunc!SymInitializeT         (dll, "SymInitialize", wasFailure);
220         auto capTrace = loadFunc!CaptureStackBackTraceT (nt, "RtlCaptureStackBackTrace", wasFailure);
221         auto getLine  = loadFunc!SymGetLineFromAddr64   (dll, "SymGetLineFromAddr64", wasFailure);
222         auto fromAddr = loadFunc!SymFromAddrT           (dll, "SymFromAddr", wasFailure);
223         auto setOpt   = loadFunc!SymSetOptions          (dll, "SymSetOptions", wasFailure);
224         auto getOpt   = loadFunc!SymGetOptions          (dll, "SymGetOptions", wasFailure);
225 
226         if(wasFailure)
227             return;
228 
229         help.SymInitialize = symInit;
230         help.CaptureStackBackTrace = capTrace;
231         help.SymGetLineFromAddr = getLine;
232         help.SymFromAddr = fromAddr;
233 
234         enum SYMOPT_FAIL_CRITICAL_ERRORS = 0x00000200;
235         enum SYMOPT_LOAD_LINES = 0x00000010;
236         enum SYMOPT_NO_PROMPTS = 0x00080000;
237         enum SYMOPT_UNDNAME = 0x00000002;
238         enum SYMOPT_DEFERRED_LOADS = 0x00000004;
239         const currOpt = getOpt();
240         setOpt(
241             currOpt
242             | SYMOPT_FAIL_CRITICAL_ERRORS
243             | SYMOPT_LOAD_LINES
244             | SYMOPT_NO_PROMPTS
245             | SYMOPT_UNDNAME
246             | SYMOPT_DEFERRED_LOADS
247         );
248 
249         if(!help.SymInitialize(process, null, true))
250         {
251             debug consoleWriteln(
252                 "[debug-only][libd-runtime] SymInitialize failed - Stack trace disabled"
253                 .ansi.fg(Ansi4BitColour.yellow)
254             );
255             return;
256         }
257 
258         help.isAvailable = true;
259     });
260 
261     return;
262 }