1 module ddebug.gdb.gdbinterface; 2 3 public import ddebug.common.debugger; 4 import ddebug.common.execution; 5 import dlangui.core.logger; 6 import ddebug.common.queue; 7 import dlangide.builders.extprocess; 8 import ddebug.gdb.gdbmiparser; 9 import std.utf; 10 import std.conv : to; 11 import std.array : empty; 12 import std.algorithm : startsWith, endsWith, equal; 13 import core.thread; 14 15 abstract class ConsoleDebuggerInterface : DebuggerBase, TextWriter { 16 protected ExternalProcess _debuggerProcess; 17 18 protected ExternalProcessState runDebuggerProcess(string executable, string[]args, string dir) { 19 _debuggerProcess = new ExternalProcess(); 20 ExternalProcessState state = _debuggerProcess.run(executable, args, dir, this); 21 return state; 22 } 23 24 private string[] _stdoutLines; 25 private char[] _stdoutBuf; 26 /// return true to clear lines list 27 protected bool onDebuggerStdoutLines(string[] lines) { 28 foreach(line; lines) { 29 onDebuggerStdoutLine(line); 30 } 31 return true; 32 } 33 protected void onDebuggerStdoutLine(string line) { 34 } 35 private void onStdoutText(string text) { 36 Log.v("onStdoutText: ", text); 37 _stdoutBuf ~= text; 38 // pass full lines 39 int startPos = 0; 40 bool fullLinesFound = false; 41 for (int i = 0; i < _stdoutBuf.length; i++) { 42 if (_stdoutBuf[i] == '\n' || _stdoutBuf[i] == '\r') { 43 if (i <= startPos) 44 _stdoutLines ~= ""; 45 else 46 _stdoutLines ~= _stdoutBuf[startPos .. i].dup; 47 fullLinesFound = true; 48 if (i + 1 < _stdoutBuf.length) { 49 if ((_stdoutBuf[i] == '\n' && _stdoutBuf[i + 1] == '\r') 50 || (_stdoutBuf[i] == '\r' && _stdoutBuf[i + 1] == '\n')) 51 i++; 52 } 53 startPos = i + 1; 54 } 55 } 56 if (fullLinesFound) { 57 //Log.v("onStdoutText: full lines found"); 58 for (int i = 0; i + startPos < _stdoutBuf.length; i++) 59 _stdoutBuf[i] = _stdoutBuf[i + startPos]; 60 _stdoutBuf.length = _stdoutBuf.length - startPos; 61 if (onDebuggerStdoutLines(_stdoutLines)) { 62 _stdoutLines.length = 0; 63 } 64 } 65 } 66 67 bool sendLine(string text) { 68 return _debuggerProcess.write(text ~ "\n"); 69 } 70 71 /// log lines 72 override void writeText(dstring text) { 73 string text8 = toUTF8(text); 74 postRequest(delegate() { 75 onStdoutText(text8); 76 }); 77 } 78 79 } 80 81 interface TextCommandTarget { 82 /// send command as a text string 83 int sendCommand(string text, int commandId = 0); 84 /// reserve next command id 85 int reserveCommandId(); 86 } 87 88 import std.process; 89 class GDBInterface : ConsoleDebuggerInterface, TextCommandTarget { 90 91 this() { 92 _requests.setTarget(this); 93 } 94 95 // last command id 96 private int _commandId; 97 98 int reserveCommandId() { 99 _commandId++; 100 return _commandId; 101 } 102 103 int sendCommand(string text, int id = 0) { 104 ExternalProcessState state = _debuggerProcess.poll(); 105 if (state != ExternalProcessState.Running) { 106 _stopRequested = true; 107 return 0; 108 } 109 if (!id) 110 id = reserveCommandId(); 111 string cmd = to!string(id) ~ text; 112 Log.d("GDB command[", id, "]> ", text); 113 sendLine(cmd); 114 return id; 115 } 116 117 Pid terminalPid; 118 string externalTerminalTty; 119 120 string startTerminal() { 121 if (!_terminalTty.empty) 122 return _terminalTty; 123 Log.d("Starting terminal ", _terminalExecutable); 124 import std.random; 125 import std.file; 126 import std.path; 127 import std..string; 128 import core.thread; 129 uint n = uniform(0, 0x10000000, rndGen()); 130 externalTerminalTty = null; 131 string termfile = buildPath(tempDir, format("dlangide-term-name-%07x.tmp", n)); 132 Log.d("temp file for tty name: ", termfile); 133 try { 134 string[] args = [ 135 _terminalExecutable, 136 "-title", 137 "DLangIDE External Console", 138 "-e", 139 "echo 'DlangIDE External Console' && tty > " ~ termfile ~ " && sleep 1000000" 140 ]; 141 Log.d("Terminal command line: ", args); 142 terminalPid = spawnProcess(args); 143 for (int i = 0; i < 80; i++) { 144 Thread.sleep(dur!"msecs"(100)); 145 if (!isTerminalActive) { 146 Log.e("Failed to get terminal TTY"); 147 return null; 148 } 149 if (exists(termfile)) { 150 Thread.sleep(dur!"msecs"(20)); 151 break; 152 } 153 } 154 // read TTY from file 155 if (exists(termfile)) { 156 externalTerminalTty = readText(termfile); 157 if (externalTerminalTty.endsWith("\n")) 158 externalTerminalTty = externalTerminalTty[0 .. $-1]; 159 // delete file 160 remove(termfile); 161 Log.d("Terminal tty: ", externalTerminalTty); 162 } 163 } catch (Exception e) { 164 Log.e("Failed to start terminal ", e); 165 killTerminal(); 166 } 167 if (externalTerminalTty.length == 0) { 168 Log.i("Cannot start terminal"); 169 killTerminal(); 170 } else { 171 Log.i("Terminal: ", externalTerminalTty); 172 } 173 return externalTerminalTty; 174 } 175 176 bool isTerminalActive() { 177 if (!_terminalTty.empty) 178 return true; 179 if (_terminalExecutable.empty) 180 return true; 181 if (terminalPid is null) 182 return false; 183 auto res = tryWait(terminalPid); 184 if (res.terminated) { 185 Log.d("isTerminalActive: Terminal is stopped"); 186 wait(terminalPid); 187 terminalPid = Pid.init; 188 return false; 189 } else { 190 return true; 191 } 192 } 193 194 void killTerminal() { 195 if (!_terminalTty.empty) 196 return; 197 if (_terminalExecutable.empty) 198 return; 199 if (terminalPid is null) 200 return; 201 try { 202 Log.d("Trying to kill terminal"); 203 kill(terminalPid, 9); 204 Log.d("Waiting for terminal process termination"); 205 wait(terminalPid); 206 terminalPid = Pid.init; 207 Log.d("Killed"); 208 } catch (Exception e) { 209 Log.d("Exception while killing terminal", e); 210 terminalPid = Pid.init; 211 } 212 } 213 214 override void startDebugging() { 215 Log.d("GDBInterface.startDebugging()"); 216 string[] debuggerArgs; 217 if (!_terminalExecutable.empty || !_terminalTty.empty) { 218 externalTerminalTty = startTerminal(); 219 if (externalTerminalTty.length == 0) { 220 //_callback.onResponse(ResponseCode.CannotRunDebugger, "Cannot start terminal"); 221 _status = ExecutionStatus.Error; 222 _stopRequested = true; 223 return; 224 } 225 if (!USE_INIT_SEQUENCE) { 226 debuggerArgs ~= "-tty"; 227 debuggerArgs ~= externalTerminalTty; 228 } 229 } 230 debuggerArgs ~= "--interpreter"; 231 debuggerArgs ~= "mi2"; 232 debuggerArgs ~= "--silent"; 233 if (!USE_INIT_SEQUENCE) { 234 debuggerArgs ~= "--args"; 235 debuggerArgs ~= _executableFile; 236 foreach(arg; _executableArgs) 237 debuggerArgs ~= arg; 238 } 239 ExternalProcessState state = runDebuggerProcess(_debuggerExecutable, debuggerArgs, _executableWorkingDir); 240 Log.i("Debugger process state:", state); 241 if (USE_INIT_SEQUENCE) { 242 if (state == ExternalProcessState.Running) { 243 submitInitRequests(); 244 } else { 245 _status = ExecutionStatus.Error; 246 _stopRequested = true; 247 return; 248 } 249 } else { 250 if (state == ExternalProcessState.Running) { 251 Thread.sleep(dur!"seconds"(1)); 252 _callback.onProgramLoaded(true, true); 253 //sendCommand("-break-insert main"); 254 } else { 255 _status = ExecutionStatus.Error; 256 _stopRequested = true; 257 return; 258 } 259 } 260 } 261 262 immutable bool USE_INIT_SEQUENCE = true; 263 264 override protected void onDebuggerThreadFinished() { 265 Log.d("Debugger thread finished"); 266 if (_debuggerProcess !is null) { 267 Log.d("Killing debugger process"); 268 _debuggerProcess.kill(); 269 Log.d("Waiting for debugger process finishing"); 270 //_debuggerProcess.wait(); 271 } 272 killTerminal(); 273 Log.d("Sending execution status"); 274 _callback.onProgramExecutionStatus(this, _status, _exitCode); 275 } 276 277 bool _threadJoined = false; 278 override void stop() { 279 if (_stopRequested) { 280 Log.w("GDBInterface.stop() - _stopRequested flag already set"); 281 return; 282 } 283 _stopRequested = true; 284 Log.d("GDBInterface.stop()"); 285 postRequest(delegate() { 286 Log.d("GDBInterface.stop() processing in queue"); 287 execStop(); 288 }); 289 Thread.sleep(dur!"msecs"(200)); 290 postRequest(delegate() { 291 }); 292 _queue.close(); 293 if (!_threadJoined) { 294 _threadJoined = true; 295 if (_threadStarted) { 296 try { 297 join(); 298 } catch (Exception e) { 299 Log.e("Exception while trying to join debugger thread"); 300 } 301 } 302 } 303 } 304 305 /// start program execution, can be called after program is loaded 306 int _startRequestId; 307 void execStart() { 308 submitRequest("handle SIGUSR1 nostop noprint"); 309 submitRequest("handle SIGUSR2 nostop noprint"); 310 _startRequestId = submitRequest("-exec-run"); 311 } 312 313 void execAbort() { 314 _startRequestId = submitRequest("-exec-abort"); 315 } 316 317 /// start program execution, can be called after program is loaded 318 int _continueRequestId; 319 void execContinue() { 320 _continueRequestId = submitRequest("-exec-continue"); 321 } 322 323 /// stop program execution 324 int _stopRequestId; 325 void execStop() { 326 _continueRequestId = submitRequest("-gdb-exit"); 327 } 328 /// interrupt execution 329 int _pauseRequestId; 330 void execPause() { 331 _pauseRequestId = submitRequest("-exec-interrupt", true); 332 } 333 334 /// step over 335 int _stepOverRequestId; 336 void execStepOver(ulong threadId) { 337 _stepOverRequestId = submitRequest("-exec-next".appendThreadParam(threadId)); 338 } 339 /// step in 340 int _stepInRequestId; 341 void execStepIn(ulong threadId) { 342 _stepInRequestId = submitRequest("-exec-step".appendThreadParam(threadId)); 343 } 344 /// step out 345 int _stepOutRequestId; 346 void execStepOut(ulong threadId) { 347 _stepOutRequestId = submitRequest("-exec-finish".appendThreadParam(threadId)); 348 } 349 /// restart 350 int _restartRequestId; 351 void execRestart() { 352 //_restartRequestId = sendCommand("-exec-restart"); 353 } 354 355 private GDBBreakpoint[] _breakpoints; 356 private static class GDBBreakpoint { 357 Breakpoint bp; 358 string number; 359 int createRequestId; 360 } 361 private GDBBreakpoint findBreakpoint(Breakpoint bp) { 362 foreach(gdbbp; _breakpoints) { 363 if (gdbbp.bp.id == bp.id) 364 return gdbbp; 365 } 366 return null; 367 } 368 369 private GDBBreakpoint findBreakpointByNumber(string number) { 370 if (number.empty) 371 return null; 372 foreach(gdbbp; _breakpoints) { 373 if (gdbbp.number.equal(number)) 374 return gdbbp; 375 } 376 return null; 377 } 378 379 static string quotePathIfNeeded(string s) { 380 char[] buf; 381 buf.assumeSafeAppend(); 382 bool hasSpaces = false; 383 for(uint i = 0; i < s.length; i++) { 384 if (s[i] == ' ') 385 hasSpaces = true; 386 } 387 if (hasSpaces) 388 buf ~= '\"'; 389 for(uint i = 0; i < s.length; i++) { 390 char ch = s[i]; 391 if (ch == '\t') 392 buf ~= "\\t"; 393 else if (ch == '\n') 394 buf ~= "\\n"; 395 else if (ch == '\r') 396 buf ~= "\\r"; 397 else if (ch == '\\') 398 buf ~= "\\\\"; 399 else 400 buf ~= ch; 401 } 402 if (hasSpaces) 403 buf ~= '\"'; 404 return buf.dup; 405 } 406 407 class AddBreakpointRequest : GDBRequest { 408 GDBBreakpoint gdbbp; 409 this(Breakpoint bp) { 410 gdbbp = new GDBBreakpoint(); 411 gdbbp.bp = bp; 412 char[] cmd; 413 cmd ~= "-break-insert "; 414 if (!bp.enabled) 415 cmd ~= "-d "; // create disabled 416 cmd ~= quotePathIfNeeded(bp.fullFilePath); 417 cmd ~= ":"; 418 cmd ~= to!string(bp.line); 419 command = cmd.dup; 420 _breakpoints ~= gdbbp; 421 } 422 423 override void onResult() { 424 if (MIValue bkpt = params["bkpt"]) { 425 string number = bkpt.getString("number"); 426 gdbbp.number = number; 427 Log.d("GDB number for breakpoint " ~ gdbbp.bp.id.to!string ~ " assigned is " ~ number); 428 } 429 } 430 } 431 432 /// update list of breakpoints 433 void setBreakpoints(Breakpoint[] breakpoints) { 434 char[] breakpointsToDelete; 435 char[] breakpointsToEnable; 436 char[] breakpointsToDisable; 437 // checking for removed breakpoints 438 for (int i = cast(int)_breakpoints.length - 1; i >= 0; i--) { 439 bool found = false; 440 foreach(bp; breakpoints) 441 if (bp.id == _breakpoints[i].bp.id) { 442 found = true; 443 break; 444 } 445 if (!found) { 446 for (int j = i; j < _breakpoints.length - 1; j++) 447 _breakpoints[j] = _breakpoints[j + 1]; 448 if (breakpointsToDelete.length) 449 breakpointsToDelete ~= ","; 450 breakpointsToDelete ~= _breakpoints[i].number; 451 _breakpoints.length = _breakpoints.length - 1; 452 } 453 } 454 // checking for added or updated breakpoints 455 foreach(bp; breakpoints) { 456 GDBBreakpoint existing = findBreakpoint(bp); 457 if (!existing) { 458 submitRequest(new AddBreakpointRequest(bp)); 459 } else { 460 if (bp.enabled && !existing.bp.enabled) { 461 if (breakpointsToEnable.length) 462 breakpointsToEnable ~= ","; 463 breakpointsToEnable ~= existing.number; 464 existing.bp.enabled = true; 465 } else if (!bp.enabled && existing.bp.enabled) { 466 if (breakpointsToDisable.length) 467 breakpointsToDisable ~= ","; 468 breakpointsToDisable ~= existing.number; 469 existing.bp.enabled = false; 470 } 471 } 472 } 473 if (breakpointsToDelete.length) { 474 Log.d("Deleting breakpoints: " ~ breakpointsToDelete); 475 submitRequest(("-break-delete " ~ breakpointsToDelete).dup); 476 } 477 if (breakpointsToEnable.length) { 478 Log.d("Enabling breakpoints: " ~ breakpointsToEnable); 479 submitRequest(("-break-enable " ~ breakpointsToEnable).dup); 480 } 481 if (breakpointsToDisable.length) { 482 Log.d("Disabling breakpoints: " ~ breakpointsToDisable); 483 submitRequest(("-break-disable " ~ breakpointsToDisable).dup); 484 } 485 } 486 487 488 // ~message 489 void handleStreamLineCLI(string s) { 490 Log.d("GDB CLI: ", s); 491 if (s.length >= 2 && s.startsWith('\"') && s.endsWith('\"')) 492 s = parseCString(s); 493 _callback.onDebuggerMessage(s); 494 } 495 496 // @message 497 void handleStreamLineProgram(string s) { 498 Log.d("GDB program stream: ", s); 499 //_callback.onDebuggerMessage(s); 500 } 501 502 // &message 503 void handleStreamLineGDBDebug(string s) { 504 Log.d("GDB internal debug message: ", s); 505 } 506 507 long _stoppedThreadId = 0; 508 509 // *stopped,reason="exited-normally" 510 // *running,thread-id="all" 511 // *asyncclass,result 512 void handleExecAsyncMessage(uint token, string s) { 513 string msgType = parseIdentAndSkipComma(s); 514 AsyncClass msgId = asyncByName(msgType); 515 if (msgId == AsyncClass.other) 516 Log.d("GDB WARN unknown async class type: ", msgType); 517 MIValue params = parseMI(s); 518 if (!params) { 519 Log.e("Failed to parse exec state params"); 520 return; 521 } 522 Log.v("GDB async *[", token, "] ", msgType, " params: ", params.toString); 523 string reason = params.getString("reason"); 524 if (msgId == AsyncClass.running) { 525 _callback.onDebugState(DebuggingState.running, StateChangeReason.unknown, null, null); 526 } else if (msgId == AsyncClass.stopped) { 527 StateChangeReason reasonId = StateChangeReason.unknown; 528 DebugFrame location = parseFrame(params["frame"]); 529 string threadId = params.getString("thread-id"); 530 _stoppedThreadId = params.getUlong("thread-id", 0); 531 string stoppedThreads = params.getString("all"); 532 Breakpoint bp = null; 533 if (reason.equal("end-stepping-range")) { 534 updateState(); 535 _callback.onDebugState(DebuggingState.paused, StateChangeReason.endSteppingRange, location, bp); 536 } else if (reason.equal("breakpoint-hit")) { 537 Log.v("handling breakpoint-hit"); 538 if (GDBBreakpoint gdbbp = findBreakpointByNumber(params.getString("bkptno"))) { 539 bp = gdbbp.bp; 540 if (!location && bp) { 541 location = new DebugFrame(); 542 location.fillMissingFields(bp); 543 } 544 } 545 //_requests.targetIsReady(); 546 updateState(); 547 _callback.onDebugState(DebuggingState.paused, StateChangeReason.breakpointHit, location, bp); 548 } else if (reason.equal("signal-received")) { 549 //_requests.targetIsReady(); 550 updateState(); 551 _callback.onDebugState(DebuggingState.paused, StateChangeReason.exception, location, bp); 552 } else if (reason.equal("exited-normally")) { 553 _exitCode = 0; 554 Log.i("Program exited. Exit code ", _exitCode); 555 _callback.onDebugState(DebuggingState.stopped, StateChangeReason.exited, null, null); 556 } else if (reason.equal("exited")) { 557 _exitCode = params.getInt("exit-code"); 558 Log.i("Program exited. Exit code ", _exitCode); 559 _callback.onDebugState(DebuggingState.stopped, StateChangeReason.exited, null, null); 560 } else if (reason.equal("exited-signalled")) { 561 _exitCode = -2; //params.getInt("exit-code"); 562 string signalName = params.getString("signal-name"); 563 string signalMeaning = params.getString("signal-meaning"); 564 Log.i("Program exited by signal. Signal code: ", signalName, " Signal meaning: ", signalMeaning); 565 _callback.onDebugState(DebuggingState.stopped, StateChangeReason.exited, null, null); 566 } else { 567 _exitCode = -1; 568 _callback.onDebugState(DebuggingState.stopped, StateChangeReason.exited, null, null); 569 } 570 } else { 571 Log.e("unknown async type `", msgType, "`"); 572 } 573 } 574 575 int _stackListLocalsRequest; 576 DebugThreadList _currentState; 577 void updateState() { 578 _currentState = null; 579 submitRequest(new ThreadInfoRequest()); 580 } 581 582 // +asyncclass,result 583 void handleStatusAsyncMessage(uint token, string s) { 584 string msgType = parseIdentAndSkipComma(s); 585 AsyncClass msgId = asyncByName(msgType); 586 if (msgId == AsyncClass.other) 587 Log.d("GDB WARN unknown async class type: ", msgType); 588 Log.v("GDB async +[", token, "] ", msgType, " params: ", s); 589 } 590 591 // =asyncclass,result 592 void handleNotifyAsyncMessage(uint token, string s) { 593 string msgType = parseIdentAndSkipComma(s); 594 AsyncClass msgId = asyncByName(msgType); 595 if (msgId == AsyncClass.other) 596 Log.d("GDB WARN unknown async class type: ", msgType); 597 Log.v("GDB async =[", token, "] ", msgType, " params: ", s); 598 } 599 600 // ^resultClass,result 601 void handleResultMessage(uint token, string s) { 602 Log.v("GDB result ^[", token, "] ", s); 603 string msgType = parseIdentAndSkipComma(s); 604 ResultClass msgId = resultByName(msgType); 605 if (msgId == ResultClass.other) 606 Log.d("GDB WARN unknown result class type: ", msgType); 607 MIValue params = parseMI(s); 608 Log.v("GDB result ^[", token, "] ", msgType, " params: ", (params ? params.toString : "unparsed: " ~ s)); 609 if (_requests.handleResult(token, msgId, params)) { 610 // handled using result list 611 } else { 612 Log.w("received results for unknown request"); 613 } 614 } 615 616 GDBRequestList _requests; 617 /// submit single request or request chain 618 void submitRequest(GDBRequest[] requests ... ) { 619 for (int i = 0; i + 1 < requests.length; i++) 620 requests[i].chain(requests[i + 1]); 621 _requests.submit(requests[0]); 622 } 623 624 /// submit simple text command request 625 int submitRequest(string text, bool forceNoWaitDebuggerReady = false) { 626 auto request = new GDBRequest(text); 627 _requests.submit(request, forceNoWaitDebuggerReady); 628 return request.id; 629 } 630 631 /// request stack trace and local vars for thread and frame 632 void requestDebugContextInfo(ulong threadId, int frame) { 633 Log.d("requestDebugContextInfo threadId=", threadId, " frame=", frame); 634 submitRequest(new StackListFramesRequest(threadId, frame)); 635 } 636 637 private int initRequestsSuccessful = 0; 638 private int initRequestsError = 0; 639 private int initRequestsWarnings = 0; 640 private int totalInitRequests = 0; 641 private int finishedInitRequests = 0; 642 class GDBInitRequest : GDBRequest { 643 bool _mandatory; 644 this(string cmd, bool mandatory) { 645 command = cmd; 646 _mandatory = mandatory; 647 totalInitRequests++; 648 } 649 650 override void onOtherResult() { 651 initRequestsSuccessful++; 652 finishedInitRequests++; 653 checkFinished(); 654 } 655 656 override void onResult() { 657 initRequestsSuccessful++; 658 finishedInitRequests++; 659 checkFinished(); 660 } 661 662 /// called if resultClass is error 663 override void onError() { 664 if (_mandatory) 665 initRequestsError++; 666 else 667 initRequestsWarnings++; 668 finishedInitRequests++; 669 checkFinished(); 670 } 671 672 void checkFinished() { 673 if (initRequestsError) 674 initRequestsCompleted(false); 675 else if (finishedInitRequests == totalInitRequests) 676 initRequestsCompleted(true); 677 } 678 } 679 680 void initRequestsCompleted(bool successful = true) { 681 Log.d("Init sequence complection result: ", successful); 682 if (successful) { 683 // ok 684 _callback.onProgramLoaded(true, true); 685 } else { 686 // error 687 _requests.cancelPendingRequests(); 688 _status = ExecutionStatus.Error; 689 _stopRequested = true; 690 } 691 } 692 693 void submitInitRequests() { 694 initRequestsSuccessful = 0; 695 initRequestsError = 0; 696 totalInitRequests = 0; 697 initRequestsWarnings = 0; 698 finishedInitRequests = 0; 699 submitRequest(new GDBInitRequest("-environment-cd " ~ quotePathIfNeeded(_executableWorkingDir), true)); 700 if (externalTerminalTty) 701 submitRequest(new GDBInitRequest("-inferior-tty-set " ~ quotePathIfNeeded(externalTerminalTty), true)); 702 703 submitRequest(new GDBInitRequest("-gdb-set breakpoint pending on", false)); 704 //submitRequest(new GDBInitRequest("-enable-pretty-printing", false)); 705 submitRequest(new GDBInitRequest("-gdb-set print object on", false)); 706 submitRequest(new GDBInitRequest("-gdb-set print sevenbit-strings on", false)); 707 submitRequest(new GDBInitRequest("-gdb-set host-charset UTF-8", false)); 708 //11-gdb-set target-charset WINDOWS-1252 709 //12-gdb-set target-wide-charset UTF-16 710 //13source .gdbinit 711 submitRequest(new GDBInitRequest("-gdb-set target-async off", false)); 712 submitRequest(new GDBInitRequest("-gdb-set auto-solib-add on", false)); 713 if (_executableArgs.length) { 714 char[] buf; 715 for(uint i = 0; i < _executableArgs.length; i++) { 716 if (i > 0) 717 buf ~= " "; 718 buf ~= quotePathIfNeeded(_executableArgs[i]); 719 } 720 submitRequest(new GDBInitRequest(("-exec-arguments " ~ buf).dup, true)); 721 } 722 submitRequest(new GDBInitRequest("-file-exec-and-symbols " ~ quotePathIfNeeded(_executableFile), true)); 723 724 //debuggerArgs ~= _executableFile; 725 //foreach(arg; _executableArgs) 726 // debuggerArgs ~= arg; 727 //ExternalProcessState state = runDebuggerProcess(_debuggerExecutable, debuggerArgs, _executableWorkingDir); 728 //17-gdb-show language 729 //18-gdb-set language c 730 //19-interpreter-exec console "p/x (char)-1" 731 //20-data-evaluate-expression "sizeof (void*)" 732 //21-gdb-set language auto 733 734 } 735 736 class ThreadInfoRequest : GDBRequest { 737 this() { command = "-thread-info"; } 738 override void onResult() { 739 _currentState = parseThreadList(params); 740 if (_currentState) { 741 // TODO 742 Log.d("Thread list is parsed"); 743 if (!_currentState.currentThreadId) 744 _currentState.currentThreadId = _stoppedThreadId; 745 submitRequest(new StackListFramesRequest(_currentState.currentThreadId, 0)); 746 } 747 } 748 } 749 750 class StackListFramesRequest : GDBRequest { 751 private ulong _threadId; 752 private int _frameId; 753 this(ulong threadId, int frameId) { 754 _threadId = threadId; 755 _frameId = frameId; 756 if (!_threadId) 757 _threadId = _currentState ? _currentState.currentThreadId : 0; 758 command = "-stack-list-frames --thread " ~ to!string(_threadId); 759 } 760 override void onResult() { 761 DebugStack stack = parseStack(params); 762 if (stack) { 763 // TODO 764 Log.d("Stack frames list is parsed: " ~ to!string(stack)); 765 if (_currentState) { 766 if (DebugThread currentThread = _currentState.findThread(_threadId)) { 767 currentThread.stack = stack; 768 Log.d("Setting stack frames for current thread"); 769 } 770 submitRequest(new LocalVariableListRequest(_threadId, _frameId)); 771 } 772 } 773 } 774 } 775 776 class LocalVariableListRequest : GDBRequest { 777 ulong _threadId; 778 int _frameId; 779 this(ulong threadId, int frameId) { 780 _threadId = threadId; 781 _frameId = frameId; 782 //command = "-stack-list-variables --thread " ~ to!string(_threadId) ~ " --frame " ~ to!string(_frameId) ~ " --simple-values"; 783 command = "-stack-list-locals --thread " ~ to!string(_threadId) ~ " --frame " ~ to!string(_frameId) ~ " 1"; 784 } 785 override void onResult() { 786 DebugVariableList variables = parseVariableList(params, "locals"); 787 if (variables) { 788 // TODO 789 Log.d("Variable list is parsed: " ~ to!string(variables)); 790 if (_currentState) { 791 if (DebugThread currentThread = _currentState.findThread(_threadId)) { 792 if (currentThread.length > 0) { 793 if (_frameId > currentThread.length) 794 _frameId = 0; 795 if (_frameId < currentThread.length) 796 currentThread[_frameId].locals = variables; 797 Log.d("Setting variables for current thread top frame"); 798 _callback.onDebugContextInfo(_currentState.clone(), _threadId, _frameId); 799 } 800 } 801 } 802 } 803 } 804 } 805 806 bool _firstIdle = true; 807 // (gdb) 808 void onDebuggerIdle() { 809 Log.d("GDB idle"); 810 _requests.targetIsReady(); 811 if (_firstIdle) { 812 _firstIdle = false; 813 return; 814 } 815 } 816 817 override protected void onDebuggerStdoutLine(string gdbLine) { 818 Log.d("GDB stdout: '", gdbLine, "'"); 819 string line = gdbLine; 820 if (line.empty) 821 return; 822 // parse token (sequence of digits at the beginning of message) 823 uint tokenId = 0; 824 int tokenLen = 0; 825 while (tokenLen < line.length && line[tokenLen] >= '0' && line[tokenLen] <= '9') 826 tokenLen++; 827 if (tokenLen > 0) { 828 tokenId = to!uint(line[0..tokenLen]); 829 line = line[tokenLen .. $]; 830 } 831 if (line.length == 0) 832 return; // token only, no message! 833 char firstChar = line[0]; 834 string restLine = line.length > 1 ? line[1..$] : ""; 835 if (firstChar == '~') { 836 handleStreamLineCLI(restLine); 837 return; 838 } else if (firstChar == '@') { 839 handleStreamLineProgram(restLine); 840 return; 841 } else if (firstChar == '&') { 842 handleStreamLineGDBDebug(restLine); 843 return; 844 } else if (firstChar == '*') { 845 handleExecAsyncMessage(tokenId, restLine); 846 return; 847 } else if (firstChar == '+') { 848 handleStatusAsyncMessage(tokenId, restLine); 849 return; 850 } else if (firstChar == '=') { 851 handleNotifyAsyncMessage(tokenId, restLine); 852 return; 853 } else if (firstChar == '^') { 854 handleResultMessage(tokenId, restLine); 855 return; 856 } else if (line.startsWith("(gdb)")) { 857 onDebuggerIdle(); 858 return; 859 } else { 860 Log.d("GDB unprocessed: ", gdbLine); 861 } 862 } 863 864 } 865 866 867 class GDBRequest { 868 int id; 869 string command; 870 ResultClass resultClass; 871 MIValue params; 872 GDBRequest next; 873 874 this() { 875 } 876 877 this(string cmdtext) { 878 command = cmdtext; 879 } 880 881 /// called if resultClass is done 882 void onResult() { 883 } 884 /// called if resultClass is error 885 void onError() { 886 } 887 /// called on other result types 888 void onOtherResult() { 889 } 890 891 /// chain additional request, for case when previous finished ok 892 GDBRequest chain(GDBRequest next) { 893 this.next = next; 894 return this; 895 } 896 } 897 898 struct GDBRequestList { 899 900 private bool _synchronousMode = false; 901 902 void setSynchronousMode(bool flg) { 903 _synchronousMode = flg; 904 _ready = _ready | _synchronousMode; 905 } 906 907 private TextCommandTarget _target; 908 private GDBRequest[int] _activeRequests; 909 private GDBRequest[] _pendingRequests; 910 911 private bool _ready = false; 912 913 void setTarget(TextCommandTarget target) { 914 _target = target; 915 } 916 917 private void executeRequest(GDBRequest request) { 918 request.id = _target.sendCommand(request.command, request.id); 919 if (request.id) 920 _activeRequests[request.id] = request; 921 } 922 923 int submit(GDBRequest request, bool forceNoWaitDebuggerReady = false) { 924 if (!request.id) 925 request.id = _target.reserveCommandId(); 926 if (Log.traceEnabled) 927 Log.v("submitting request " ~ to!string(request.id) ~ " " ~ request.command); 928 if (_ready || _synchronousMode || forceNoWaitDebuggerReady) { 929 if (!forceNoWaitDebuggerReady) 930 _ready = _synchronousMode; 931 executeRequest(request); 932 } else 933 _pendingRequests ~= request; 934 return request.id; 935 } 936 937 // (gdb) prompt received 938 void targetIsReady() { 939 _ready = true; 940 if (_pendingRequests.length) { 941 // execute next pending request 942 GDBRequest next = _pendingRequests[0]; 943 for (uint i = 0; i + 1 < _pendingRequests.length; i++) 944 _pendingRequests[i] = _pendingRequests[i + 1]; 945 _pendingRequests[$ - 1] = null; 946 _pendingRequests.length = _pendingRequests.length - 1; 947 executeRequest(next); 948 } 949 } 950 951 void cancelPendingRequests() { 952 foreach(ref r; _pendingRequests) 953 r = null; // just to help GC 954 _pendingRequests.length = 0; 955 } 956 957 bool handleResult(int token, ResultClass resultClass, MIValue params) { 958 if (token in _activeRequests) { 959 GDBRequest r = _activeRequests[token]; 960 _activeRequests.remove(token); 961 r.resultClass = resultClass; 962 r.params = params; 963 if (resultClass == ResultClass.done) { 964 r.onResult(); 965 if (r.next) 966 submit(r.next); 967 } else if (resultClass == ResultClass.error) { 968 r.onError(); 969 } else { 970 r.onOtherResult(); 971 } 972 return true; 973 } 974 return false; 975 } 976 } 977 978 /// appends --thread parameter to command text if threadId != 0 979 string appendThreadParam(string src, ulong threadId) { 980 if (!threadId) 981 return src; 982 return src ~= " --thread " ~ to!string(threadId); 983 }