1 /*
2 SPDX-FileCopyrightText: 1999 John Birch <jbb@kdevelop.org >
3 SPDX-FileCopyrightText: 2007 Vladimir Prus <ghost@cs.msu.su>
4 SPDX-FileCopyrightText: 2016 Aetf <aetf@unlimitedcodeworks.xyz>
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9 #include "midebugger.h"
10
11 #include "debuglog.h"
12 #include "mi/micommand.h"
13
14 #include <interfaces/icore.h>
15 #include <interfaces/iuicontroller.h>
16 #include <sublime/message.h>
17
18 #include <kcoreaddons_version.h>
19 #include <KLocalizedString>
20 #include <KMessageBox>
21
22 #include <QApplication>
23 #include <QString>
24 #include <QStringList>
25
26 #include <csignal>
27
28 #include <memory>
29 #include <stdexcept>
30 #include <sstream>
31
32 #ifdef Q_OS_WIN
33 #include <Windows.h>
34 #endif
35
36 // #define DEBUG_NO_TRY //to get a backtrace to where the exception was thrown
37
38 using namespace KDevMI;
39 using namespace KDevMI::MI;
40
MIDebugger(QObject * parent)41 MIDebugger::MIDebugger(QObject* parent)
42 : QObject(parent)
43 {
44 m_process = new KProcess(this);
45 m_process->setOutputChannelMode(KProcess::SeparateChannels);
46 connect(m_process, &KProcess::readyReadStandardOutput,
47 this, &MIDebugger::readyReadStandardOutput);
48 connect(m_process, &KProcess::readyReadStandardError,
49 this, &MIDebugger::readyReadStandardError);
50 connect(m_process, QOverload<int,QProcess::ExitStatus>::of(&QProcess::finished),
51 this, &MIDebugger::processFinished);
52 connect(m_process, &QProcess::errorOccurred,
53 this, &MIDebugger::processErrored);
54 }
55
~MIDebugger()56 MIDebugger::~MIDebugger()
57 {
58 // prevent Qt warning: QProcess: Destroyed while process is still running.
59 if (m_process && m_process->state() == QProcess::Running) {
60 disconnect(m_process, &QProcess::errorOccurred,
61 this, &MIDebugger::processErrored);
62 m_process->kill();
63 m_process->waitForFinished(10);
64 }
65 }
66
execute(MICommand * command)67 void MIDebugger::execute(MICommand* command)
68 {
69 m_currentCmd = command;
70 QString commandText = m_currentCmd->cmdToSend();
71
72 qCDebug(DEBUGGERCOMMON) << "SEND:" << commandText.trimmed();
73
74 QByteArray commandUtf8 = commandText.toUtf8();
75
76 m_process->write(commandUtf8);
77 command->markAsSubmitted();
78
79 QString prettyCmd = m_currentCmd->cmdToSend();
80 prettyCmd.remove(QRegExp(QStringLiteral("set prompt \032.\n")));
81 prettyCmd = QLatin1String("(gdb) ") + prettyCmd;
82
83 if (m_currentCmd->isUserCommand())
84 emit userCommandOutput(prettyCmd);
85 else
86 emit internalCommandOutput(prettyCmd);
87 }
88
isReady() const89 bool MIDebugger::isReady() const
90 {
91 return m_currentCmd == nullptr;
92 }
93
interrupt()94 void MIDebugger::interrupt()
95 {
96 #ifndef Q_OS_WIN
97 #if KCOREADDONS_VERSION < QT_VERSION_CHECK(5, 78, 0)
98 int pid = m_process->pid();
99 #else
100 int pid = m_process->processId();
101 #endif
102 if (pid != 0) {
103 ::kill(pid, SIGINT);
104 }
105 #else
106 SetConsoleCtrlHandler(nullptr, true);
107 GenerateConsoleCtrlEvent(0, 0);
108 SetConsoleCtrlHandler(nullptr, false);
109 #endif
110 }
111
currentCommand() const112 MICommand* MIDebugger::currentCommand() const
113 {
114 return m_currentCmd;
115 }
116
kill()117 void MIDebugger::kill()
118 {
119 m_process->kill();
120 }
121
readyReadStandardOutput()122 void MIDebugger::readyReadStandardOutput()
123 {
124 auto* const core = KDevelop::ICore::self();
125 if (!core || !core->debugController()) {
126 const auto nullObject = core ? QLatin1String("the debug controller")
127 : QLatin1String("the KDevelop core");
128 qCDebug(DEBUGGERCOMMON).nospace().noquote()
129 << "Cannot process standard output without " << nullObject
130 << ". KDevelop must be exiting and " << nullObject << " already destroyed.";
131 return;
132 }
133
134 m_process->setReadChannel(QProcess::StandardOutput);
135
136 m_buffer += m_process->readAll();
137 for (;;)
138 {
139 /* In MI mode, all messages are exactly one line.
140 See if we have any complete lines in the buffer. */
141 int i = m_buffer.indexOf('\n');
142 if (i == -1)
143 break;
144 QByteArray reply(m_buffer.left(i));
145 m_buffer.remove(0, i+1);
146
147 processLine(reply);
148 }
149 }
150
readyReadStandardError()151 void MIDebugger::readyReadStandardError()
152 {
153 m_process->setReadChannel(QProcess::StandardError);
154 emit debuggerInternalOutput(QString::fromUtf8(m_process->readAll()));
155 }
156
processLine(const QByteArray & line)157 void MIDebugger::processLine(const QByteArray& line)
158 {
159 if (line != "(gdb) ") {
160 #if KCOREADDONS_VERSION < QT_VERSION_CHECK(5, 78, 0)
161 qCDebug(DEBUGGERCOMMON) << "Debugger output (pid =" << m_process->pid() << "): " << line;
162 #else
163 qCDebug(DEBUGGERCOMMON) << "Debugger output (pid =" << m_process->processId() << "): " << line;
164 #endif
165 }
166
167 FileSymbol file;
168 file.contents = line;
169
170 std::unique_ptr<MI::Record> r(m_parser.parse(&file));
171
172 if (!r)
173 {
174 // simply ignore the invalid MI message because both gdb and lldb
175 // sometimes produces invalid messages that can be safely ignored.
176 qCDebug(DEBUGGERCOMMON) << "Invalid MI message:" << line;
177 // We don't consider the current command done.
178 // So, if a command results in unparseable reply,
179 // we'll just wait for the "right" reply, which might
180 // never come. However, marking the command as
181 // done in this case is even more risky.
182 // It's probably possible to get here if we're debugging
183 // natively without PTY, though this is uncommon case.
184 return;
185 }
186
187 #ifndef DEBUG_NO_TRY
188 try
189 {
190 #endif
191 switch(r->kind)
192 {
193 case MI::Record::Result: {
194 auto& result = static_cast<MI::ResultRecord&>(*r);
195
196 // it's still possible for the user to issue a MI command,
197 // emit correct signal
198 if (m_currentCmd && m_currentCmd->isUserCommand()) {
199 emit userCommandOutput(QString::fromUtf8(line) + QLatin1Char('\n'));
200 } else {
201 emit internalCommandOutput(QString::fromUtf8(line) + QLatin1Char('\n'));
202 }
203
204 // protect against wild replies that sometimes returned from gdb without a pending command
205 if (!m_currentCmd)
206 {
207 qCWarning(DEBUGGERCOMMON) << "Received a result without a pending command";
208 throw std::runtime_error("Received a result without a pending command");
209 }
210 else if (m_currentCmd->token() != result.token)
211 {
212 std::stringstream ss;
213 ss << "Received a result with token not matching pending command. "
214 << "Pending: " << m_currentCmd->token() << "Received: " << result.token;
215 qCWarning(DEBUGGERCOMMON) << ss.str().c_str();
216 throw std::runtime_error(ss.str());
217 }
218
219 // GDB doc: "running" and "exit" are status codes equivalent to "done"
220 if (result.reason == QLatin1String("done") || result.reason == QLatin1String("running") || result.reason == QLatin1String("exit"))
221 {
222 qCDebug(DEBUGGERCOMMON) << "Result token is" << result.token;
223 m_currentCmd->markAsCompleted();
224 qCDebug(DEBUGGERCOMMON) << "Command successful, times "
225 << m_currentCmd->totalProcessingTime()
226 << m_currentCmd->queueTime()
227 << m_currentCmd->gdbProcessingTime();
228 m_currentCmd->invokeHandler(result);
229 }
230 else if (result.reason == QLatin1String("error"))
231 {
232 qCDebug(DEBUGGERCOMMON) << "Handling error";
233 m_currentCmd->markAsCompleted();
234 qCDebug(DEBUGGERCOMMON) << "Command error, times"
235 << m_currentCmd->totalProcessingTime()
236 << m_currentCmd->queueTime()
237 << m_currentCmd->gdbProcessingTime();
238 // Some commands want to handle errors themself.
239 if (m_currentCmd->handlesError() &&
240 m_currentCmd->invokeHandler(result))
241 {
242 qCDebug(DEBUGGERCOMMON) << "Invoked custom handler\n";
243 // Done, nothing more needed
244 }
245 else
246 emit error(result);
247 }
248 else
249 {
250 qCDebug(DEBUGGERCOMMON) << "Unhandled result code: " << result.reason;
251 }
252
253 delete m_currentCmd;
254 m_currentCmd = nullptr;
255 emit ready();
256 break;
257 }
258
259 case MI::Record::Async: {
260 auto& async = static_cast<MI::AsyncRecord&>(*r);
261
262 switch (async.subkind) {
263 case MI::AsyncRecord::Exec: {
264 // Prefix '*'; asynchronous state changes of the target
265 if (async.reason == QLatin1String("stopped"))
266 {
267 emit programStopped(async);
268 }
269 else if (async.reason == QLatin1String("running"))
270 {
271 emit programRunning();
272 }
273 else
274 {
275 qCDebug(DEBUGGERCOMMON) << "Unhandled exec notification: " << async.reason;
276 }
277 break;
278 }
279
280 case MI::AsyncRecord::Notify: {
281 // Prefix '='; supplementary information that we should handle (new breakpoint etc.)
282 emit notification(async);
283 break;
284 }
285
286 case MI::AsyncRecord::Status: {
287 // Prefix '+'; GDB documentation:
288 // On-going status information about progress of a slow operation; may be ignored
289 break;
290 }
291 }
292 break;
293 }
294
295 case MI::Record::Stream: {
296
297 auto& s = static_cast<MI::StreamRecord&>(*r);
298
299 if (s.subkind == MI::StreamRecord::Target) {
300 emit applicationOutput(s.message);
301 } else if (s.subkind == MI::StreamRecord::Console) {
302 if (m_currentCmd && m_currentCmd->isUserCommand())
303 emit userCommandOutput(s.message);
304 else
305 emit internalCommandOutput(s.message);
306
307 if (m_currentCmd)
308 m_currentCmd->newOutput(s.message);
309 } else {
310 emit debuggerInternalOutput(s.message);
311 }
312
313 emit streamRecord(s);
314
315 break;
316 }
317
318 case MI::Record::Prompt:
319 break;
320 }
321 #ifndef DEBUG_NO_TRY
322 }
323 catch(const std::exception& e)
324 {
325 KMessageBox::detailedSorry(
326 qApp->activeWindow(),
327 i18nc("<b>Internal debugger error</b>",
328 "<p>The debugger component encountered an internal error while "
329 "processing the reply from the debugger. Please submit a bug report. "
330 "The debug session will now end to prevent potential crash"),
331 i18n("The exception is: %1\n"
332 "The MI response is: %2", QString::fromUtf8(e.what()),
333 QString::fromLatin1(line)),
334 i18nc("@title:window", "Internal Debugger Error"));
335 emit exited(true, QString::fromUtf8(e.what()));
336 }
337 #endif
338 }
339
processFinished(int exitCode,QProcess::ExitStatus exitStatus)340 void MIDebugger::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
341 {
342 qCDebug(DEBUGGERCOMMON) << "Debugger FINISHED\n";
343
344 bool abnormal = exitCode != 0 || exitStatus != QProcess::NormalExit;
345 emit userCommandOutput(QStringLiteral("Process exited\n"));
346 emit exited(abnormal, i18n("Process exited"));
347 }
348
processErrored(QProcess::ProcessError error)349 void MIDebugger::processErrored(QProcess::ProcessError error)
350 {
351 qCWarning(DEBUGGERCOMMON) << "Debugger ERRORED" << error << m_process->errorString();
352 if(error == QProcess::FailedToStart)
353 {
354 const QString messageText =
355 i18n("<b>Could not start debugger.</b>"
356 "<p>Could not run '%1'. "
357 "Make sure that the path name is specified correctly.",
358 m_debuggerExecutable);
359 auto* message = new Sublime::Message(messageText, Sublime::Message::Error);
360 KDevelop::ICore::self()->uiController()->postMessage(message);
361
362 emit userCommandOutput(QStringLiteral("Process failed to start\n"));
363 emit exited(true, i18n("Process failed to start"));
364
365 } else if (error == QProcess::Crashed) {
366 KMessageBox::error(
367 qApp->activeWindow(),
368 i18n("<b>Debugger crashed.</b>"
369 "<p>The debugger process '%1' crashed.<br>"
370 "Because of that the debug session has to be ended.<br>"
371 "Try to reproduce the crash without KDevelop and report a bug.<br>",
372 m_debuggerExecutable),
373 i18nc("@title:window", "Debugger Crashed"));
374
375 emit userCommandOutput(QStringLiteral("Process crashed\n"));
376 emit exited(true, i18n("Process crashed"));
377 }
378 }
379