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"