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 }