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 }