File indexing completed on 2024-04-28 04:38:57
0001 /* 0002 SPDX-FileCopyrightText: 2016 Aetf <aetf@unlimitedcodeworks.xyz> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "debugsession.h" 0008 0009 #include "controllers/variable.h" 0010 #include "dbgglobal.h" 0011 #include "debuggerplugin.h" 0012 #include "debuglog.h" 0013 #include "lldbcommand.h" 0014 #include "mi/micommand.h" 0015 #include "stty.h" 0016 #include "stringhelpers.h" 0017 0018 #include <debugger/breakpoint/breakpoint.h> 0019 #include <debugger/breakpoint/breakpointmodel.h> 0020 #include <execute/iexecuteplugin.h> 0021 #include <interfaces/icore.h> 0022 #include <interfaces/idebugcontroller.h> 0023 #include <interfaces/iuicontroller.h> 0024 #include <interfaces/ilaunchconfiguration.h> 0025 #include <sublime/message.h> 0026 #include <util/environmentprofilelist.h> 0027 0028 #include <KConfigGroup> 0029 #include <KLocalizedString> 0030 #include <KMessageBox> 0031 #include <KMessageBox_KDevCompat> 0032 #include <KShell> 0033 0034 #include <QApplication> 0035 #include <QDir> 0036 #include <QFileInfo> 0037 #include <QStandardPaths> 0038 #include <QGuiApplication> 0039 0040 using namespace KDevMI::LLDB; 0041 using namespace KDevMI::MI; 0042 using namespace KDevMI; 0043 using namespace KDevelop; 0044 0045 struct ExecRunHandler : public MICommandHandler 0046 { 0047 explicit ExecRunHandler(DebugSession *session, int maxRetry = 5) 0048 : m_session(session) 0049 , m_remainRetry(maxRetry) 0050 , m_activeCommands(1) 0051 { 0052 } 0053 0054 void handle(const ResultRecord& r) override 0055 { 0056 --m_activeCommands; 0057 if (r.reason == QLatin1String("error")) { 0058 if (r.hasField(QStringLiteral("msg")) 0059 && r[QStringLiteral("msg")].literal().contains(QLatin1String("Invalid process during debug session"))) { 0060 // for some unknown reason, lldb-mi sometimes fails to start process 0061 if (m_remainRetry && m_session) { 0062 qCDebug(DEBUGGERLLDB) << "Retry starting"; 0063 --m_remainRetry; 0064 // resend the command again. 0065 ++m_activeCommands; 0066 m_session->addCommand(ExecRun, QString(), 0067 this, // use *this as handler, so we can track error times 0068 CmdMaybeStartsRunning | CmdHandlesError); 0069 return; 0070 } 0071 } 0072 qCDebug(DEBUGGERLLDB) << "Failed to start inferior:" 0073 << "exceeded retry times or session become invalid"; 0074 m_session->stopDebugger(); 0075 } 0076 if (m_activeCommands == 0) 0077 delete this; 0078 } 0079 0080 bool handlesError() override { return true; } 0081 bool autoDelete() override { return false; } 0082 0083 QPointer<DebugSession> m_session; 0084 int m_remainRetry; 0085 int m_activeCommands; 0086 }; 0087 0088 DebugSession::DebugSession(LldbDebuggerPlugin *plugin) 0089 : MIDebugSession(plugin) 0090 , m_formatterPath() 0091 { 0092 m_breakpointController = new BreakpointController(this); 0093 m_variableController = new VariableController(this); 0094 m_frameStackModel = new LldbFrameStackModel(this); 0095 0096 if (m_plugin) m_plugin->setupToolViews(); 0097 0098 connect(this, &DebugSession::stateChanged, this, &DebugSession::handleSessionStateChange); 0099 } 0100 0101 DebugSession::~DebugSession() 0102 { 0103 if (m_plugin) m_plugin->unloadToolViews(); 0104 } 0105 0106 BreakpointController *DebugSession::breakpointController() const 0107 { 0108 return m_breakpointController; 0109 } 0110 0111 VariableController *DebugSession::variableController() const 0112 { 0113 return m_variableController; 0114 } 0115 0116 LldbFrameStackModel *DebugSession::frameStackModel() const 0117 { 0118 return m_frameStackModel; 0119 } 0120 0121 LldbDebugger *DebugSession::createDebugger() const 0122 { 0123 return new LldbDebugger; 0124 } 0125 0126 std::unique_ptr<MICommand> DebugSession::createCommand(MI::CommandType type, const QString& arguments, 0127 MI::CommandFlags flags) const 0128 { 0129 // using protected ctor, cannot use make_unique 0130 return std::unique_ptr<MICommand>(new LldbCommand(type, arguments, flags)); 0131 } 0132 0133 std::unique_ptr<MICommand> DebugSession::createUserCommand(const QString& cmd) const 0134 { 0135 if (m_hasCorrectCLIOutput) 0136 return MIDebugSession::createUserCommand(cmd); 0137 auto msg = i18n("Attempting to execute user command on unsupported LLDB version"); 0138 emit debuggerInternalOutput(msg); 0139 qCDebug(DEBUGGERLLDB) << "Attempting user command on unsupported LLDB version"; 0140 return nullptr; 0141 } 0142 0143 void DebugSession::setFormatterPath(const QString &path) 0144 { 0145 m_formatterPath = path; 0146 } 0147 0148 void DebugSession::initializeDebugger() 0149 { 0150 //addCommand(MI::EnableTimings, "yes"); 0151 0152 // Check version 0153 addCommand(std::make_unique<CliCommand>(MI::NonMI, QStringLiteral("version"), this, &DebugSession::handleVersion)); 0154 0155 // load data formatter 0156 auto formatterPath = m_formatterPath; 0157 if (!QFileInfo(formatterPath).isFile()) { 0158 formatterPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, 0159 QStringLiteral("kdevlldb/formatters/all.py")); 0160 } 0161 if (!formatterPath.isEmpty()) { 0162 addCommand(MI::NonMI, QLatin1String("command script import ") + KShell::quoteArg(formatterPath)); 0163 } 0164 0165 0166 // Treat char array as string 0167 addCommand(MI::GdbSet, QStringLiteral("print char-array-as-string on")); 0168 0169 // set a larger term width. 0170 // TODO: set term-width to exact max column count in console view 0171 addCommand(MI::NonMI, QStringLiteral("settings set term-width 1024")); 0172 0173 qCDebug(DEBUGGERLLDB) << "Initialized LLDB"; 0174 } 0175 0176 void DebugSession::configInferior(ILaunchConfiguration *cfg, IExecutePlugin *iexec, const QString &executable) 0177 { 0178 // Read Configuration values 0179 KConfigGroup grp = cfg->config(); 0180 0181 // Create target as early as possible, so we can do target specific configuration later 0182 QString filesymbols = Utils::quote(executable); 0183 bool remoteDebugging = grp.readEntry(Config::LldbRemoteDebuggingEntry, false); 0184 if (remoteDebugging) { 0185 auto connStr = grp.readEntry(Config::LldbRemoteServerEntry, QString()); 0186 auto remoteDir = grp.readEntry(Config::LldbRemotePathEntry, QString()); 0187 auto remoteExe = QDir(remoteDir).filePath(QFileInfo(executable).fileName()); 0188 0189 filesymbols += QLatin1String(" -r ") + Utils::quote(remoteExe); 0190 0191 addCommand(MI::FileExecAndSymbols, filesymbols, 0192 this, &DebugSession::handleFileExecAndSymbols, 0193 CmdHandlesError); 0194 0195 addCommand(MI::TargetSelect, QLatin1String("remote ") + connStr, 0196 this, &DebugSession::handleTargetSelect, CmdHandlesError); 0197 0198 // ensure executable is on remote end 0199 addCommand(MI::NonMI, QStringLiteral("platform mkdir -v 755 %0").arg(Utils::quote(remoteDir))); 0200 addCommand(MI::NonMI, QStringLiteral("platform put-file %0 %1") 0201 .arg(Utils::quote(executable), Utils::quote(remoteExe))); 0202 } else { 0203 addCommand(MI::FileExecAndSymbols, filesymbols, 0204 this, &DebugSession::handleFileExecAndSymbols, 0205 CmdHandlesError); 0206 } 0207 0208 raiseEvent(connected_to_program); 0209 0210 // Set the environment variables has effect only after target created 0211 const EnvironmentProfileList environmentProfiles(KSharedConfig::openConfig()); 0212 QString envProfileName = iexec->environmentProfileName(cfg); 0213 if (envProfileName.isEmpty()) { 0214 envProfileName = environmentProfiles.defaultProfileName(); 0215 } 0216 const auto &envVariables = environmentProfiles.variables(envProfileName); 0217 if (!envVariables.isEmpty()) { 0218 QStringList vars; 0219 vars.reserve(envVariables.size()); 0220 for (auto it = envVariables.constBegin(), ite = envVariables.constEnd(); it != ite; ++it) { 0221 vars.append(QStringLiteral("%0=%1").arg(it.key(), Utils::quote(it.value()))); 0222 } 0223 // actually using lldb command 'settings set target.env-vars' which accepts multiple values 0224 addCommand(GdbSet, QLatin1String("environment ") + vars.join(QLatin1Char(' '))); 0225 } 0226 0227 // Break on start: can't use "-exec-run --start" because in lldb-mi 0228 // the inferior stops without any notification 0229 bool breakOnStart = grp.readEntry(KDevMI::Config::BreakOnStartEntry, false); 0230 if (breakOnStart) { 0231 BreakpointModel* m = ICore::self()->debugController()->breakpointModel(); 0232 bool found = false; 0233 const auto breakpoints = m->breakpoints(); 0234 for (Breakpoint* b : breakpoints) { 0235 if (b->location() == QLatin1String("main")) { 0236 found = true; 0237 break; 0238 } 0239 } 0240 if (!found) { 0241 m->addCodeBreakpoint(QStringLiteral("main")); 0242 } 0243 } 0244 0245 // Needed so that breakpoint widget has a chance to insert breakpoints. 0246 // FIXME: a bit hacky, as we're really not ready for new commands. 0247 setDebuggerStateOn(s_dbgBusy); 0248 raiseEvent(debugger_ready); 0249 0250 qCDebug(DEBUGGERLLDB) << "Per inferior configuration done"; 0251 } 0252 0253 bool DebugSession::execInferior(ILaunchConfiguration *cfg, IExecutePlugin *, const QString &) 0254 { 0255 qCDebug(DEBUGGERLLDB) << "Executing inferior"; 0256 0257 KConfigGroup grp = cfg->config(); 0258 0259 // Start inferior 0260 bool remoteDebugging = grp.readEntry(Config::LldbRemoteDebuggingEntry, false); 0261 QUrl configLldbScript = grp.readEntry(Config::LldbConfigScriptEntry, QUrl()); 0262 addCommand(std::make_unique<SentinelCommand>( 0263 [this, remoteDebugging, configLldbScript]() { 0264 // setup inferior I/O redirection 0265 if (!remoteDebugging) { 0266 // FIXME: a hacky way to emulate tty setting on linux. Not sure if this provides all needed 0267 // functionalities of a pty. Should make this conditional on other platforms. 0268 0269 // no need to quote, settings set takes 'raw' input 0270 addCommand(MI::NonMI, QStringLiteral("settings set target.input-path %0").arg(m_tty->getSlave())); 0271 addCommand(MI::NonMI, QStringLiteral("settings set target.output-path %0").arg(m_tty->getSlave())); 0272 addCommand(MI::NonMI, QStringLiteral("settings set target.error-path %0").arg(m_tty->getSlave())); 0273 } else { 0274 // what is the expected behavior for using external terminal when doing remote debugging? 0275 } 0276 0277 // send breakpoints already in our breakpoint model to lldb 0278 auto bc = breakpointController(); 0279 bc->initSendBreakpoints(); 0280 0281 qCDebug(DEBUGGERLLDB) << "Turn on delete duplicate mode"; 0282 // turn on delete duplicate breakpoints model, so that breakpoints created by user command in 0283 // the script and returned as a =breakpoint-created notification won't get duplicated with the 0284 // one already in our model. 0285 // we will turn this model off once we first reach a paused state, and from that time on, 0286 // the user can create duplicated breakpoints using normal command. 0287 bc->setDeleteDuplicateBreakpoints(true); 0288 // run custom config script right before we starting the inferior, 0289 // so the user has the freedom to change everything. 0290 if (configLldbScript.isValid()) { 0291 addCommand(MI::NonMI, 0292 QLatin1String("command source -s 0 ") + KShell::quoteArg(configLldbScript.toLocalFile())); 0293 } 0294 0295 addCommand(MI::ExecRun, QString(), new ExecRunHandler(this), CmdMaybeStartsRunning | CmdHandlesError); 0296 }, 0297 CmdMaybeStartsRunning)); 0298 return true; 0299 } 0300 0301 bool DebugSession::loadCoreFile(ILaunchConfiguration *, 0302 const QString &debugee, const QString &corefile) 0303 { 0304 addCommand(MI::FileExecAndSymbols, debugee, 0305 this, &DebugSession::handleFileExecAndSymbols, 0306 CmdHandlesError); 0307 raiseEvent(connected_to_program); 0308 0309 addCommand(std::make_unique<CliCommand>(NonMI, QLatin1String("target create -c ") + Utils::quote(corefile), this, 0310 &DebugSession::handleCoreFile, CmdHandlesError)); 0311 return true; 0312 } 0313 0314 void DebugSession::interruptDebugger() 0315 { 0316 if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) 0317 return; 0318 0319 addCommand(ExecInterrupt, QString(), CmdInterrupt); 0320 return; 0321 } 0322 0323 void DebugSession::ensureDebuggerListening() 0324 { 0325 // lldb always uses async mode and prompt is always available. 0326 // no need to interrupt 0327 setDebuggerStateOff(s_dbgNotListening); 0328 // NOTE: there is actually a bug in lldb-mi that, when receiving SIGINT, 0329 // it would print '^done', which doesn't corresponds to any previous command. 0330 // This confuses our command queue. 0331 } 0332 0333 void DebugSession::handleFileExecAndSymbols(const MI::ResultRecord& r) 0334 { 0335 if (r.reason == QLatin1String("error")) { 0336 const QString messageText = i18n("<b>Could not start debugger:</b><br />")+ 0337 r[QStringLiteral("msg")].literal(); 0338 auto* message = new Sublime::Message(messageText, Sublime::Message::Error); 0339 ICore::self()->uiController()->postMessage(message); 0340 stopDebugger(); 0341 } 0342 } 0343 0344 void DebugSession::handleTargetSelect(const MI::ResultRecord& r) 0345 { 0346 if (r.reason == QLatin1String("error")) { 0347 const QString messageText = i18n("<b>Error connecting to remote target:</b><br />")+ 0348 r[QStringLiteral("msg")].literal(); 0349 auto* message = new Sublime::Message(messageText, Sublime::Message::Error); 0350 ICore::self()->uiController()->postMessage(message); 0351 stopDebugger(); 0352 } 0353 } 0354 0355 void DebugSession::handleCoreFile(const QStringList &s) 0356 { 0357 qCDebug(DEBUGGERLLDB) << s; 0358 for (const auto &line : s) { 0359 if (line.startsWith(QLatin1String("error:"))) { 0360 const QString messageText = i18n("<b>Failed to load core file</b>" 0361 "<p>Debugger reported the following error:" 0362 "<p><tt>%1", 0363 s.join(QLatin1Char('\n'))); 0364 auto* message = new Sublime::Message(messageText, Sublime::Message::Error); 0365 ICore::self()->uiController()->postMessage(message); 0366 stopDebugger(); 0367 return; 0368 } 0369 } 0370 // There's no "thread-group-started" notification from lldb-mi, so pretend we have received one. 0371 // see MIDebugSession::processNotification(const MI::AsyncRecord & async) 0372 setDebuggerStateOff(s_appNotStarted | s_programExited); 0373 0374 setDebuggerStateOn(s_programExited | s_core); 0375 } 0376 0377 void DebugSession::handleVersion(const QStringList& s) 0378 { 0379 m_hasCorrectCLIOutput = !s.isEmpty(); 0380 if (!m_hasCorrectCLIOutput) { 0381 // No output from 'version' command. It's likely that 0382 // the lldb used is not patched for the CLI output 0383 0384 if (!qobject_cast<QGuiApplication*>(qApp)) { 0385 //for unittest 0386 qFatal("You need a graphical application."); 0387 } 0388 0389 auto ans = KMessageBox::warningTwoActions( 0390 qApp->activeWindow(), 0391 i18n("<b>Your lldb-mi version is unsupported, as it lacks an essential patch.</b><br/>" 0392 "See https://llvm.org/bugs/show_bug.cgi?id=28026 for more information.<br/>" 0393 "Debugger console will be disabled to prevent crash.<br/>" 0394 "Do you want to continue?"), 0395 i18n("LLDB Version Unsupported"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), 0396 QStringLiteral("unsupported-lldb-debugger")); 0397 if (ans == KMessageBox::SecondaryAction) { 0398 programFinished(QStringLiteral("Stopped because of unsupported LLDB version")); 0399 stopDebugger(); 0400 } 0401 return; 0402 } 0403 0404 qCDebug(DEBUGGERLLDB) << s.first(); 0405 0406 // minimal version is 3.8.1 0407 #ifdef Q_OS_OSX 0408 QRegularExpression rx(QStringLiteral("^lldb-(\\d+).(\\d+).(\\d+)\\b"), QRegularExpression::MultilineOption); 0409 // lldb 3.8.1 reports version 350.99.0 on OS X 0410 const int min_ver[] = {350, 99, 0}; 0411 #else 0412 QRegularExpression rx(QStringLiteral("^lldb version (\\d+).(\\d+).(\\d+)\\b"), QRegularExpression::MultilineOption); 0413 const int min_ver[] = {3, 8, 1}; 0414 #endif 0415 0416 auto match = rx.match(s.first()); 0417 int version[] = {0, 0, 0}; 0418 if (match.hasMatch()) { 0419 for (int i = 0; i != 3; ++i) { 0420 version[i] = match.capturedRef(i+1).toInt(); 0421 } 0422 } 0423 0424 bool ok = true; 0425 for (int i = 0; i < 3; ++i) { 0426 if (version[i] < min_ver[i]) { 0427 ok = false; 0428 break; 0429 } else if (version[i] > min_ver[i]) { 0430 ok = true; 0431 break; 0432 } 0433 } 0434 0435 if (!ok) { 0436 if (!qobject_cast<QGuiApplication*>(qApp)) { 0437 //for unittest 0438 qFatal("You need a graphical application."); 0439 } 0440 0441 const QString messageText = i18n("<b>You need lldb-mi from LLDB 3.8.1 or higher.</b><br />" 0442 "You are using: %1", s.first()); 0443 auto* message = new Sublime::Message(messageText, Sublime::Message::Error); 0444 ICore::self()->uiController()->postMessage(message); 0445 stopDebugger(); 0446 } 0447 } 0448 0449 void DebugSession::updateAllVariables() 0450 { 0451 // FIXME: this is only a workaround for lldb-mi doesn't provide -var-update changelist 0452 // for variables that have a python synthetic provider. Remove this after this is fixed 0453 // in the upstream. 0454 0455 // re-fetch all toplevel variables, as -var-update doesn't work with data formatter 0456 // we have to pick out top level variables first, as refetching will delete child 0457 // variables. 0458 QList<LldbVariable*> toplevels; 0459 for (auto* variable : qAsConst(m_allVariables)) { 0460 auto *var = qobject_cast<LldbVariable*>(variable); 0461 if (var->topLevel()) { 0462 toplevels << var; 0463 } 0464 } 0465 0466 for (auto var : qAsConst(toplevels)) { 0467 var->refetch(); 0468 } 0469 } 0470 0471 void DebugSession::handleSessionStateChange(IDebugSession::DebuggerState state) 0472 { 0473 if (state == IDebugSession::PausedState) { 0474 // session is paused, the user can input any commands now. 0475 // Turn off delete duplicate breakpoints mode, as the user 0476 // may intentionally want to do this. 0477 qCDebug(DEBUGGERLLDB) << "Turn off delete duplicate mode"; 0478 breakpointController()->setDeleteDuplicateBreakpoints(false); 0479 } 0480 } 0481 0482 #include "moc_debugsession.cpp"