1 module ddebug.gdb.gdbinterface; 2 3 public import ddebug.common.debugger; 4 import dlangui.core.logger; 5 import ddebug.common.queue; 6 import dlangide.builders.extprocess; 7 import std.utf; 8 import std.conv : to; 9 10 class ConsoleDebuggerInterface : DebuggerBase, TextWriter { 11 protected ExternalProcess _debuggerProcess; 12 13 protected ExternalProcessState runDebuggerProcess(string executable, string[]args, string dir) { 14 _debuggerProcess = new ExternalProcess(); 15 ExternalProcessState state = _debuggerProcess.run(executable, args, dir, this); 16 return state; 17 } 18 19 private string[] _stdoutLines; 20 private char[] _stdoutBuf; 21 /// return true to clear lines list 22 protected bool onDebuggerStdoutLines(string[] lines) { 23 return true; 24 } 25 private void onStdoutText(string text) { 26 _stdoutBuf ~= text; 27 // pass full lines 28 int startPos = 0; 29 bool fullLinesFound = false; 30 for (int i = 0; i < _stdoutBuf.length; i++) { 31 if (_stdoutBuf[i] == '\n' || _stdoutBuf[i] == '\r') { 32 if (i <= startPos) 33 _stdoutLines ~= ""; 34 else 35 _stdoutLines ~= _stdoutBuf[startPos .. i].dup; 36 fullLinesFound = true; 37 if (i + 1 < _stdoutBuf.length) { 38 if ((_stdoutBuf[i] == '\n' && _stdoutBuf[i + 1] == '\r') 39 || (_stdoutBuf[i] == '\r' && _stdoutBuf[i + 1] == '\n')) 40 i++; 41 } 42 startPos = i + 1; 43 } 44 } 45 if (fullLinesFound) { 46 for (int i = 0; i + startPos < _stdoutBuf.length; i++) 47 _stdoutBuf[i] = _stdoutBuf[i + startPos]; 48 _stdoutBuf.length = _stdoutBuf.length - startPos; 49 if (onDebuggerStdoutLines(_stdoutLines)) { 50 _stdoutLines.length = 0; 51 } 52 } 53 } 54 55 bool sendLine(string text) { 56 return _debuggerProcess.write(text ~ "\n"); 57 } 58 59 /// log lines 60 override void writeText(dstring text) { 61 string text8 = toUTF8(text); 62 postRequest(delegate() { 63 onStdoutText(text8); 64 }); 65 } 66 67 } 68 69 import std.process; 70 class GDBInterface : ConsoleDebuggerInterface { 71 72 protected int commandId; 73 74 75 int sendCommand(string text) { 76 commandId++; 77 sendLine(to!string(commandId) ~ text); 78 return commandId; 79 } 80 81 Pid terminalPid; 82 string terminalTty; 83 84 string startTerminal(string termExecutable) { 85 Log.d("Starting terminal"); 86 import std.random; 87 import std.file; 88 import std.path; 89 import std.string; 90 import core.thread; 91 uint n = uniform(0, 0x10000000, rndGen()); 92 terminalTty = null; 93 string termfile = buildPath(tempDir, format("dlangide-term-name-%07x.tmp", n)); 94 Log.d("temp file for tty name: ", termfile); 95 try { 96 terminalPid = spawnProcess([ 97 termExecutable, 98 "-title", 99 "DLangIDE External Console", 100 "-e", 101 "echo 'DlangIDE External Console' && tty > " ~ termfile ~ " && sleep 1000000" 102 ]); 103 for (int i = 0; i < 20; i++) { 104 Thread.sleep(dur!"msecs"(100)); 105 if (!isTerminalActive) { 106 Log.e("Failed to get terminal TTY"); 107 return null; 108 } 109 if (!exists(termfile)) { 110 Thread.sleep(dur!"msecs"(20)); 111 break; 112 } 113 } 114 // read TTY from file 115 if (exists(termfile)) { 116 terminalTty = readText(termfile); 117 if (terminalTty.endsWith("\n")) 118 terminalTty = terminalTty[0 .. $-1]; 119 // delete file 120 remove(termfile); 121 } 122 } catch (Exception e) { 123 Log.e("Failed to start terminal ", e); 124 killTerminal(); 125 } 126 if (terminalTty.length == 0) { 127 Log.i("Cannot start terminal"); 128 killTerminal(); 129 } else { 130 Log.i("Terminal: ", terminalTty); 131 } 132 return terminalTty; 133 } 134 135 bool isTerminalActive() { 136 if (terminalPid is null) 137 return false; 138 auto res = tryWait(terminalPid); 139 if (res.terminated) { 140 Log.d("isTerminalActive: Terminal is stopped"); 141 wait(terminalPid); 142 terminalPid = Pid.init; 143 return false; 144 } else { 145 return true; 146 } 147 } 148 149 void killTerminal() { 150 if (terminalPid is null) 151 return; 152 try { 153 Log.d("Trying to kill terminal"); 154 kill(terminalPid, 9); 155 Log.d("Waiting for terminal process termination"); 156 wait(terminalPid); 157 terminalPid = Pid.init; 158 Log.d("Killed"); 159 } catch (Exception e) { 160 Log.d("Exception while killing terminal", e); 161 terminalPid = Pid.init; 162 } 163 } 164 165 string terminalExecutableFileName = "xterm"; 166 override void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response) { 167 string[] debuggerArgs; 168 terminalTty = startTerminal(terminalExecutableFileName); 169 if (terminalTty.length == 0) { 170 response(ResponseCode.CannotRunDebugger, "Cannot start terminal"); 171 return; 172 } 173 debuggerArgs ~= "-tty"; 174 debuggerArgs ~= terminalTty; 175 debuggerArgs ~= "--interpreter=mi"; 176 debuggerArgs ~= "--silent"; 177 debuggerArgs ~= "--args"; 178 debuggerArgs ~= executable; 179 foreach(arg; args) 180 debuggerArgs ~= arg; 181 ExternalProcessState state = runDebuggerProcess(debuggerExecutable, debuggerArgs, workingDir); 182 Log.i("Debugger process state:"); 183 if (state == ExternalProcessState.Running) { 184 response(ResponseCode.Ok, "Started"); 185 //sendCommand("-break-insert main"); 186 sendCommand("-exec-run"); 187 } else { 188 response(ResponseCode.CannotRunDebugger, "Error while trying to run debugger process"); 189 return; 190 } 191 } 192 193 override void stop() { 194 if (_debuggerProcess !is null) 195 _debuggerProcess.kill(); 196 killTerminal(); 197 super.stop(); 198 } 199 200 /// return true to clear lines list 201 override protected bool onDebuggerStdoutLines(string[] lines) { 202 Log.d("onDebuggerStdout ", lines); 203 return true; 204 } 205 }