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