diff --git a/debuggers/lldb/TODO.txt b/debuggers/lldb/TODO.txt index e66bd411a8..9c00fe8624 100644 --- a/debuggers/lldb/TODO.txt +++ b/debuggers/lldb/TODO.txt @@ -1,144 +1,145 @@ - [DONE] Correct signal handling commands for lldb - [DONE] Correct environment variable setting commands for lldb - [DONE] Port DebugJob - [DONE] Port exam core and attach process to KJob - [DONE] Move DebuggerPlugin to MIDebuggerPlugin - [DONE] Verify how unit test works in test_gdb - [DONE] Create minimal unit test cases to be able to test debugger and debugsession + [DONE] can break on start + [DONE] environment var successfully set + Check if environment-cd works in remote debugging + [DONE] Check if lldb supports unicode correctly * [DONE] a program output in encoding other than utf8 and different from host system default + Make examine core test working with coredumpctl - [DONE] Verify MIParser/MILexer can handle output without any stream prefix: no they can't - [DONE] Make config options in config page actually working - [DONE] Remove duplicate breakpoint: look at breakpointController()->setDeleteDuplicateBreakpoints(true) - [DONE] Rework interrupt action -- Variables view doesn't update when variable changes +- [DONE] (only workaround) Variables view doesn't update when variable changes - Add lldb actions for attach to process and examine core file - Clean up extra actions provided by gdb/lldb plugins * Global launch configuration for attach to process and examine core file - LLDB data formatters - * show summary for const char [] + * [DONE] show summary for const char [] * Qt types + [DONE] QString, QChar + [DONE] QByteArray + [DONE] QList, QStringList, QQueue + [DONE] QVector, QStack + [DONE] QLinkedList + [DONE] QMap, QMultiMap + [DONE] QHash, QMultiHash + [DONE] QSet + [DONE] QDate, QTime, QDateTime + [DONE] QUrl + [DONE] QUuid * KDE types + [DONE] KDevelop::Path + [DONE] KTextEditor::Cursor + [DONE] KTextEditor::Range +- Finish unit tests for LLDB data formatters - [DONE] Show application exit reason in the Debug View - Clean up tool views * register views + controller + tool view * disassembly widget * memory view - Qt data formatter cause hangs data size is too large * use dynamic caching - [DONE] Find a way to avoid duplicate tool views for GDB and LLDB plugin - Polish debugger console * [DONE] user command output regarded as internal command * [DONE] not correctly raised when starting debug * correct prompt (from "(gdb)" to "(lldb)") - [DONE] Handle error sometime with Command 'exec-run'. Invalid process during debug session. - An unified way to report error - File bug to lldb-mi * HIGH PRIORITY: -break-insert + [SUBMITED] pending breakpoints set with '-f' not got resolved after file loaded - https://llvm.org/bugs/show_bug.cgi?id=28702 + [SUBMITED] pending breakpoints '-f' can only be last flag switch - https://llvm.org/bugs/show_bug.cgi?id=28698 + [SUBMITED] create disabled breakpoint with '-d' not working when combined with '-f' - https://llvm.org/bugs/show_bug.cgi?id=28703 + [SUBMITED] -break-enable has no effect - https://llvm.org/bugs/show_bug.cgi?id=28857 * [SUBMITED] breakpoint hit doesn't generate corresponding breakpoint-modified notification - https://llvm.org/bugs/show_bug.cgi?id=28860 (needed to update hitCount) * -break-watch command not supported + use raw cli command 'break set var' doesn't provides MI response * when hit watch point, nothing is output, which confuses the controller * thread-info returns malformated result + there should be only one 'frame' key for each thread in the list + [FIXED] there shoule be a current-thread-id field (Fixed at least in revision 265858) * [FIXED] lldb-mi crashes when break on a point where multiple threads running. (Fixed at least in revision 265858) * var-update doesn't support * as variable name * can't have space in environment cd * -inferior-tty-set only has dummy implementation * 'process launch' doesn't provide thread-group-started notification * [SUBMITED] sliently stop when attaching to process, which confuses the controller - https://llvm.org/bugs/show_bug.cgi?id=28858 * [SUBMITED] sliently stop when request stop at start - https://llvm.org/bugs/show_bug.cgi?id=25000 * [SUBMITED] [PATCHED] cli output not wrapped in console stream record * [SUBMITED][PATCHED] -data-disassemble start and end address parameter doesn't accept expressions - https://llvm.org/bugs/show_bug.cgi?id=28859 * -gdb-set not fully implemented + environment + [SUBMITED] [PATCHED] disassembly-flavor - https://llvm.org/bugs/show_bug.cgi?id=28718 * -gdb-show not fully implemented + [SUBMITED] [PATCHED] disassembly-flavor - https://llvm.org/bugs/show_bug.cgi?id=28718 * -data-list-register-values output format doesn't conform to spec * [SUBMITED] -stack-list-locals shows empty list - https://llvm.org/bugs/show_bug.cgi?id=28621 * File bug to lldb-mi for other missing commands - Fix TODOs in files - Change test_gdb to avoid direct use of QTest::qWait, which starts event loop, and could cause session to be deleted. Use WAIT_FOR_STATE_AND_IDLE(session, ) instead. Or use QPointer for session. - Known issues * Debugger console + debugger CLI stdout isn't shown, due to bug https://llvm.org/bugs/show_bug.cgi?id=28026 * Remote debugging + When using 'lldb-server gdbserver' as remote server, server exits once debug session ended. + When using 'gdbserver' as remote server - Remote work path can't contain space - Can't actually start inferior * Breakpoints + Pending breakpoints doesn't work. (Break on start now works even without pending breakpoint support) - Can still manually set pending breakpoints + Breakpoint hit count is not updated timely (limitation in lldb-mi) + No watchpoint support - Can still manually add watch point * Threads + lldb-mi crashes when break on a point where multiple threads running. (Fixed in latest lldb trunk version) * Attach to process + works internally, but there's no way to access it in the UI currently. relavent lldb settings: disassembly-format thread-format frame-format diff --git a/debuggers/lldb/controllers/variable.cpp b/debuggers/lldb/controllers/variable.cpp index 260d00a9c0..ff6c888444 100644 --- a/debuggers/lldb/controllers/variable.cpp +++ b/debuggers/lldb/controllers/variable.cpp @@ -1,91 +1,119 @@ /* * LLDB-specific variable * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "variable.h" #include "debuglog.h" #include "debugsession.h" #include "mi/micommand.h" #include "stringhelpers.h" #include using namespace KDevelop; using namespace KDevMI::LLDB; using namespace KDevMI::MI; LldbVariable::LldbVariable(DebugSession *session, TreeModel *model, TreeItem *parent, const QString& expression, const QString& display) : MIVariable(session, model, parent, expression, display) { } +void LldbVariable::refetch() +{ + if (!topLevel() || varobj_.isEmpty()) { + return; + } + + if (!sessionIsAlive()) { + return; + } + + + // update the value itself + QPointer guarded_this(this); + debugSession->addCommand(VarEvaluateExpression, varobj_, [guarded_this](const ResultRecord &r){ + if (guarded_this && r.reason == "done" && r.hasField("value")) { + guarded_this->setValue(r["value"].literal()); + } + }); + + // update children + // remove all children fisrt, this will cause some gliches in the UI, but there's no good way + // that we can know if there's anything changed + if (isExpanded()) { + deleteChildren(); + fetchMoreChildren(); + } +} + void LldbVariable::handleRawUpdate(const ResultRecord& r) { qCDebug(DEBUGGERLLDB) << "handleRawUpdate for variable" << varobj(); const Value& changelist = r["changelist"]; Q_ASSERT_X(changelist.size() <= 1, "LldbVariable::handleRawUpdate", "should only be used with one variable VarUpdate"); if (changelist.size() == 1) handleUpdate(changelist[0]); } void LldbVariable::formatChanged() { if(childCount()) { foreach(TreeItem* item, childItems) { Q_ASSERT(dynamic_cast(item)); if( MIVariable* var=dynamic_cast(item)) var->setFormat(format()); } } else { if (sessionIsAlive()) { QPointer guarded_this(this); debugSession->addCommand( VarSetFormat, QString(" %1 %2 ").arg(varobj_).arg(format2str(format())), [guarded_this](const ResultRecord &r){ if(guarded_this && r.hasField("changelist")) { if (r["changelist"].size() > 0) { guarded_this->handleRawUpdate(r); } } }); } } } QString LldbVariable::formatValue(const QString& value) const { // Data formatter emits value with unicode escape sequence for string and char, // translate them back. // Only check with first char is enough, as unquote will do the rest check if (value.startsWith('"')) { return Utils::quote(Utils::unquote(value, true)); } else if (value.startsWith('\'')) { return Utils::quote(Utils::unquote(value, true, '\''), '\''); } return value; } diff --git a/debuggers/lldb/controllers/variable.h b/debuggers/lldb/controllers/variable.h index a52dd02b01..ba89ff08ad 100644 --- a/debuggers/lldb/controllers/variable.h +++ b/debuggers/lldb/controllers/variable.h @@ -1,57 +1,61 @@ /* * LLDB-specific variable * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef LLDB_VARIABLE_H #define LLDB_VARIABLE_H #include "mivariable.h" namespace KDevelop { class TreeModel; class TreeItem; } namespace KDevMI { namespace LLDB { class DebugSession; class LldbVariable : public MIVariable { Q_OBJECT public: LldbVariable(DebugSession *session, KDevelop::TreeModel* model, KDevelop::TreeItem* parent, const QString& expression, const QString& display = ""); void handleRawUpdate(const MI::ResultRecord &r); + void refetch(); + + using KDevelop::Variable::topLevel; + // For testing using KDevelop::Variable::childCount; using KDevelop::Variable::child; protected: void formatChanged() override; QString formatValue(const QString &value) const override; }; } // end of namespace LLDB } // end of namespace KDevMI #endif // LLDB_VARIABLE_H diff --git a/debuggers/lldb/debugsession.cpp b/debuggers/lldb/debugsession.cpp index 1dafd77361..909cf08a51 100644 --- a/debuggers/lldb/debugsession.cpp +++ b/debuggers/lldb/debugsession.cpp @@ -1,359 +1,372 @@ /* * LLDB Debugger Support * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "debugsession.h" #include "controllers/variable.h" #include "dbgglobal.h" #include "debuggerplugin.h" #include "debuglog.h" #include "lldbcommand.h" #include "mi/micommand.h" #include "stty.h" #include "stringhelpers.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevMI::LLDB; using namespace KDevMI::MI; using namespace KDevelop; struct ExecRunHandler : public MICommandHandler { ExecRunHandler(DebugSession *session, int maxRetry = 5) : m_session(session) , m_remainRetry(maxRetry) , m_activeCommands(1) { } void handle(const ResultRecord& r) override { --m_activeCommands; if (r.reason == QLatin1String("error")) { if (r.hasField("msg") && r["msg"].literal().contains("Invalid process during debug session")) { // for some unknown reason, lldb-mi sometimes fails to start process if (m_remainRetry && m_session) { qCDebug(DEBUGGERLLDB) << "Retry starting"; --m_remainRetry; // resend the command again. ++m_activeCommands; m_session->addCommand(ExecRun, QStringLiteral(""), this, // use *this as handler, so we can track error times CmdMaybeStartsRunning | CmdHandlesError); return; } } qCDebug(DEBUGGERLLDB) << "Failed to start inferior:" << "exceeded retry times or session become invalid"; m_session->stopDebugger(); } if (m_activeCommands == 0) delete this; } bool handlesError() override { return true; } bool autoDelete() override { return false; } QPointer m_session; int m_remainRetry; int m_activeCommands; }; DebugSession::DebugSession(LldbDebuggerPlugin *plugin) : MIDebugSession(plugin) , m_breakpointController(nullptr) , m_variableController(nullptr) , m_frameStackModel(nullptr) { m_breakpointController = new BreakpointController(this); m_variableController = new VariableController(this); m_frameStackModel = new LldbFrameStackModel(this); if (m_plugin) m_plugin->setupToolviews(); connect(this, &DebugSession::stateChanged, this, &DebugSession::handleSessionStateChange); } DebugSession::~DebugSession() { if (m_plugin) m_plugin->unloadToolviews(); } BreakpointController *DebugSession::breakpointController() const { return m_breakpointController; } VariableController *DebugSession::variableController() const { return m_variableController; } LldbFrameStackModel *DebugSession::frameStackModel() const { return m_frameStackModel; } LldbDebugger *DebugSession::createDebugger() const { return new LldbDebugger; } MICommand *DebugSession::createCommand(MI::CommandType type, const QString& arguments, MI::CommandFlags flags) const { return new LldbCommand(type, arguments, flags); } void DebugSession::setFormatterPath(const QString &path) { m_formatterPath = path; } void DebugSession::initializeDebugger() { //addCommand(MI::EnableTimings, "yes"); // load data formatter auto formatterPath = m_formatterPath; if (!QFileInfo(formatterPath).isFile()) { formatterPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevlldb/formatters/all.py"); } if (!formatterPath.isEmpty()) { addCommand(MI::NonMI, "command script import " + KShell::quoteArg(formatterPath)); } // set a larger term width. // TODO: set term-width to exact max column count in console view addCommand(MI::NonMI, "settings set term-width 1024"); qCDebug(DEBUGGERLLDB) << "Initialized LLDB"; } void DebugSession::configure(ILaunchConfiguration *cfg, IExecutePlugin *iexec) { // Read Configuration values KConfigGroup grp = cfg->config(); // Set the environment variables has effect only after target created const EnvironmentGroupList l(KSharedConfig::openConfig()); QString envgrp = iexec->environmentGroup(cfg); if (envgrp.isEmpty()) { qCWarning(DEBUGGERCOMMON) << i18n("No environment group specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment group.", cfg->name()); envgrp = l.defaultGroup(); } QStringList vars; for (auto it = l.variables(envgrp).constBegin(), ite = l.variables(envgrp).constEnd(); it != ite; ++it) { vars.append(QStringLiteral("%0=%1").arg(it.key(), Utils::quote(it.value()))); } // actually using lldb command 'settings set target.env-vars' which accepts multiple values addCommand(GdbSet, "environment " + vars.join(" ")); // Break on start: can't use "-exec-run --start" because in lldb-mi // the inferior stops without any notification bool breakOnStart = grp.readEntry(KDevMI::Config::BreakOnStartEntry, false); if (breakOnStart) { BreakpointModel* m = ICore::self()->debugController()->breakpointModel(); bool found = false; foreach (Breakpoint *b, m->breakpoints()) { if (b->location() == "main") { found = true; break; } } if (!found) { m->addCodeBreakpoint("main"); } } // Needed so that breakpoint widget has a chance to insert breakpoints. // FIXME: a bit hacky, as we're really not ready for new commands. setDebuggerStateOn(s_dbgBusy); raiseEvent(debugger_ready); qCDebug(DEBUGGERGDB) << "Per inferior configuration done"; } bool DebugSession::execInferior(ILaunchConfiguration *cfg, IExecutePlugin *iexec, const QString &executable) { qCDebug(DEBUGGERGDB) << "Executing inferior"; KConfigGroup grp = cfg->config(); // Create target as early as possible, so we can do target specific configuration later QString filesymbols = Utils::quote(executable); bool remoteDebugging = grp.readEntry(Config::LldbRemoteDebuggingEntry, false); if (remoteDebugging) { auto connStr = grp.readEntry(Config::LldbRemoteServerEntry, QString()); auto remoteDir = grp.readEntry(Config::LldbRemotePathEntry, QString()); auto remoteExe = QDir(remoteDir).filePath(QFileInfo(executable).fileName()); filesymbols += " -r " + Utils::quote(remoteExe); addCommand(MI::FileExecAndSymbols, filesymbols, this, &DebugSession::handleFileExecAndSymbols, CmdHandlesError); addCommand(MI::TargetSelect, "remote " + connStr, this, &DebugSession::handleTargetSelect, CmdHandlesError); // ensure executable is on remote end addCommand(MI::NonMI, QStringLiteral("platform mkdir -v 755 %0").arg(Utils::quote(remoteDir))); addCommand(MI::NonMI, QStringLiteral("platform put-file %0 %1") .arg(Utils::quote(executable), Utils::quote(remoteExe))); } else { addCommand(MI::FileExecAndSymbols, filesymbols, this, &DebugSession::handleFileExecAndSymbols, CmdHandlesError); } raiseEvent(connected_to_program); // Do other per target config configure(cfg, iexec); // Start inferior QUrl configLldbScript = grp.readEntry(Config::LldbConfigScriptEntry, QUrl()); addCommand(new SentinelCommand([this, remoteDebugging, configLldbScript]() { // setup inferior I/O redirection if (!remoteDebugging) { // FIXME: a hacky way to emulate tty setting on linux. Not sure if this provides all needed // functionalities of a pty. Should make this conditional on other platforms. // no need to quote, settings set takes 'raw' input addCommand(MI::NonMI, QStringLiteral("settings set target.input-path %0").arg(m_tty->getSlave())); addCommand(MI::NonMI, QStringLiteral("settings set target.output-path %0").arg(m_tty->getSlave())); addCommand(MI::NonMI, QStringLiteral("settings set target.error-path %0").arg(m_tty->getSlave())); } else { // what is the expected behavior for using external terminal when doing remote debugging? } // send breakpoints already in our breakpoint model to lldb auto bc = breakpointController(); bc->initSendBreakpoints(); qCDebug(DEBUGGERLLDB) << "Turn on delete duplicate mode"; // turn on delete duplicate breakpoints model, so that breakpoints created by user command in // the script and returned as a =breakpoint-created notification won't get duplicated with the // one already in our model. // we will turn this model off once we first reach a paused state, and from that time on, // the user can create duplicated breakpoints using normal command. bc->setDeleteDuplicateBreakpoints(true); // run custom config script right before we starting the inferior, // so the user has the freedom to change everything. if (configLldbScript.isValid()) { addCommand(MI::NonMI, "command source -s 0 " + KShell::quoteArg(configLldbScript.toLocalFile())); } addCommand(MI::ExecRun, QString(), new ExecRunHandler(this), CmdMaybeStartsRunning | CmdHandlesError); }, CmdMaybeStartsRunning)); return true; } void DebugSession::interruptDebugger() { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; addCommand(ExecInterrupt, QString(), CmdInterrupt); return; } void DebugSession::ensureDebuggerListening() { // lldb always uses async mode and prompt is always available. // no need to interrupt setDebuggerStateOff(s_dbgNotListening); // NOTE: there is actually a bug in lldb-mi that, when receiving SIGINT, // it would print '^done', which doesn't coresponds to any previous command. // This confuses our command queue. } void DebugSession::handleFileExecAndSymbols(const MI::ResultRecord& r) { if (r.reason == "error") { KMessageBox::error( qApp->activeWindow(), i18n("Could not start debugger:
")+ r["msg"].literal(), i18n("Startup error")); stopDebugger(); } } void DebugSession::handleTargetSelect(const MI::ResultRecord& r) { if (r.reason == "error") { KMessageBox::error(qApp->activeWindow(), i18n("Error connecting to remote target:
")+ r["msg"].literal(), i18n("Startup error")); stopDebugger(); } } void DebugSession::updateAllVariables() { + // FIXME: this is only a workaround for lldb-mi doesn't provide -var-update changelist + // for variables that have a python synthetic provider. Remove this after this is fixed + // in the upstream. + + // re-fetch all toplevel variables, as -var-update doesn't work with data formatter + // we have to pick out top level variables first, as refetching will delete child + // variables. + QList toplevels; for (auto it = m_allVariables.begin(), ite = m_allVariables.end(); it != ite; ++it) { LldbVariable *var = qobject_cast(it.value()); - addCommand(VarUpdate, "--all-values " + it.key(), - var, &LldbVariable::handleRawUpdate); + if (var->topLevel()) { + toplevels << var; + } + } + + for (auto var : toplevels) { + var->refetch(); } } void DebugSession::handleSessionStateChange(IDebugSession::DebuggerState state) { if (state == IDebugSession::PausedState) { // session is paused, the user can input any commands now. // Turn off delete duplicate breakpoints mode, as the user // may intentionaly want to do this. qCDebug(DEBUGGERLLDB) << "Turn off delete duplicate mode"; breakpointController()->setDeleteDuplicateBreakpoints(false); } }