1 module ddebug.common.debugger; 2 3 import core.thread; 4 import dlangui.core.logger; 5 import ddebug.common.queue; 6 import ddebug.common.execution; 7 import std.array : empty; 8 import std.algorithm : startsWith, endsWith, equal; 9 import std..string : format; 10 11 enum DebuggingState { 12 loaded, 13 running, 14 paused, 15 stopped 16 } 17 18 enum StateChangeReason { 19 unknown, 20 breakpointHit, 21 endSteppingRange, 22 exception, 23 exited, 24 } 25 26 class LocationBase { 27 string file; 28 string fullFilePath; 29 string projectFilePath; 30 string from; 31 int line; 32 this() {} 33 this(LocationBase v) { 34 file = v.file; 35 fullFilePath = v.fullFilePath; 36 projectFilePath = v.projectFilePath; 37 line = v.line; 38 from = v.from; 39 } 40 LocationBase clone() { return new LocationBase(this); } 41 } 42 43 class Breakpoint : LocationBase { 44 int id; 45 bool enabled = true; 46 string projectName; 47 this() { 48 id = _nextBreakpointId++; 49 } 50 this(Breakpoint v) { 51 super(v); 52 id = v.id; 53 enabled = v.enabled; 54 projectName = v.projectName; 55 } 56 override Breakpoint clone() { 57 return new Breakpoint(this); 58 } 59 } 60 61 class DebugFrame : LocationBase { 62 ulong address; 63 string func; 64 int level; 65 DebugVariableList locals; 66 67 @property string formattedAddress() { 68 if (address < 0x100000000) { 69 return "%08x".format(address); 70 } else { 71 return "%016x".format(address); 72 } 73 } 74 75 this() {} 76 this(DebugFrame v) { 77 super(v); 78 address = v.address; 79 func = v.func; 80 level = v.level; 81 if (v.locals) 82 locals = new DebugVariableList(v.locals); 83 } 84 override DebugFrame clone() { return new DebugFrame(this); } 85 86 void fillMissingFields(LocationBase v) { 87 if (file.empty) 88 file = v.file; 89 if (fullFilePath.empty) 90 fullFilePath = v.fullFilePath; 91 if (projectFilePath.empty) 92 projectFilePath = v.projectFilePath; 93 if (!line) 94 line = v.line; 95 } 96 } 97 98 class DebugThread { 99 ulong id; 100 string name; 101 DebugFrame frame; 102 DebuggingState state; 103 DebugStack stack; 104 105 this() { 106 } 107 this(DebugThread v) { 108 id = v.id; 109 name = v.name; 110 if (v.frame) 111 frame = new DebugFrame(v.frame); 112 state = v.state; 113 if (v.stack) 114 stack = new DebugStack(v.stack); 115 } 116 DebugThread clone() { return new DebugThread(this); } 117 118 @property string displayName() { 119 return "%u: %s".format(id, name); 120 } 121 122 @property int length() { 123 if (stack && stack.length > 0) 124 return stack.length; 125 if (frame) 126 return 1; 127 return 0; 128 } 129 DebugFrame opIndex(int index) { 130 if (index < 0 || index > length) 131 return null; 132 if (stack && stack.length > 0) 133 return stack[index]; 134 if (frame && index == 0) 135 return frame; 136 return null; 137 } 138 } 139 140 class DebugThreadList { 141 DebugThread[] threads; 142 ulong currentThreadId; 143 this() {} 144 this(DebugThreadList v) { 145 currentThreadId = v.currentThreadId; 146 foreach(t; v.threads) 147 threads ~= new DebugThread(t); 148 } 149 DebugThreadList clone() { return new DebugThreadList(this); } 150 151 @property DebugThread currentThread() { 152 return findThread(currentThreadId); 153 } 154 DebugThread findThread(ulong id) { 155 foreach(t; threads) 156 if (t.id == id) 157 return t; 158 return null; 159 } 160 @property int length() { return cast(int)threads.length; } 161 DebugThread opIndex(int index) { return threads[index]; } 162 } 163 164 class DebugStack { 165 DebugFrame[] frames; 166 167 this() {} 168 this(DebugStack v) { 169 foreach(t; v.frames) 170 frames ~= new DebugFrame(t); 171 } 172 173 @property int length() { return cast(int)frames.length; } 174 DebugFrame opIndex(int index) { return frames[index]; } 175 } 176 177 class DebugVariable { 178 string name; 179 string type; 180 string value; 181 DebugVariable[] children; 182 183 this() {} 184 /// deep copy 185 this(DebugVariable v) { 186 name = v.name; 187 type = v.type; 188 value = v.value; 189 // deep copy of child vars 190 foreach(item; v.children) 191 children ~= new DebugVariable(item); 192 } 193 } 194 195 class DebugVariableList { 196 DebugVariable[] variables; 197 this() {} 198 this(DebugVariableList v) { 199 foreach(t; v.variables) 200 variables ~= new DebugVariable(t); 201 } 202 203 @property int length() { return variables ? cast(int)variables.length : 0; } 204 205 DebugVariable opIndex(int index) { 206 if (!variables || index < 0 || index > variables.length) 207 return null; 208 return variables[index]; 209 } 210 } 211 212 static __gshared _nextBreakpointId = 1; 213 214 interface Debugger : ProgramExecution { 215 void setDebuggerCallback(DebuggerCallback callback); 216 void setDebuggerExecutable(string debuggerExecutable); 217 218 /// can be called after program is loaded 219 void execStart(); 220 /// continue execution 221 void execContinue(); 222 /// stop program execution 223 void execStop(); 224 /// interrupt execution 225 void execPause(); 226 /// step over 227 void execStepOver(ulong threadId); 228 /// step in 229 void execStepIn(ulong threadId); 230 /// step out 231 void execStepOut(ulong threadId); 232 /// restart 233 void execRestart(); 234 235 /// update list of breakpoints 236 void setBreakpoints(Breakpoint[] bp); 237 238 /// request stack trace and local vars for thread and frame 239 void requestDebugContextInfo(ulong threadId, int frame); 240 } 241 242 interface DebuggerCallback : ProgramExecutionStatusListener { 243 /// debugger message line 244 void onDebuggerMessage(string msg); 245 246 /// debugger is started and loaded program, you can set breakpoints at this time 247 void onProgramLoaded(bool successful, bool debugInfoLoaded); 248 249 /// state changed: running / paused / stopped 250 void onDebugState(DebuggingState state, StateChangeReason reason, DebugFrame location, Breakpoint bp); 251 252 void onResponse(ResponseCode code, string msg); 253 254 /// send debug context (threads, stack frames, local vars...) 255 void onDebugContextInfo(DebugThreadList info, ulong threadId, int frame); 256 } 257 258 enum ResponseCode : int { 259 /// Operation finished successfully 260 Ok = 0, 261 262 // more success codes here 263 264 /// General purpose failure code 265 Fail = 1000, 266 /// method is not implemented 267 NotImplemented, 268 /// error running debugger 269 CannotRunDebugger, 270 271 // more error codes here 272 } 273 274 alias Runnable = void delegate(); 275 276 //interface Debugger { 277 // /// start debugging 278 // void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response); 279 //} 280 281 282 283 /// proxy for debugger interface implementing async calls 284 class DebuggerProxy : Debugger, DebuggerCallback { 285 private DebuggerBase _debugger; 286 private void delegate(void delegate() runnable) _callbackDelegate; 287 288 this(DebuggerBase debugger, void delegate(void delegate() runnable) callbackDelegate) { 289 _debugger = debugger; 290 _callbackDelegate = callbackDelegate; 291 } 292 293 /// returns true if it's debugger 294 @property bool isDebugger() { return true; } 295 /// returns true if it's mago debugger 296 @property bool isMagoDebugger() { return _debugger.isMagoDebugger; } 297 298 /// executable file 299 @property string executableFile() { return _debugger.executableFile; } 300 /// returns execution status 301 //@property ExecutionStatus status(); 302 303 void setExecutableParams(string executableFile, string[] args, string workingDir, string[string] envVars) { 304 _debugger.setExecutableParams(executableFile, args, workingDir, envVars); 305 } 306 307 /// set external terminal parameters before execution 308 void setTerminalExecutable(string terminalExecutable) { 309 _debugger.setTerminalExecutable(terminalExecutable); 310 } 311 312 /// set terminal device name before execution 313 void setTerminalTty(string terminalTty) { 314 _debugger.setTerminalTty(terminalTty); 315 } 316 317 /// set debugger executable 318 void setDebuggerExecutable(string debuggerExecutable) { 319 _debugger.setDebuggerExecutable(debuggerExecutable); 320 } 321 322 protected DebuggerCallback _callback; 323 /// set debugger callback 324 void setDebuggerCallback(DebuggerCallback callback) { 325 _callback = callback; 326 _debugger.setDebuggerCallback(this); 327 } 328 329 /// called when program execution is stopped 330 void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) { 331 DebuggerProxy proxy = this; 332 _callbackDelegate( delegate() { _callback.onProgramExecutionStatus(proxy, status, exitCode); } ); 333 } 334 335 /// debugger is started and loaded program, you can set breakpoints at this time 336 void onProgramLoaded(bool successful, bool debugInfoLoaded) { 337 _callbackDelegate( delegate() { _callback.onProgramLoaded(successful, debugInfoLoaded); } ); 338 } 339 340 /// state changed: running / paused / stopped 341 void onDebugState(DebuggingState state, StateChangeReason reason, DebugFrame location, Breakpoint bp) { 342 _callbackDelegate( delegate() { _callback.onDebugState(state, reason, location, bp); } ); 343 } 344 345 /// send debug context (threads, stack frames, local vars...) 346 void onDebugContextInfo(DebugThreadList info, ulong threadId, int frame) { 347 _callbackDelegate( delegate() { _callback.onDebugContextInfo(info, threadId, frame); } ); 348 } 349 350 void onResponse(ResponseCode code, string msg) { 351 _callbackDelegate( delegate() { _callback.onResponse(code, msg); } ); 352 } 353 354 void onDebuggerMessage(string msg) { 355 _callbackDelegate( delegate() { _callback.onDebuggerMessage(msg); } ); 356 } 357 358 /// start execution 359 void run() { 360 Log.d("DebuggerProxy.run()"); 361 _debugger.run(); 362 //_debugger.postRequest(delegate() { _debugger.run(); }); 363 } 364 /// stop execution 365 void stop() { 366 Log.d("DebuggerProxy.stop()"); 367 _debugger.stop(); 368 //_debugger.postRequest(delegate() { _debugger.stop(); }); 369 } 370 371 /// start execution, can be called after program is loaded 372 void execStart() { 373 _debugger.postRequest(delegate() { _debugger.execStart(); }); 374 } 375 /// continue program 376 void execContinue() { 377 _debugger.postRequest(delegate() { _debugger.execContinue(); }); 378 } 379 /// stop program execution 380 void execStop() { 381 _debugger.postRequest(delegate() { _debugger.execStop(); }); 382 } 383 /// interrupt execution 384 void execPause() { 385 _debugger.postRequest(delegate() { _debugger.execPause(); }); 386 } 387 /// step over 388 void execStepOver(ulong threadId) { 389 _debugger.postRequest(delegate() { _debugger.execStepOver(threadId); }); 390 } 391 /// step in 392 void execStepIn(ulong threadId) { 393 _debugger.postRequest(delegate() { _debugger.execStepIn(threadId); }); 394 } 395 /// step out 396 void execStepOut(ulong threadId) { 397 _debugger.postRequest(delegate() { _debugger.execStepOut(threadId); }); 398 } 399 /// restart 400 void execRestart() { 401 _debugger.postRequest(delegate() { _debugger.execRestart(); }); 402 } 403 /// request stack trace and local vars for thread and frame 404 void requestDebugContextInfo(ulong threadId, int frame) { 405 _debugger.postRequest(delegate() { _debugger.requestDebugContextInfo(threadId, frame); }); 406 } 407 /// update list of breakpoints 408 void setBreakpoints(Breakpoint[] breakpoints) { 409 Breakpoint[] cloned; 410 foreach(bp; breakpoints) 411 cloned ~= bp.clone; 412 _debugger.postRequest(delegate() { _debugger.setBreakpoints(cloned); }); 413 } 414 } 415 416 abstract class DebuggerBase : Thread, Debugger { 417 protected bool _runRequested; 418 protected bool _stopRequested; 419 private bool _finished; 420 protected BlockingQueue!Runnable _queue; 421 422 protected ExecutionStatus _status = ExecutionStatus.NotStarted; 423 protected int _exitCode = 0; 424 425 /// provides _executableFile, _executableArgs, _executableWorkingDir, _executableEnvVars parameters and setter function setExecutableParams 426 mixin ExecutableParams; 427 /// provides _terminalExecutable, _terminalTty, setTerminalExecutable, and setTerminalTty 428 mixin TerminalParams; 429 430 protected DebuggerCallback _callback; 431 void setDebuggerCallback(DebuggerCallback callback) { 432 _callback = callback; 433 } 434 435 protected string _debuggerExecutable; 436 void setDebuggerExecutable(string debuggerExecutable) { 437 _debuggerExecutable = debuggerExecutable; 438 } 439 440 /// returns true if it's mago debugger 441 @property bool isMagoDebugger() { 442 import std..string; 443 return _debuggerExecutable.indexOf("mago-mi") >= 0; 444 } 445 446 447 @property bool isDebugger() { return true; } 448 449 @property string executableFile() { 450 return _executableFile; 451 } 452 453 void postRequest(Runnable request) { 454 _queue.put(request); 455 } 456 457 this() { 458 super(&threadFunc); 459 _queue = new BlockingQueue!Runnable(); 460 } 461 462 ~this() { 463 //stop(); 464 //destroy(_queue); 465 _queue = null; 466 } 467 468 // call from GUI thread 469 void run() { 470 Log.d("DebuggerBase.run()"); 471 assert(!_runRequested); 472 _runRequested = true; 473 postRequest(&startDebugging); 474 start(); 475 } 476 477 void startDebugging() { 478 // override to implement 479 } 480 481 void stop() { 482 Log.i("Debugger.stop()"); 483 if (_stopRequested) 484 return; 485 _stopRequested = true; 486 _queue.close(); 487 } 488 489 bool _threadStarted; 490 protected void onDebuggerThreadStarted() { 491 _threadStarted = true; 492 } 493 494 protected void onDebuggerThreadFinished() { 495 _callback.onProgramExecutionStatus(this, _status, _exitCode); 496 } 497 498 /// thread func: execute all tasks from queue 499 private void threadFunc() { 500 onDebuggerThreadStarted(); 501 Log.i("Debugger thread started"); 502 try { 503 while (!_stopRequested) { 504 Runnable task; 505 if (_queue.get(task, 0)) { 506 task(); 507 } 508 } 509 } catch (Exception e) { 510 Log.e("Exception in debugger thread", e); 511 } 512 Log.i("Debugger thread finished"); 513 _finished = true; 514 onDebuggerThreadFinished(); 515 } 516 517 } 518 519 /// helper for removing class array item by ref 520 T removeItem(T)(ref T[]array, T item) { 521 for (int i = cast(int)array.length - 1; i >= 0; i--) { 522 if (array[i] is item) { 523 for (int j = i; j < array.length - 1; j++) 524 array[j] = array[j + 1]; 525 array.length = array.length - 1; 526 return item; 527 } 528 } 529 return null; 530 } 531 532 /// helper for removing array item by index 533 T removeItem(T)(ref T[]array, ulong index) { 534 if (index >= 0 && index < array.length) { 535 T res = array[index]; 536 for (int j = cast(int)index; j < array.length - 1; j++) 537 array[j] = array[j + 1]; 538 array.length = array.length - 1; 539 return res; 540 } 541 return null; 542 } 543