File indexing completed on 2024-04-28 04:38:34

0001 /*
0002     SPDX-FileCopyrightText: 1999 John Birch <jbb@kdevelop.org >
0003     SPDX-FileCopyrightText: 2007 Vladimir Prus <ghost@cs.msu.su>
0004     SPDX-FileCopyrightText: 2016 Aetf <aetf@unlimitedcodeworks.xyz>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "midebugger.h"
0010 
0011 #include "debuglog.h"
0012 #include "mi/micommand.h"
0013 
0014 #include <interfaces/icore.h>
0015 #include <interfaces/iuicontroller.h>
0016 #include <sublime/message.h>
0017 
0018 #include <KLocalizedString>
0019 #include <KMessageBox>
0020 
0021 #include <QApplication>
0022 #include <QString>
0023 #include <QStringList>
0024 
0025 #include <csignal>
0026 
0027 #include <memory>
0028 #include <stdexcept>
0029 #include <sstream>
0030 
0031 #ifdef Q_OS_WIN
0032 #include <Windows.h>
0033 #endif
0034 
0035 // #define DEBUG_NO_TRY //to get a backtrace to where the exception was thrown
0036 
0037 using namespace KDevMI;
0038 using namespace KDevMI::MI;
0039 
0040 MIDebugger::MIDebugger(QObject* parent)
0041     : QObject(parent)
0042 {
0043     m_process = new KProcess(this);
0044     m_process->setOutputChannelMode(KProcess::SeparateChannels);
0045     connect(m_process, &KProcess::readyReadStandardOutput,
0046             this, &MIDebugger::readyReadStandardOutput);
0047     connect(m_process, &KProcess::readyReadStandardError,
0048             this, &MIDebugger::readyReadStandardError);
0049     connect(m_process, QOverload<int,QProcess::ExitStatus>::of(&QProcess::finished),
0050             this, &MIDebugger::processFinished);
0051     connect(m_process, &QProcess::errorOccurred,
0052             this, &MIDebugger::processErrored);
0053 }
0054 
0055 MIDebugger::~MIDebugger()
0056 {
0057     // prevent Qt warning: QProcess: Destroyed while process is still running.
0058     if (m_process && m_process->state() == QProcess::Running) {
0059         disconnect(m_process, &QProcess::errorOccurred,
0060                     this, &MIDebugger::processErrored);
0061         m_process->kill();
0062         m_process->waitForFinished(10);
0063     }
0064 }
0065 
0066 void MIDebugger::execute(std::unique_ptr<MICommand> command)
0067 {
0068     m_currentCmd = std::move(command);
0069     QString commandText = m_currentCmd->cmdToSend();
0070 
0071     qCDebug(DEBUGGERCOMMON) << "SEND:" << commandText.trimmed();
0072 
0073     QByteArray commandUtf8 = commandText.toUtf8();
0074 
0075     m_process->write(commandUtf8);
0076     m_currentCmd->markAsSubmitted();
0077 
0078     QString prettyCmd = m_currentCmd->cmdToSend();
0079     prettyCmd.remove(QRegExp(QStringLiteral("set prompt \032.\n")));
0080     prettyCmd = QLatin1String("(gdb) ") + prettyCmd;
0081 
0082     if (m_currentCmd->isUserCommand())
0083         emit userCommandOutput(prettyCmd);
0084     else
0085         emit internalCommandOutput(prettyCmd);
0086 }
0087 
0088 bool MIDebugger::isReady() const
0089 {
0090     return m_currentCmd == nullptr;
0091 }
0092 
0093 void MIDebugger::interrupt()
0094 {
0095 #ifndef Q_OS_WIN
0096     int pid = m_process->processId();
0097     if (pid != 0) {
0098         ::kill(pid, SIGINT);
0099     }
0100 #else
0101     SetConsoleCtrlHandler(nullptr, true);
0102     GenerateConsoleCtrlEvent(0, 0);
0103     SetConsoleCtrlHandler(nullptr, false);
0104 #endif
0105 }
0106 
0107 MICommand* MIDebugger::currentCommand() const
0108 {
0109     return m_currentCmd.get();
0110 }
0111 
0112 void MIDebugger::kill()
0113 {
0114     m_process->kill();
0115 }
0116 
0117 void MIDebugger::readyReadStandardOutput()
0118 {
0119     auto* const core = KDevelop::ICore::self();
0120     if (!core || !core->debugController()) {
0121         const auto nullObject = core ? QLatin1String("the debug controller")
0122                                      : QLatin1String("the KDevelop core");
0123         qCDebug(DEBUGGERCOMMON).nospace().noquote()
0124                 << "Cannot process standard output without " << nullObject
0125                 << ". KDevelop must be exiting and " << nullObject << " already destroyed.";
0126         return;
0127     }
0128 
0129     m_process->setReadChannel(QProcess::StandardOutput);
0130 
0131     m_buffer += m_process->readAll();
0132     for (;;)
0133     {
0134         /* In MI mode, all messages are exactly one line.
0135            See if we have any complete lines in the buffer. */
0136         int i = m_buffer.indexOf('\n');
0137         if (i == -1)
0138             break;
0139         QByteArray reply(m_buffer.left(i));
0140         m_buffer.remove(0, i+1);
0141 
0142         processLine(reply);
0143     }
0144 }
0145 
0146 void MIDebugger::readyReadStandardError()
0147 {
0148     m_process->setReadChannel(QProcess::StandardError);
0149     emit debuggerInternalOutput(QString::fromUtf8(m_process->readAll()));
0150 }
0151 
0152 void MIDebugger::processLine(const QByteArray& line)
0153 {
0154     if (line != "(gdb) ") {
0155         qCDebug(DEBUGGERCOMMON) << "Debugger output (pid =" << m_process->processId() << "): " << line;
0156     }
0157 
0158     FileSymbol file;
0159     file.contents = line;
0160 
0161     std::unique_ptr<MI::Record> r(m_parser.parse(&file));
0162 
0163     if (!r)
0164     {
0165         // simply ignore the invalid MI message because both gdb and lldb
0166         // sometimes produces invalid messages that can be safely ignored.
0167         qCDebug(DEBUGGERCOMMON) << "Invalid MI message:" << line;
0168         // We don't consider the current command done.
0169         // So, if a command results in unparseable reply,
0170         // we'll just wait for the "right" reply, which might
0171         // never come.  However, marking the command as
0172         // done in this case is even more risky.
0173         // It's probably possible to get here if we're debugging
0174         // natively without PTY, though this is uncommon case.
0175         return;
0176     }
0177 
0178     #ifndef DEBUG_NO_TRY
0179     try
0180     {
0181     #endif
0182         switch(r->kind)
0183         {
0184         case MI::Record::Result: {
0185             auto& result = static_cast<MI::ResultRecord&>(*r);
0186 
0187             // it's still possible for the user to issue a MI command,
0188             // emit correct signal
0189             if (m_currentCmd && m_currentCmd->isUserCommand()) {
0190                 emit userCommandOutput(QString::fromUtf8(line) + QLatin1Char('\n'));
0191             } else {
0192                 emit internalCommandOutput(QString::fromUtf8(line) + QLatin1Char('\n'));
0193             }
0194 
0195             // protect against wild replies that sometimes returned from gdb without a pending command
0196             if (!m_currentCmd)
0197             {
0198                 qCWarning(DEBUGGERCOMMON) << "Received a result without a pending command";
0199                 throw std::runtime_error("Received a result without a pending command");
0200             }
0201             else if (m_currentCmd->token() != result.token)
0202             {
0203                 std::stringstream ss;
0204                 ss << "Received a result with token not matching pending command. "
0205                    << "Pending: " << m_currentCmd->token() << "Received: " << result.token;
0206                 qCWarning(DEBUGGERCOMMON) << ss.str().c_str();
0207                 throw std::runtime_error(ss.str());
0208             }
0209 
0210             // GDB doc: "running" and "exit" are status codes equivalent to "done"
0211             if (result.reason == QLatin1String("done") || result.reason == QLatin1String("running") || result.reason == QLatin1String("exit"))
0212             {
0213                 qCDebug(DEBUGGERCOMMON) << "Result token is" << result.token;
0214                 m_currentCmd->markAsCompleted();
0215                 qCDebug(DEBUGGERCOMMON) << "Command successful, times "
0216                                         << m_currentCmd->totalProcessingTime()
0217                                         << m_currentCmd->queueTime()
0218                                         << m_currentCmd->gdbProcessingTime();
0219                 m_currentCmd->invokeHandler(result);
0220             }
0221             else if (result.reason == QLatin1String("error"))
0222             {
0223                 qCDebug(DEBUGGERCOMMON) << "Handling error";
0224                 m_currentCmd->markAsCompleted();
0225                 qCDebug(DEBUGGERCOMMON) << "Command error, times"
0226                                         << m_currentCmd->totalProcessingTime()
0227                                         << m_currentCmd->queueTime()
0228                                         << m_currentCmd->gdbProcessingTime();
0229                 // Some commands want to handle errors themself.
0230                 if (m_currentCmd->handlesError() &&
0231                     m_currentCmd->invokeHandler(result))
0232                 {
0233                     qCDebug(DEBUGGERCOMMON) << "Invoked custom handler\n";
0234                     // Done, nothing more needed
0235                 }
0236                 else
0237                     emit error(result);
0238             }
0239             else
0240             {
0241                 qCDebug(DEBUGGERCOMMON) << "Unhandled result code: " << result.reason;
0242             }
0243 
0244             m_currentCmd.reset();
0245             emit ready();
0246             break;
0247         }
0248 
0249         case MI::Record::Async: {
0250             auto& async = static_cast<MI::AsyncRecord&>(*r);
0251 
0252             switch (async.subkind) {
0253             case MI::AsyncRecord::Exec: {
0254                 // Prefix '*'; asynchronous state changes of the target
0255                 if (async.reason == QLatin1String("stopped"))
0256                 {
0257                     emit programStopped(async);
0258                 }
0259                 else if (async.reason == QLatin1String("running"))
0260                 {
0261                     emit programRunning();
0262                 }
0263                 else
0264                 {
0265                     qCDebug(DEBUGGERCOMMON) << "Unhandled exec notification: " << async.reason;
0266                 }
0267                 break;
0268             }
0269 
0270             case MI::AsyncRecord::Notify: {
0271                 // Prefix '='; supplementary information that we should handle (new breakpoint etc.)
0272                 emit notification(async);
0273                 break;
0274             }
0275 
0276             case MI::AsyncRecord::Status: {
0277                 // Prefix '+'; GDB documentation:
0278                 // On-going status information about progress of a slow operation; may be ignored
0279                 break;
0280             }
0281             }
0282             break;
0283         }
0284 
0285         case MI::Record::Stream: {
0286 
0287             auto& s = static_cast<MI::StreamRecord&>(*r);
0288 
0289             if (s.subkind == MI::StreamRecord::Target) {
0290                 emit applicationOutput(s.message);
0291             } else if (s.subkind == MI::StreamRecord::Console) {
0292                 if (m_currentCmd && m_currentCmd->isUserCommand())
0293                     emit userCommandOutput(s.message);
0294                 else
0295                     emit internalCommandOutput(s.message);
0296 
0297                 if (m_currentCmd)
0298                     m_currentCmd->newOutput(s.message);
0299             } else {
0300                 emit debuggerInternalOutput(s.message);
0301             }
0302 
0303             emit streamRecord(s);
0304 
0305             break;
0306         }
0307 
0308         case MI::Record::Prompt:
0309             break;
0310         }
0311     #ifndef DEBUG_NO_TRY
0312     }
0313     catch(const std::exception& e)
0314     {
0315         KMessageBox::detailedError(
0316             qApp->activeWindow(),
0317             i18nc("<b>Internal debugger error</b>",
0318                     "<p>The debugger component encountered an internal error while "
0319                     "processing the reply from the debugger. Please submit a bug report. "
0320                     "The debug session will now end to prevent potential crash"),
0321             i18n("The exception is: %1\n"
0322                 "The MI response is: %2", QString::fromUtf8(e.what()),
0323                 QString::fromLatin1(line)),
0324             i18nc("@title:window", "Internal Debugger Error"));
0325         emit exited(true, QString::fromUtf8(e.what()));
0326     }
0327     #endif
0328 }
0329 
0330 void MIDebugger::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
0331 {
0332     qCDebug(DEBUGGERCOMMON) << "Debugger FINISHED\n";
0333 
0334     bool abnormal = exitCode != 0 || exitStatus != QProcess::NormalExit;
0335     emit userCommandOutput(QStringLiteral("Process exited\n"));
0336     emit exited(abnormal, i18n("Process exited"));
0337 }
0338 
0339 void MIDebugger::processErrored(QProcess::ProcessError error)
0340 {
0341     qCWarning(DEBUGGERCOMMON) << "Debugger ERRORED" << error << m_process->errorString();
0342     if(error == QProcess::FailedToStart)
0343     {
0344         const QString messageText =
0345             i18n("<b>Could not start debugger.</b>"
0346                  "<p>Could not run '%1'. "
0347                  "Make sure that the path name is specified correctly.",
0348                  m_debuggerExecutable);
0349         auto* message = new Sublime::Message(messageText, Sublime::Message::Error);
0350         KDevelop::ICore::self()->uiController()->postMessage(message);
0351 
0352         emit userCommandOutput(QStringLiteral("Process failed to start\n"));
0353         emit exited(true, i18n("Process failed to start"));
0354 
0355     } else if (error == QProcess::Crashed) {
0356         KMessageBox::error(
0357             qApp->activeWindow(),
0358             i18n("<b>Debugger crashed.</b>"
0359                  "<p>The debugger process '%1' crashed.<br>"
0360                  "Because of that the debug session has to be ended.<br>"
0361                  "Try to reproduce the crash without KDevelop and report a bug.<br>",
0362                  m_debuggerExecutable),
0363             i18nc("@title:window", "Debugger Crashed"));
0364 
0365         emit userCommandOutput(QStringLiteral("Process crashed\n"));
0366         emit exited(true, i18n("Process crashed"));
0367     }
0368 }
0369 
0370 #include "moc_midebugger.cpp"