File indexing completed on 2024-05-05 04:39:53

0001 /*
0002     SPDX-FileCopyrightText: 1999-2001 John Birch <jbb@kdevelop.org>
0003     SPDX-FileCopyrightText: 2001 Bernd Gehrmann <bernd@kdevelop.org>
0004     SPDX-FileCopyrightText: 2006 Vladimir Prus <ghost@cs.msu.su>
0005     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
0006     SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "debugsession.h"
0012 
0013 #include "debuglog.h"
0014 #include "debuggerplugin.h"
0015 #include "gdb.h"
0016 #include "gdbbreakpointcontroller.h"
0017 #include "gdbframestackmodel.h"
0018 #include "mi/micommand.h"
0019 #include "stty.h"
0020 #include "variablecontroller.h"
0021 
0022 #include <debugger/breakpoint/breakpoint.h>
0023 #include <debugger/breakpoint/breakpointmodel.h>
0024 #include <execute/iexecuteplugin.h>
0025 #include <interfaces/icore.h>
0026 #include <interfaces/idebugcontroller.h>
0027 #include <interfaces/ilaunchconfiguration.h>
0028 #include <interfaces/iuicontroller.h>
0029 #include <sublime/message.h>
0030 #include <util/environmentprofilelist.h>
0031 
0032 #include <KLocalizedString>
0033 #include <KShell>
0034 
0035 #include <QApplication>
0036 #include <QDir>
0037 #include <QFileInfo>
0038 #include <QStandardPaths>
0039 #include <QGuiApplication>
0040 #include <QRegularExpression>
0041 #include <QVersionNumber>
0042 
0043 using namespace KDevMI::GDB;
0044 using namespace KDevMI::MI;
0045 using namespace KDevelop;
0046 
0047 DebugSession::DebugSession(CppDebuggerPlugin *plugin)
0048     : MIDebugSession(plugin)
0049 {
0050     m_breakpointController = new BreakpointController(this);
0051     m_variableController = new VariableController(this);
0052     m_frameStackModel = new GdbFrameStackModel(this);
0053 
0054     if (m_plugin) m_plugin->setupToolViews();
0055 }
0056 
0057 DebugSession::~DebugSession()
0058 {
0059     if (m_plugin) m_plugin->unloadToolViews();
0060 }
0061 
0062 void DebugSession::setAutoDisableASLR(bool enable)
0063 {
0064     m_autoDisableASLR = enable;
0065 }
0066 
0067 BreakpointController *DebugSession::breakpointController() const
0068 {
0069     return m_breakpointController;
0070 }
0071 
0072 VariableController *DebugSession::variableController() const
0073 {
0074     return m_variableController;
0075 }
0076 
0077 GdbFrameStackModel *DebugSession::frameStackModel() const
0078 {
0079     return m_frameStackModel;
0080 }
0081 
0082 GdbDebugger *DebugSession::createDebugger() const
0083 {
0084     return new GdbDebugger;
0085 }
0086 
0087 void DebugSession::initializeDebugger()
0088 {
0089     //addCommand(new GDBCommand(GDBMI::EnableTimings, "yes"));
0090 
0091     addCommand(
0092         std::make_unique<CliCommand>(MI::GdbShow, QStringLiteral("version"), this, &DebugSession::handleVersion));
0093 
0094     // This makes gdb pump a variable out on one line.
0095     addCommand(MI::GdbSet, QStringLiteral("width 0"));
0096     addCommand(MI::GdbSet, QStringLiteral("height 0"));
0097 
0098     addCommand(MI::SignalHandle, QStringLiteral("SIG32 pass nostop noprint"));
0099     addCommand(MI::SignalHandle, QStringLiteral("SIG41 pass nostop noprint"));
0100     addCommand(MI::SignalHandle, QStringLiteral("SIG42 pass nostop noprint"));
0101     addCommand(MI::SignalHandle, QStringLiteral("SIG43 pass nostop noprint"));
0102 
0103     addCommand(MI::EnablePrettyPrinting);
0104 
0105     addCommand(MI::GdbSet, QStringLiteral("charset UTF-8"));
0106     addCommand(MI::GdbSet, QStringLiteral("print sevenbit-strings off"));
0107 
0108     QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
0109                                               QStringLiteral("kdevgdb/printers/gdbinit"));
0110     if (!fileName.isEmpty()) {
0111         QFileInfo fileInfo(fileName);
0112         QString quotedPrintersPath = fileInfo.dir().path()
0113                                              .replace(QLatin1Char('\\'), QLatin1String("\\\\"))
0114                                              .replace(QLatin1Char('"'), QLatin1String("\\\""));
0115         addCommand(MI::NonMI,
0116                    QStringLiteral("python sys.path.insert(0, \"%0\")").arg(quotedPrintersPath));
0117         addCommand(MI::NonMI, QLatin1String("source ") + fileName);
0118     }
0119 
0120     // GDB can't disable ASLR on CI server.
0121     addCommand(MI::GdbSet,
0122                QStringLiteral("disable-randomization %1").arg(m_autoDisableASLR ? QLatin1String("on") : QLatin1String("off")));
0123 
0124     qCDebug(DEBUGGERGDB) << "Initialized GDB";
0125 }
0126 
0127 void DebugSession::configInferior(ILaunchConfiguration *cfg, IExecutePlugin *iexec, const QString &)
0128 {
0129     // Read Configuration values
0130     KConfigGroup grp = cfg->config();
0131     bool breakOnStart = grp.readEntry(KDevMI::Config::BreakOnStartEntry, false);
0132     bool displayStaticMembers = grp.readEntry(Config::StaticMembersEntry, false);
0133     bool asmDemangle = grp.readEntry(Config::DemangleNamesEntry, true);
0134 
0135     if (breakOnStart) {
0136         BreakpointModel* m = ICore::self()->debugController()->breakpointModel();
0137         bool found = false;
0138         const auto breakpoints = m->breakpoints();
0139         for (Breakpoint* b : breakpoints) {
0140             if (b->location() == QLatin1String("main")) {
0141                 found = true;
0142                 break;
0143             }
0144         }
0145         if (!found) {
0146             m->addCodeBreakpoint(QStringLiteral("main"));
0147         }
0148     }
0149     // Needed so that breakpoint widget has a chance to insert breakpoints.
0150     // FIXME: a bit hacky, as we're really not ready for new commands.
0151     setDebuggerStateOn(s_dbgBusy);
0152     raiseEvent(debugger_ready);
0153 
0154     if (displayStaticMembers) {
0155         addCommand(MI::GdbSet, QStringLiteral("print static-members on"));
0156     } else {
0157         addCommand(MI::GdbSet, QStringLiteral("print static-members off"));
0158     }
0159 
0160     if (asmDemangle) {
0161         addCommand(MI::GdbSet, QStringLiteral("print asm-demangle on"));
0162     } else {
0163         addCommand(MI::GdbSet, QStringLiteral("print asm-demangle off"));
0164     }
0165 
0166     // Set the environment variables
0167     const EnvironmentProfileList environmentProfiles(KSharedConfig::openConfig());
0168     QString envProfileName = iexec->environmentProfileName(cfg);
0169     if (envProfileName.isEmpty()) {
0170         qCWarning(DEBUGGERGDB) << i18n("No environment profile specified, looks like a broken "
0171                                        "configuration, please check run configuration '%1'. "
0172                                        "Using default environment profile.", cfg->name());
0173         envProfileName = environmentProfiles.defaultProfileName();
0174     }
0175     const auto& envvars = environmentProfiles.createEnvironment(envProfileName, {});
0176     for (const auto& envvar : envvars) {
0177         addCommand(GdbSet, QLatin1String("environment ") + envvar);
0178     }
0179 
0180     qCDebug(DEBUGGERGDB) << "Per inferior configuration done";
0181 }
0182 
0183 bool DebugSession::execInferior(KDevelop::ILaunchConfiguration *cfg, IExecutePlugin *, const QString &executable)
0184 {
0185     qCDebug(DEBUGGERGDB) << "Executing inferior";
0186 
0187     KConfigGroup grp = cfg->config();
0188     QUrl configGdbScript = grp.readEntry(Config::RemoteGdbConfigEntry, QUrl());
0189     QUrl runShellScript = grp.readEntry(Config::RemoteGdbShellEntry, QUrl());
0190     QUrl runGdbScript = grp.readEntry(Config::RemoteGdbRunEntry, QUrl());
0191 
0192     // handle remote debug
0193     if (configGdbScript.isValid()) {
0194         addCommand(MI::NonMI, QLatin1String("source ") + configGdbScript.toLocalFile());
0195     }
0196 
0197     // FIXME: have a check box option that controls remote debugging
0198     if (runShellScript.isValid()) {
0199         // Special for remote debug, the remote inferior is started by this shell script
0200         const auto tty = m_tty->getSlave();
0201         const auto options = QString(QLatin1String(">") + tty + QLatin1String("  2>&1 <") + tty);
0202 
0203         const QStringList arguments {
0204             QStringLiteral("-c"),
0205             KShell::quoteArg(runShellScript.toLocalFile()) + QLatin1Char(' ') + KShell::quoteArg(executable) + options,
0206         };
0207 
0208         qCDebug(DEBUGGERGDB) << "starting sh" << arguments;
0209         QProcess::startDetached(QStringLiteral("sh"), arguments);
0210     }
0211 
0212     if (runGdbScript.isValid()) {
0213         // Special for remote debug, gdb script at run is requested, to connect to remote inferior
0214 
0215         // Race notice: wait for the remote gdbserver/executable
0216         // - but that might be an issue for this script to handle...
0217 
0218         // Note: script could contain "run" or "continue"
0219 
0220         // Future: the shell script should be able to pass info (like pid)
0221         // to the gdb script...
0222 
0223         addCommand(std::make_unique<SentinelCommand>(
0224             [this, runGdbScript]() {
0225                 breakpointController()->initSendBreakpoints();
0226 
0227                 breakpointController()->setDeleteDuplicateBreakpoints(true);
0228                 qCDebug(DEBUGGERGDB) << "Running gdb script " << KShell::quoteArg(runGdbScript.toLocalFile());
0229 
0230                 addCommand(
0231                     MI::NonMI, QLatin1String("source ") + runGdbScript.toLocalFile(),
0232                     [this](const MI::ResultRecord&) { breakpointController()->setDeleteDuplicateBreakpoints(false); },
0233                     CmdMaybeStartsRunning);
0234                 raiseEvent(connected_to_program);
0235             },
0236             CmdMaybeStartsRunning));
0237     } else {
0238         // normal local debugging
0239         addCommand(MI::FileExecAndSymbols, KShell::quoteArg(executable),
0240                    this, &DebugSession::handleFileExecAndSymbols,
0241                    CmdHandlesError);
0242         raiseEvent(connected_to_program);
0243 
0244         addCommand(std::make_unique<SentinelCommand>(
0245             [this]() {
0246                 breakpointController()->initSendBreakpoints();
0247                 addCommand(MI::ExecRun, QString(), CmdMaybeStartsRunning);
0248             },
0249             CmdMaybeStartsRunning));
0250     }
0251     return true;
0252 }
0253 
0254 bool DebugSession::loadCoreFile(KDevelop::ILaunchConfiguration*,
0255                                 const QString& debugee, const QString& corefile)
0256 {
0257     addCommand(MI::FileExecAndSymbols, debugee,
0258                this, &DebugSession::handleFileExecAndSymbols,
0259                CmdHandlesError);
0260     raiseEvent(connected_to_program);
0261 
0262     addCommand(NonMI, QLatin1String("core ") + corefile,
0263                this, &DebugSession::handleCoreFile,
0264                CmdHandlesError);
0265     return true;
0266 }
0267 
0268 void DebugSession::handleVersion(const QStringList& s)
0269 {
0270     static const QVersionNumber minRequiredVersion(7, 0, 0);
0271     static const QRegularExpression versionRegExp(QStringLiteral("([0-9]+)\\.([0-9]+)(\\.([0-9]+))?"));
0272     QString detectedVersion = i18n("<unknown version>");
0273 
0274     for (const QString& response : s) {
0275         qCDebug(DEBUGGERGDB) << response;
0276 
0277         if (!response.contains(QLatin1String{"GNU gdb"})) {
0278             continue; // this line is not a version string, skip it
0279         }
0280 
0281         const auto match = versionRegExp.match(response);
0282         if (match.hasMatch() && QVersionNumber::fromString(match.capturedView()) >= minRequiredVersion) {
0283             return; // Early exit. Version check passed.
0284         }
0285 
0286         detectedVersion = response;
0287     }
0288 
0289     if (!qobject_cast<QGuiApplication*>(qApp)) {
0290         //for unittest
0291         qFatal("You need a graphical application.");
0292     }
0293 
0294     // TODO: reuse minRequiredVersion in the error message text when the minimum
0295     // required GDB version changes or the message is modified for some other reason.
0296     const QString messageText = i18n("<b>You need gdb 7.0.0 or higher.</b><br />"
0297         "You are using: %1",
0298         detectedVersion);
0299     auto* message = new Sublime::Message(messageText, Sublime::Message::Error);
0300     ICore::self()->uiController()->postMessage(message);
0301     stopDebugger();
0302 }
0303 
0304 void DebugSession::handleFileExecAndSymbols(const ResultRecord& r)
0305 {
0306     if (r.reason == QLatin1String("error")) {
0307         const QString messageText =
0308             i18n("<b>Could not start debugger:</b><br />")+
0309                  r[QStringLiteral("msg")].literal();
0310         auto* message = new Sublime::Message(messageText, Sublime::Message::Error);
0311         ICore::self()->uiController()->postMessage(message);
0312         stopDebugger();
0313     }
0314 }
0315 
0316 void DebugSession::handleCoreFile(const ResultRecord& r)
0317 {
0318     if (r.reason != QLatin1String("error")) {
0319         setDebuggerStateOn(s_programExited | s_core);
0320     } else {
0321         const QString messageText =
0322             i18n("<b>Failed to load core file</b>"
0323                  "<p>Debugger reported the following error:"
0324                  "<p><tt>%1",
0325                  r[QStringLiteral("msg")].literal());
0326         auto* message = new Sublime::Message(messageText, Sublime::Message::Error);
0327         ICore::self()->uiController()->postMessage(message);
0328         stopDebugger();
0329     }
0330 }
0331 
0332 #include "moc_debugsession.cpp"