1 // just an attempt to implement D debugger for win32
2 module ddebug.windows.windebug;
3 
4 version(Windows):
5 version(USE_WIN_DEBUG):
6 
7 import dlangui.core.logger;
8 import win32.psapi;
9 import win32.windows;
10 
11 import std.utf;
12 import core.thread;
13 import std.format;
14 
15 class ModuleInfo {
16     HANDLE hFile;
17     ulong baseOfImage;
18     ulong debugInfoFileOffset;
19     ulong debugInfoSize;
20     string imageFileName;
21 }
22 
23 class DllInfo : ModuleInfo {
24     ProcessInfo process;
25     this(ProcessInfo baseProcess, ref DEBUG_EVENT di) {
26         process = baseProcess;
27         hFile = di.LoadDll.hFile;
28         baseOfImage = cast(ulong)di.LoadDll.lpBaseOfDll;
29         debugInfoFileOffset = di.LoadDll.dwDebugInfoFileOffset;
30         debugInfoSize = di.LoadDll.nDebugInfoSize;
31         ulong imageName = cast(ulong)di.LoadDll.lpImageName;
32         Log.d(format("imageName address: %x", imageName));
33         imageFileName = getFileNameFromHandle(hFile);
34         //imageFileName = decodeZString(di.LoadDll.lpImageName, di.LoadDll.fUnicode != 0);
35         //if (imageFileName.length == 0)
36         //    imageFileName = getModuleFileName(process.hProcess, hFile);
37     }
38 }
39 
40 class ProcessInfo : ModuleInfo {
41 	HANDLE hProcess;
42     uint processId;
43 	HANDLE hThread;
44 	ulong threadLocalBase;
45 	ulong startAddress; //LPTHREAD_START_ROUTINE
46 
47     this(ref DEBUG_EVENT di) {
48 	    hFile = di.CreateProcessInfo.hFile;
49 	    hProcess = di.CreateProcessInfo.hProcess;
50         processId = di.dwProcessId;
51 	    hThread = di.CreateProcessInfo.hThread;
52 	    LPVOID lpBaseOfImage;
53         baseOfImage = cast(ulong)di.CreateProcessInfo.lpBaseOfImage;
54         debugInfoFileOffset = di.CreateProcessInfo.dwDebugInfoFileOffset;
55         debugInfoSize = di.CreateProcessInfo.nDebugInfoSize;
56         threadLocalBase = cast(ulong)di.CreateProcessInfo.lpThreadLocalBase;
57         startAddress = cast(ulong)di.CreateProcessInfo.lpStartAddress;
58         //imageFileName = decodeZString(di.CreateProcessInfo.lpImageName, di.CreateProcessInfo.fUnicode != 0);
59         //if (imageFileName.length == 0)
60         imageFileName = getFileNameFromHandle(hFile);
61 //            imageFileName = getModuleFileName(hProcess, hFile);
62     }
63 }
64 
65 private string decodeZString(void * pstr, bool isUnicode) {
66     if (!pstr)
67         return null;
68     if (isUnicode) {
69         wchar * ptr = cast(wchar*)pstr;
70         wchar[] buf;
71         for(; *ptr; ptr++)
72             buf ~= *ptr;
73         return toUTF8(buf);
74     } else {
75         char * ptr = cast(char*)pstr;
76         char[] buf;
77         for(; *ptr; ptr++)
78             buf ~= *ptr;
79         return buf.dup;
80     }
81 }
82 
83 private string getModuleFileName(HANDLE hProcess, HANDLE hFile) {
84     //wchar[4096] buf;
85     //uint chars = GetModuleFileNameExW(hProcess, hFile, buf.ptr, 4096);
86     //return toUTF8(buf[0..chars]);
87     return null;
88 }
89 
90 // based on sample from MSDN https://msdn.microsoft.com/ru-ru/library/windows/desktop/aa366789(v=vs.85).aspx
91 string getFileNameFromHandle(HANDLE hFile) 
92 {
93     string res = null;
94     bool bSuccess = false;
95     const int BUFSIZE = 4096;
96     wchar[BUFSIZE + 1] pszFilename;
97     HANDLE hFileMap;
98 
99     // Get the file size.
100     DWORD dwFileSizeHi = 0;
101     DWORD dwFileSizeLo = GetFileSize(hFile, &dwFileSizeHi); 
102 
103     if( dwFileSizeLo == 0 && dwFileSizeHi == 0 ) {
104        return null;
105     }
106 
107     // Create a file mapping object.
108     hFileMap = CreateFileMapping(hFile, 
109                                  null, 
110                     PAGE_READONLY,
111                     0, 
112                     1,
113                                  null);
114 
115     if (hFileMap) {
116         // Create a file mapping to get the file name.
117         void* pMem = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 1);
118 
119         if (pMem) {
120             if (win32.psapi.GetMappedFileNameW(GetCurrentProcess(), 
121                                  pMem,
122                                  pszFilename.ptr,
123                                  MAX_PATH)) 
124             {
125 
126                 // Translate path with device name to drive letters.
127                 TCHAR[BUFSIZE] szTemp;
128                 szTemp[0] = '\0';
129 
130                 size_t uFilenameLen = 0;
131                 for (int i = 0; i < MAX_PATH && pszFilename[i]; i++)
132                     uFilenameLen++;
133 
134                 if (GetLogicalDriveStrings(BUFSIZE-1, szTemp.ptr)) {
135                     wchar[MAX_PATH] szName;
136                     wchar[3] szDrive = [' ', ':', 0];
137                     bool bFound = false;
138                     wchar* p = szTemp.ptr;
139 
140                     do {
141                         // Copy the drive letter to the template string
142                         szDrive[0] = *p;
143 
144                         // Look up each device name
145                         if (QueryDosDevice(szDrive.ptr, szName.ptr, MAX_PATH)) {
146                             size_t uNameLen = 0;
147                             for (int i = 0; i < MAX_PATH && szName[i]; i++)
148                                 uNameLen++;
149                             //_tcslen(szName);
150 
151                             if (uNameLen < MAX_PATH) {
152                                 bFound = false; //_tcsnicmp(pszFilename, szName, uNameLen) == 0
153                                      //&& *(pszFilename + uNameLen) == _T('\\');
154                                 for (int i = 0; pszFilename[i] && i <= uNameLen; i++) {
155                                     wchar c1 = pszFilename[i];
156                                     wchar c2 = szName[i];
157                                     if (c1 >= 'a' && c1 <= 'z') 
158                                         c1 = cast(wchar)(c1 - 'a' + 'A');
159                                     if (c2 >= 'a' && c2 <= 'z') 
160                                         c2 = cast(wchar)(c2 - 'a' + 'A');
161                                     if (c1 != c2) {
162                                         if (c1 == '\\' && c2 == 0)
163                                             bFound = true;
164                                         break;
165                                     }
166                                 }
167 
168                                 if (bFound) {
169                                     // Reconstruct pszFilename using szTempFile
170                                     // Replace device path with DOS path
171                                     res = toUTF8(szDrive[0..2] ~ pszFilename[uNameLen .. uFilenameLen]);
172                                 }
173                             }
174                         }
175 
176                         // Go to the next NULL character.
177                         while (*p++) {
178                         }
179                     } while (!bFound && *p); // end of string
180                 }
181             }
182             UnmapViewOfFile(pMem);
183         } 
184 
185         CloseHandle(hFileMap);
186     }
187     return res;
188 }
189 
190 class WinDebugger : Thread {
191     string _exefile;
192     string _args;
193 
194     DllInfo[] _dlls;
195     ProcessInfo[] _processes;
196 
197     this(string exefile, string args) {
198         super(&run);
199         _exefile = exefile;
200         _args = args;
201     }
202 
203     private void run() {
204         Log.i("Debugger thread started");
205         if (startDebugging())
206             enterDebugLoop();
207         Log.i("Debugger thread finished");
208         _finished = true;
209     }
210 
211     private shared bool _finished;
212     STARTUPINFOW _si; 
213     PROCESS_INFORMATION _pi;
214 
215     bool startDebugging() {
216 
217         Log.i("starting debug for '" ~ _exefile ~ "' args: " ~ _args);
218 
219         _stopRequested = false;
220         _si = STARTUPINFOW.init;
221         _si.cb = _si.sizeof;
222         _pi = PROCESS_INFORMATION.init;
223 
224         string cmdline = "\"" ~ _exefile ~ "\"";
225         if (_args.length > 0)
226             cmdline = cmdline ~ " " ~ _args;
227         wchar[] exefilew = cast(wchar[])toUTF16(_exefile);
228         exefilew ~= cast(dchar)0;
229         wchar[] cmdlinew = cast(wchar[])toUTF16(cmdline);
230         cmdlinew ~= cast(dchar)0;
231         if (!CreateProcessW(cast(const wchar*)exefilew.ptr, 
232                             cmdlinew.ptr, 
233                             cast(SECURITY_ATTRIBUTES*)NULL, cast(SECURITY_ATTRIBUTES*)NULL, 
234                             FALSE, 
235                             DEBUG_ONLY_THIS_PROCESS, 
236                             NULL, 
237                             cast(const wchar*)NULL, &_si, &_pi)) {
238             return false;
239         }
240         Log.i("Executable '" ~ _exefile ~ "' started successfully");
241         return true;
242     }
243 
244     uint onCreateThreadDebugEvent(ref DEBUG_EVENT debug_event) {
245         Log.d("onCreateThreadDebugEvent");
246         return DBG_CONTINUE;
247     }
248 
249     uint onCreateProcessDebugEvent(ref DEBUG_EVENT debug_event) {
250         ProcessInfo pi = new ProcessInfo(debug_event);
251         _processes ~= pi;
252         Log.d("onCreateProcessDebugEvent " ~ pi.imageFileName ~ " debugInfoSize=" ~ format("%d", pi.debugInfoSize));
253         return DBG_CONTINUE;
254     }
255 
256     uint onExitThreadDebugEvent(ref DEBUG_EVENT debug_event) {
257         Log.d("onExitThreadDebugEvent");
258         return DBG_CONTINUE;
259     }
260 
261     uint onExitProcessDebugEvent(ref DEBUG_EVENT debug_event) {
262         Log.d("onExitProcessDebugEvent");
263         return DBG_CONTINUE;
264     }
265     ProcessInfo findProcess(uint id) {
266         foreach(p; _processes) {
267             if (p.processId == id)
268                 return p;
269         }
270         return null;
271     }
272 
273     uint onLoadDllDebugEvent(ref DEBUG_EVENT debug_event) {
274         ProcessInfo pi = findProcess(debug_event.dwProcessId);
275         if (pi !is null) {
276             DllInfo dll = new DllInfo(pi, debug_event);
277             _dlls ~= dll;
278             Log.d("onLoadDllDebugEvent " ~ dll.imageFileName ~ " debugInfoSize=" ~ format("%d", dll.debugInfoSize));
279         } else {
280             Log.d("onLoadDllDebugEvent : process not found");
281         }
282         return DBG_CONTINUE;
283     }
284     uint onUnloadDllDebugEvent(ref DEBUG_EVENT debug_event) {
285         Log.d("onUnloadDllDebugEvent");
286         return DBG_CONTINUE;
287     }
288     uint onOutputDebugStringEvent(ref DEBUG_EVENT debug_event) {
289         Log.d("onOutputDebugStringEvent");
290         return DBG_CONTINUE;
291     }
292     uint onRipEvent(ref DEBUG_EVENT debug_event) {
293         Log.d("onRipEvent");
294         return DBG_TERMINATE_PROCESS;
295     }
296 
297     void processDebugEvent(ref DEBUG_EVENT debug_event) {
298         switch (debug_event.dwDebugEventCode)
299         { 
300             case EXCEPTION_DEBUG_EVENT:
301                 // Process the exception code. When handling 
302                 // exceptions, remember to set the continuation 
303                 // status parameter (dwContinueStatus). This value 
304                 // is used by the ContinueDebugEvent function. 
305 
306                 switch(debug_event.Exception.ExceptionRecord.ExceptionCode)
307                 { 
308                     case EXCEPTION_ACCESS_VIOLATION: 
309                         // First chance: Pass this on to the system. 
310                         // Last chance: Display an appropriate error. 
311                         break;
312 
313                     case EXCEPTION_BREAKPOINT: 
314                         // First chance: Display the current 
315                         // instruction and register values. 
316                         break;
317 
318                     case EXCEPTION_DATATYPE_MISALIGNMENT: 
319                         // First chance: Pass this on to the system. 
320                         // Last chance: Display an appropriate error. 
321                         break;
322 
323                     case EXCEPTION_SINGLE_STEP: 
324                         // First chance: Update the display of the 
325                         // current instruction and register values. 
326                         break;
327 
328                     case DBG_CONTROL_C: 
329                         // First chance: Pass this on to the system. 
330                         // Last chance: Display an appropriate error. 
331                         break;
332 
333                     default:
334                         // Handle other exceptions. 
335                         break;
336                 } 
337 
338                 break;
339 
340             case CREATE_THREAD_DEBUG_EVENT: 
341                 // As needed, examine or change the thread's registers 
342                 // with the GetThreadContext and SetThreadContext functions; 
343                 // and suspend and resume thread execution with the 
344                 // SuspendThread and ResumeThread functions. 
345 
346                 _continueStatus = onCreateThreadDebugEvent(debug_event);
347                 break;
348 
349             case CREATE_PROCESS_DEBUG_EVENT: 
350                 // As needed, examine or change the registers of the
351                 // process's initial thread with the GetThreadContext and
352                 // SetThreadContext functions; read from and write to the
353                 // process's virtual memory with the ReadProcessMemory and
354                 // WriteProcessMemory functions; and suspend and resume
355                 // thread execution with the SuspendThread and ResumeThread
356                 // functions. Be sure to close the handle to the process image
357                 // file with CloseHandle.
358 
359                 _continueStatus = onCreateProcessDebugEvent(debug_event);
360                 break;
361 
362             case EXIT_THREAD_DEBUG_EVENT: 
363                 // Display the thread's exit code. 
364 
365                 _continueStatus = onExitThreadDebugEvent(debug_event);
366                 break;
367 
368             case EXIT_PROCESS_DEBUG_EVENT: 
369                 // Display the process's exit code. 
370 
371                 _continueStatus = onExitProcessDebugEvent(debug_event);
372                 break;
373 
374             case LOAD_DLL_DEBUG_EVENT: 
375                 // Read the debugging information included in the newly 
376                 // loaded DLL. Be sure to close the handle to the loaded DLL 
377                 // with CloseHandle.
378 
379                 _continueStatus = onLoadDllDebugEvent(debug_event);
380                 break;
381 
382             case UNLOAD_DLL_DEBUG_EVENT: 
383                 // Display a message that the DLL has been unloaded. 
384 
385                 _continueStatus = onUnloadDllDebugEvent(debug_event);
386                 break;
387 
388             case OUTPUT_DEBUG_STRING_EVENT: 
389                 // Display the output debugging string. 
390 
391                 _continueStatus = onOutputDebugStringEvent(debug_event);
392                 break;
393 
394             case RIP_EVENT:
395                 _continueStatus = onRipEvent(debug_event);
396                 break;
397             default:
398                 // UNKNOWN EVENT
399                 break;
400         } 
401     }
402     
403     uint _continueStatus;
404     bool _stopRequested;
405 
406     bool enterDebugLoop() {
407         Log.i("entering debug loop");
408         _continueStatus = DBG_CONTINUE;
409         DEBUG_EVENT debug_event;
410         debug_event = DEBUG_EVENT.init;
411 
412         for(;;)
413         {
414             if (!WaitForDebugEvent(&debug_event, INFINITE)) {
415                 uint err = GetLastError();
416                 Log.e("WaitForDebugEvent returned false. Error=" ~ format("%08x", err));
417                 return false;
418             }
419             //Log.i("processDebugEvent");
420             processDebugEvent(debug_event);
421             if (_continueStatus == DBG_TERMINATE_PROCESS)
422                 break;
423             ContinueDebugEvent(debug_event.dwProcessId,
424                                debug_event.dwThreadId,
425                                _continueStatus);
426         }
427         Log.i("exiting debug loop");
428         return true;
429     }
430 }