diff --git a/debuggers/CMakeLists.txt b/debuggers/CMakeLists.txt --- a/debuggers/CMakeLists.txt +++ b/debuggers/CMakeLists.txt @@ -1,7 +1,25 @@ include_directories(common) +function(add_debuggable_executable target) + cmake_parse_arguments(add_debuggable_executable "" "" "SRCS" ${ARGN}) + add_executable(${target} ${add_debuggable_executable_SRCS}) + # force debug symbols for our debuggees, disable optimizations + if (WIN32) + set(_flags "/0d") + else() + set(_flags "-O0") + endif() + set_target_properties(${target} PROPERTIES COMPILE_FLAGS "${CMAKE_CXX_FLAGS_DEBUG} ${_flags}") +endfunction() + +if (CMAKE_VERSION VERSION_GREATER "2.9" OR NOT CMAKE_GENERATOR MATCHES "Ninja") + set(HAVE_PATH_WITH_SPACES_TEST TRUE) +else() + message(WARNING "Disabling 'path with spaces' test, this CMake version would create a faulty build.ninja file. Upgrade to at least CMake v3.0") +endif() + +add_subdirectory(common) +add_subdirectory(lldb) if(NOT WIN32) - add_subdirectory(common) add_subdirectory(gdb) endif() - diff --git a/debuggers/common/CMakeLists.txt b/debuggers/common/CMakeLists.txt --- a/debuggers/common/CMakeLists.txt +++ b/debuggers/common/CMakeLists.txt @@ -18,16 +18,17 @@ mivariable.cpp stringhelpers.cpp stty.cpp + # tool views + widgets/debuggerconsoleview.cpp + widgets/disassemblewidget.cpp # register registers/registersview.cpp registers/registercontroller.cpp registers/registersmanager.cpp registers/registercontroller_x86.cpp registers/registercontroller_arm.cpp registers/modelsmanager.cpp registers/converters.cpp - # disassemble widget - widgets/disassemblewidget.cpp ) if(KF5SysGuard_FOUND) list(APPEND debuggercommon_SRCS @@ -37,8 +38,9 @@ ki18n_wrap_ui(debuggercommon_SRCS dialogs/selectcoredialog.ui - registers/registersview.ui + widgets/debuggerconsoleview.ui widgets/selectaddressdialog.ui + registers/registersview.ui ) # Use old behavior (ignore the visibility properties for static libraries, object @@ -59,6 +61,7 @@ PRIVATE Qt5::Core Qt5::Gui + Qt5::Widgets KDev::Util KDev::Language ) diff --git a/debuggers/common/dbgglobal.h b/debuggers/common/dbgglobal.h --- a/debuggers/common/dbgglobal.h +++ b/debuggers/common/dbgglobal.h @@ -51,16 +51,35 @@ typeName }; // FIXME: find a more appropriate place for these strings. Possibly a place specific to debugger backend -static const char gdbPathEntry[] = "GDB Path"; -static const char debuggerShellEntry[] = "Debugger Shell"; -static const char remoteGdbConfigEntry[] = "Remote GDB Config Script"; -static const char remoteGdbShellEntry[] = "Remote GDB Shell Script"; -static const char remoteGdbRunEntry[] = "Remote GDB Run Script"; -static const char staticMembersEntry[] = "Display Static Members"; -static const char demangleNamesEntry[] = "Display Demangle Names"; -static const char allowForcedBPEntry[] = "Allow Forced Breakpoint Set"; -static const char startWithEntry[] = "Start With"; -static const char breakOnStartEntry[] = "Break on Start"; +namespace Config { +static const char StartWithEntry[] = "Start With"; +// FIXME: break on start isn't exposed in the UI for GDB +static const char BreakOnStartEntry[] = "Break on Start"; +} + +namespace GDB { namespace Config { +static const char GdbPathEntry[] = "GDB Path"; +static const char DebuggerShellEntry[] = "Debugger Shell"; +static const char RemoteGdbConfigEntry[] = "Remote GDB Config Script"; +static const char RemoteGdbShellEntry[] = "Remote GDB Shell Script"; +static const char RemoteGdbRunEntry[] = "Remote GDB Run Script"; +static const char StaticMembersEntry[] = "Display Static Members"; +static const char DemangleNamesEntry[] = "Display Demangle Names"; +static const char AllowForcedBPEntry[] = "Allow Forced Breakpoint Set"; +} +} + +namespace LLDB { namespace Config { +static const char LldbExecutableEntry[] = "LLDB Executable"; +static const char LldbArgumentsEntry[] = "LLDB Arguments"; +static const char LldbEnvironmentEntry[] = "LLDB Environment"; +static const char LldbInheritSystemEnvEntry[] = "LLDB Inherit System Env"; +static const char LldbConfigScriptEntry[] = "LLDB Config Script"; +static const char LldbRemoteDebuggingEntry[] = "LLDB Remote Debugging"; +static const char LldbRemoteServerEntry[] = "LLDB Remote Server"; +static const char LldbRemotePathEntry[] = "LLDB Remote Path"; +} +} } // end of namespace KDevMI diff --git a/debuggers/common/mi/micommand.cpp b/debuggers/common/mi/micommand.cpp --- a/debuggers/common/mi/micommand.cpp +++ b/debuggers/common/mi/micommand.cpp @@ -252,16 +252,16 @@ case ExecNextInstruction: command = "exec-next-instruction"; break; - case ExecReturn: + case ExecReturn: // FIXME: non-exist command command = "exec-command ="; break; case ExecRun: command = "exec-run"; break; - case ExecShowArguments: + case ExecShowArguments: // FIXME: non-exist command command = "exec-show-arguments"; break; - case ExecSignal: + case ExecSignal: // FIXME: non-exist command command = "exec-signal"; break; case ExecStep: @@ -274,35 +274,35 @@ command = "exec-until"; break; - case FileClear: + case FileClear: // FIXME: non-exist command command = "file-clear"; break; case FileExecAndSymbols: command = "file-exec-and-symbols";//"file" break; case FileExecFile: command = "file-exec-file";//"exec-file" break; - case FileListExecSections: + case FileListExecSections: // FIXME: non-exist command command = "file-list-exec-sections"; break; case FileListExecSourceFile: command = "file-list-exec-source-file"; break; case FileListExecSourceFiles: command = "file-list-exec-source-files"; break; - case FileListSharedLibraries: + case FileListSharedLibraries: // FIXME: non-exist command command = "file-list-shared-libraries"; break; - case FileListSymbolFiles: + case FileListSymbolFiles: // FIXME: non-exist command command = "file-list-symbol-files"; break; case FileSymbolFile: command = "file-symbol-file";//"symbol-file" break; - case GdbComplete: + case GdbComplete: // FIXME: non-exist command command = "gdb-complete"; break; case GdbExit: @@ -314,7 +314,7 @@ case GdbShow: command = "gdb-show";//"show" break; - case GdbSource: + case GdbSource: // FIXME: non-exist command command = "gdb-source"; break; case GdbVersion: @@ -336,36 +336,36 @@ command = "list-features"; break; - case OverlayAuto: + case OverlayAuto: // FIXME: non-exist command command = "overlay-auto"; break; - case OverlayListMappingState: + case OverlayListMappingState: // FIXME: non-exist command command = "overlay-list-mapping-state"; break; - case OverlayListOverlays: + case OverlayListOverlays: // FIXME: non-exist command command = "overlay-list-overlays"; break; - case OverlayMap: + case OverlayMap: // FIXME: non-exist command command = "overlay-map"; break; - case OverlayOff: + case OverlayOff: // FIXME: non-exist command command = "overlay-off"; break; - case OverlayOn: + case OverlayOn: // FIXME: non-exist command command = "overlay-on"; break; - case OverlayUnmap: + case OverlayUnmap: // FIXME: non-exist command command = "overlay-unmap"; break; case SignalHandle: return "handle"; //command = "signal-handle"; break; - case SignalListHandleActions: + case SignalListHandleActions: // FIXME: non-exist command command = "signal-list-handle-actions"; break; - case SignalListSignalTypes: + case SignalListSignalTypes: // FIXME: non-exist command command = "signal-list-signal-types"; break; @@ -378,7 +378,7 @@ case StackListArguments: command = "stack-list-arguments"; break; - case StackListExceptionHandlers: + case StackListExceptionHandlers: // FIXME: non-exist command command = "stack-list-exception-handlers"; break; case StackListFrames: @@ -391,44 +391,44 @@ command = "stack-select-frame"; break; - case SymbolInfoAddress: + case SymbolInfoAddress: // FIXME: non-exist command command = "symbol-info-address"; break; - case SymbolInfoFile: + case SymbolInfoFile: // FIXME: non-exist command command = "symbol-info-file"; break; - case SymbolInfoFunction: + case SymbolInfoFunction: // FIXME: non-exist command command = "symbol-info-function"; break; - case SymbolInfoLine: + case SymbolInfoLine: // FIXME: non-exist command command = "symbol-info-line"; break; - case SymbolInfoSymbol: + case SymbolInfoSymbol: // FIXME: non-exist command command = "symbol-info-symbol"; break; - case SymbolListFunctions: + case SymbolListFunctions: // FIXME: non-exist command command = "symbol-list-functions"; break; case SymbolListLines: command = "symbol-list-lines"; break; - case SymbolListTypes: + case SymbolListTypes: // FIXME: non-exist command command = "symbol-list-types"; break; - case SymbolListVariables: + case SymbolListVariables: // FIXME: non-exist command command = "symbol-list-variables"; break; - case SymbolLocate: + case SymbolLocate: // FIXME: non-exist command command = "symbol-locate"; break; - case SymbolType: + case SymbolType: // FIXME: non-exist command command = "symbol-type"; break; case TargetAttach: command = "target-attach"; break; - case TargetCompareSections: + case TargetCompareSections: // FIXME: non-exist command command = "target-compare-sections"; break; case TargetDetach: @@ -440,16 +440,16 @@ case TargetDownload: command = "target-download"; break; - case TargetExecStatus: + case TargetExecStatus: // FIXME: non-exist command command = "target-exec-status"; break; - case TargetListAvailableTargets: + case TargetListAvailableTargets: // FIXME: non-exist command command = "target-list-available-targets"; break; - case TargetListCurrentTargets: + case TargetListCurrentTargets: // FIXME: non-exist command command = "target-list-current-targets"; break; - case TargetListParameters: + case TargetListParameters: // FIXME: non-exist command command = "target-list-parameters"; break; case TargetSelect: @@ -459,7 +459,7 @@ case ThreadInfo: command = "thread-info"; break; - case ThreadListAllThreads: + case ThreadListAllThreads: // FIXME: non-exist command command = "thread-list-all-threads"; break; case ThreadListIds: @@ -469,43 +469,43 @@ command = "thread-select"; break; - case TraceActions: + case TraceActions: // FIXME: non-exist command command = "trace-actions"; break; - case TraceDelete: + case TraceDelete: // FIXME: non-exist command command = "trace-delete"; break; - case TraceDisable: + case TraceDisable: // FIXME: non-exist command command = "trace-disable"; break; - case TraceDump: + case TraceDump: // FIXME: non-exist command command = "trace-dump"; break; - case TraceEnable: + case TraceEnable: // FIXME: non-exist command command = "trace-enable"; break; - case TraceExists: + case TraceExists: // FIXME: non-exist command command = "trace-exists"; break; case TraceFind: command = "trace-find"; break; - case TraceFrameNumber: + case TraceFrameNumber: // FIXME: non-exist command command = "trace-frame-number"; break; - case TraceInfo: + case TraceInfo: // FIXME: non-exist command command = "trace-info"; break; - case TraceInsert: + case TraceInsert: // FIXME: non-exist command command = "trace-insert"; break; - case TraceList: + case TraceList: // FIXME: non-exist command command = "trace-list"; break; - case TracePassCount: + case TracePassCount: // FIXME: non-exist command command = "trace-pass-count"; break; - case TraceSave: + case TraceSave: // FIXME: crashes gdb command = "trace-save"; break; case TraceStart: @@ -530,7 +530,7 @@ case VarInfoPathExpression: command = "var-info-path-expression"; break; - case VarInfoExpression: + case VarInfoExpression: // FIXME: non-exist command command = "var-info-expression"; break; case VarInfoNumChildren: diff --git a/debuggers/common/mi/miparser.cpp b/debuggers/common/mi/miparser.cpp --- a/debuggers/common/mi/miparser.cpp +++ b/debuggers/common/mi/miparser.cpp @@ -356,7 +356,10 @@ { translated = '\t'; } - + else if (message[i+1] == 'r') + { + translated = '\r'; + } } } diff --git a/debuggers/common/midebugger.cpp b/debuggers/common/midebugger.cpp --- a/debuggers/common/midebugger.cpp +++ b/debuggers/common/midebugger.cpp @@ -174,7 +174,13 @@ case MI::Record::Result: { MI::ResultRecord& result = static_cast(*r); - emit internalCommandOutput(QString::fromUtf8(line) + '\n'); + // it's still possible for the user to issue a MI command, + // emit correct signal + if (currentCmd_ && currentCmd_->isUserCommand()) { + emit userCommandOutput(QString::fromUtf8(line) + '\n'); + } else { + emit internalCommandOutput(QString::fromUtf8(line) + '\n'); + } // GDB doc: "running" and "exit" are status codes equivalent to "done" if (result.reason == "done" || result.reason == "running" || result.reason == "exit") @@ -303,21 +309,19 @@ #endif } -// ************************************************************************** - void MIDebugger::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { - qCDebug(DEBUGGERCOMMON) << "GDB FINISHED\n"; + qCDebug(DEBUGGERCOMMON) << "Debugger FINISHED\n"; bool abnormal = exitCode != 0 || exitStatus != QProcess::NormalExit; - emit userCommandOutput("(gdb) Process exited\n"); + emit userCommandOutput("Process exited\n"); emit exited(abnormal, i18n("Process exited")); } void MIDebugger::processErrored(QProcess::ProcessError error) { - qCDebug(DEBUGGERCOMMON) << "GDB ERRORED" << error; - if( error == QProcess::FailedToStart ) + qCDebug(DEBUGGERCOMMON) << "Debugger ERRORED" << error; + if(error == QProcess::FailedToStart) { KMessageBox::information( qApp->activeWindow(), @@ -327,18 +331,20 @@ debuggerBinary_), i18n("Could not start debugger")); - emit userCommandOutput("(gdb) didn't start\n"); - emit exited(true, i18n("Process didn't start'")); + emit userCommandOutput("Process failed to start\n"); + emit exited(true, i18n("Process failed to start'")); } else if (error == QProcess::Crashed) { KMessageBox::error( qApp->activeWindow(), - i18n("Gdb crashed." - "

Because of that the debug session has to be ended.
" - "Try to reproduce the crash with plain gdb and report a bug.
"), - i18n("Gdb crashed")); + i18n("Debugger crashed." + "

The debugger process '%1' crashed.
" + "Because of that the debug session has to be ended.
" + "Try to reproduce the crash without KDevelop and report a bug.
", + debuggerBinary_), + i18n("Debugger crashed")); - emit userCommandOutput("(gdb) Process crashed\n"); + emit userCommandOutput("Process crashed\n"); emit exited(true, i18n("Process crashed")); } } diff --git a/debuggers/common/midebuggerplugin.h b/debuggers/common/midebuggerplugin.h --- a/debuggers/common/midebuggerplugin.h +++ b/debuggers/common/midebuggerplugin.h @@ -58,7 +58,14 @@ void unload() override; KDevelop::ContextMenuExtension contextMenuExtension( KDevelop::Context* ) override; - virtual MIDebugSession *createSession() const = 0; + virtual MIDebugSession *createSession() = 0; + + virtual void setupToolviews() = 0; + /** + * The implementation should be sure it's safe to call + * even when tool views are already unloaded. + */ + virtual void unloadToolviews() = 0; //BEGIN IStatus public: @@ -97,7 +104,6 @@ void slotCloseDrKonqi(); protected: - void setupToolviews(); void setupActions(); void setupDBus(); @@ -139,14 +145,6 @@ QObject::connect(view->widget(), SIGNAL(requestRaise()), view, SLOT(requestRaise())); } - /* At present, some debugger widgets (e.g. breakpoint) contain actions so that shortcuts - work, but they don't need any toolbar. So, suppress toolbar action. */ - QList toolBarActions(QWidget* viewWidget) const override - { - Q_UNUSED(viewWidget); - return QList(); - } - private: Plugin * m_plugin; QString m_id; diff --git a/debuggers/common/midebuggerplugin.cpp b/debuggers/common/midebuggerplugin.cpp --- a/debuggers/common/midebuggerplugin.cpp +++ b/debuggers/common/midebuggerplugin.cpp @@ -63,41 +63,10 @@ core()->debugController()->initializeUi(); - setupToolviews(); setupActions(); setupDBus(); } -void MIDebuggerPlugin::setupToolviews() -{ - // TODO: port tool views - /* - disassemblefactory = new DebuggerToolFactory( - this, "org.kdevelop.debugger.DisassemblerView", Qt::BottomDockWidgetArea); - - lldbfactory = new DebuggerToolFactory( - this, "org.kdevelop.debugger.ConsoleView",Qt::BottomDockWidgetArea); - - core()->uiController()->addToolView( - i18n("Disassemble/Registers"), - disassemblefactory); - - core()->uiController()->addToolView( - i18n("LLDB"), - lldbfactory); - -#ifndef WITH_OKTETA - memoryviewerfactory = nullptr; -#else - memoryviewerfactory = new DebuggerToolFactory( - this, "org.kdevelop.debugger.MemoryView", Qt::BottomDockWidgetArea); - core()->uiController()->addToolView( - i18n("Memory"), - memoryviewerfactory); -#endif -*/ -} - void MIDebuggerPlugin::setupActions() { KActionCollection* ac = actionCollection(); @@ -147,12 +116,7 @@ void MIDebuggerPlugin::unload() { - // TODO: port tool views - /* - core()->uiController()->removeToolView(disassemblefactory); - core()->uiController()->removeToolView(lldbfactory); - core()->uiController()->removeToolView(memoryviewerfactory); - */ + unloadToolviews(); } MIDebuggerPlugin::~MIDebuggerPlugin() diff --git a/debuggers/common/midebugjobs.cpp b/debuggers/common/midebugjobs.cpp --- a/debuggers/common/midebugjobs.cpp +++ b/debuggers/common/midebugjobs.cpp @@ -57,8 +57,8 @@ setCapabilities(Killable); m_session = p->createSession(); - connect(m_session, &MIDebugSession::inferiorStdoutLines, this, &MIDebugJob::stderrReceived); - connect(m_session, &MIDebugSession::inferiorStderrLines, this, &MIDebugJob::stdoutReceived); + connect(m_session, &MIDebugSession::inferiorStdoutLines, this, &MIDebugJob::stdoutReceived); + connect(m_session, &MIDebugSession::inferiorStderrLines, this, &MIDebugJob::stderrReceived); connect(m_session, &MIDebugSession::debuggerInternalCommandOutput, this, [this](const QString &output){ this->stdoutReceived(output.split(QRegularExpression("[\r\n]"), QString::SkipEmptyParts)); @@ -113,13 +113,11 @@ setTitle(m_launchcfg->name()); KConfigGroup grp = m_launchcfg->config(); - QString startWith = grp.readEntry(startWithEntry, QString("ApplicationOutput")); - if (startWith == "GdbConsole") { - setVerbosity(Silent); - } else if (startWith == "FrameStack") { - setVerbosity(Silent); - } else { + QString startWith = grp.readEntry(Config::StartWithEntry, QString("ApplicationOutput")); + if (startWith == "ApplicationOutput") { setVerbosity(Verbose); + } else { + setVerbosity(Silent); } startOutput(); diff --git a/debuggers/common/midebugsession.h b/debuggers/common/midebugsession.h --- a/debuggers/common/midebugsession.h +++ b/debuggers/common/midebugsession.h @@ -52,13 +52,14 @@ } class MIDebugger; +class MIDebuggerPlugin; class MIVariable; class STTY; class MIDebugSession : public KDevelop::IDebugSession { Q_OBJECT public: - MIDebugSession(); + explicit MIDebugSession(MIDebuggerPlugin *plugin = nullptr); ~MIDebugSession() override; Q_SIGNALS: @@ -258,9 +259,10 @@ busy with previous command, and there's a command in the queue, sends it. */ void executeCmd(); - void ensureDebuggerListening(); void destroyCmds(); + virtual void ensureDebuggerListening(); + /** * Start the debugger instance */ @@ -279,7 +281,8 @@ /** * Further config the debugger and start the inferior program (either local or remote). */ - virtual bool execInferior(KDevelop::ILaunchConfiguration *cfg, const QString &executable) = 0; + virtual bool execInferior(KDevelop::ILaunchConfiguration *cfg, IExecutePlugin *iexec, + const QString &executable) = 0; /** * Manipulate debugger instance state @@ -342,6 +345,8 @@ // Map from GDB varobj name to MIVariable. QMap m_allVariables; + + MIDebuggerPlugin *m_plugin; }; template diff --git a/debuggers/common/midebugsession.cpp b/debuggers/common/midebugsession.cpp --- a/debuggers/common/midebugsession.cpp +++ b/debuggers/common/midebugsession.cpp @@ -44,7 +44,6 @@ #include #include #include -#include #include #include @@ -64,7 +63,7 @@ using namespace KDevMI; using namespace KDevMI::MI; -MIDebugSession::MIDebugSession() +MIDebugSession::MIDebugSession(MIDebuggerPlugin *plugin) : m_procLineMaker(new ProcessLineMaker(this)) , m_commandQueue(new CommandQueue) , m_sessionState(NotStartedState) @@ -75,6 +74,7 @@ , m_tty(nullptr) , m_hasCrashed(false) , m_sourceInitFile(true) + , m_plugin(plugin) { // setup signals connect(m_procLineMaker, &ProcessLineMaker::receivedStdoutLines, @@ -159,7 +159,15 @@ // output signals connect(m_debugger, &MIDebugger::applicationOutput, this, [this](const QString &output) { - emit inferiorStdoutLines(output.split(QRegularExpression("[\r\n]"), QString::SkipEmptyParts)); + auto lines = output.split(QRegularExpression("[\r\n]"), QString::SkipEmptyParts); + for (auto &line : lines) { + int p = line.length(); + while (p >= 1 && (line[p-1] == '\r' || line[p-1] == '\n')) + p--; + if (p != line.length()) + line.remove(p, line.length() - p); + } + emit inferiorStdoutLines(lines); }); connect(m_debugger, &MIDebugger::userCommandOutput, this, &MIDebugSession::debuggerUserCommandOutput); connect(m_debugger, &MIDebugger::internalCommandOutput, this, &MIDebugSession::debuggerInternalCommandOutput); @@ -259,29 +267,16 @@ } addCommand(EnvironmentCd, '"' + dir + '"'); - // Set the environment variables - 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(); - } - for (const auto &envvar : l.createEnvironment(envgrp, {})) { - addCommand(GdbSet, "environment " + envvar); - } - // Set the run arguments if (!arguments.isEmpty()) addCommand(ExecArguments, KShell::joinArgs(arguments)); // Do other debugger specific config options and actually start the inferior program - if (!execInferior(cfg, executable)) { + if (!execInferior(cfg, iexec, executable)) { return false; } - QString config_startWith = cfg->config().readEntry(startWithEntry, QStringLiteral("ApplicationOutput")); + QString config_startWith = cfg->config().readEntry(Config::StartWithEntry, QStringLiteral("ApplicationOutput")); if (config_startWith == "GdbConsole") { emit raiseDebuggerConsoleViews(); } else if (config_startWith == "FrameStack") { @@ -312,14 +307,6 @@ //set current state to running, after attaching we will get *stopped response setDebuggerStateOn(s_appRunning); - // Currently, we always start debugger with a name of binary, - // we might be connecting to a different binary completely, - // so cancel all symbol tables gdb has. - // We can't omit application name from gdb invocation - // because for libtool binaries, we have no way to guess - // real binary name. - addCommand(MI::FileExecAndSymbols); - addCommand(TargetAttach, QString::number(pid), this, &MIDebugSession::handleTargetAttach, CmdHandlesError); @@ -555,6 +542,10 @@ void MIDebugSession::stopDebugger() { + if (debuggerStateIsOn(s_dbgNotStarted)) { + return; + } + m_commandQueue->clear(); qCDebug(DEBUGGERCOMMON) << "try stopping debugger"; @@ -727,7 +718,13 @@ void MIDebugSession::addUserCommand(const QString& cmd) { - queueCmd(new UserCommand(MI::NonMI, cmd)); + if (!cmd.isEmpty() && cmd[0].isDigit()) { + // Add a space to the begining, so debugger won't get confused if the + // command starts with a number (won't mix it up with command token added) + queueCmd(new UserCommand(MI::NonMI, " " + cmd)); + } else { + queueCmd(new UserCommand(MI::NonMI, cmd)); + } // User command can theoreticall modify absolutely everything, // so need to force a reload. diff --git a/debuggers/common/miframestackmodel.cpp b/debuggers/common/miframestackmodel.cpp --- a/debuggers/common/miframestackmodel.cpp +++ b/debuggers/common/miframestackmodel.cpp @@ -65,10 +65,6 @@ void MIFrameStackModel::fetchThreads() { - // TODO: preliminary test shows there might be a bug in lldb-mi - // that's causing std::logic_error when executing -thread-info with - // more than one threads. Find a workaround for this (and report bug - // if it truely is). session()->addCommand(ThreadInfo, "", this, &MIFrameStackModel::handleThreadInfo); } diff --git a/debuggers/common/mivariable.h b/debuggers/common/mivariable.h --- a/debuggers/common/mivariable.h +++ b/debuggers/common/mivariable.h @@ -53,12 +53,12 @@ bool canSetFormat() const override { return true; } -private: // Variable overrides +protected: // Variable overrides void attachMaybe(QObject *callback, const char *callbackMethod) override; void fetchMoreChildren() override; void formatChanged() override; -private: // Internal +protected: // Internal friend class ::CreateVarobjHandler; friend class ::FetchMoreChildrenHandler; diff --git a/debuggers/common/mivariable.cpp b/debuggers/common/mivariable.cpp --- a/debuggers/common/mivariable.cpp +++ b/debuggers/common/mivariable.cpp @@ -67,7 +67,7 @@ void MIVariable::setVarobj(const QString& v) { if (!debugSession) { - qCDebug(DEBUGGERCOMMON) << "WARNING: MIVariable::setVarobj called when its session died"; + qCWarning(DEBUGGERCOMMON) << "MIVariable::setVarobj called when its session died"; return; } if (!varobj_.isEmpty()) { @@ -355,7 +355,7 @@ { if (sessionIsAlive()) { debugSession->addCommand(VarSetFormat, - QString(" \"%1\" %2 ").arg(varobj_).arg(format2str(format())), + QString(" %1 %2 ").arg(varobj_).arg(format2str(format())), new SetFormatHandler(this)); } } diff --git a/debuggers/common/mivariablecontroller.h b/debuggers/common/mivariablecontroller.h --- a/debuggers/common/mivariablecontroller.h +++ b/debuggers/common/mivariablecontroller.h @@ -55,14 +55,16 @@ void addWatchpoint(KDevelop::Variable* variable) override; void update() override; +protected: + void updateLocals(); + private slots: void programStopped(const MI::AsyncRecord &r); void stateChanged(KDevelop::IDebugSession::DebuggerState); private: MIDebugSession* debugSession() const; - void updateLocals(); void handleVarUpdate(const MI::ResultRecord& r); void addWatch(const MI::ResultRecord& r); diff --git a/debuggers/common/mivariablecontroller.cpp b/debuggers/common/mivariablecontroller.cpp --- a/debuggers/common/mivariablecontroller.cpp +++ b/debuggers/common/mivariablecontroller.cpp @@ -75,7 +75,7 @@ void MIVariableController::update() { - qCDebug(DEBUGGERCOMMON) << autoUpdate(); + qCDebug(DEBUGGERCOMMON) << "autoUpdate =" << autoUpdate(); if (autoUpdate() & UpdateWatches) { variableCollection()->watches()->reinstall(); } @@ -118,16 +118,17 @@ { if (!KDevelop::ICore::self()->debugController()) return; //happens on shutdown - // FIXME: handle error. - const Value& locals = r["stack-args"][0]["args"]; - - for (int i = 0; i < locals.size(); i++) { - m_localsName << locals[i].literal(); - } - QList variables = KDevelop::ICore::self()->debugController()->variableCollection() - ->locals()->updateLocals(m_localsName); - foreach (Variable *v, variables) { - v->attachMaybe(); + if (r.hasField("stack-args") && r["stack-args"].size() > 0) { + const Value& locals = r["stack-args"][0]["args"]; + + for (int i = 0; i < locals.size(); i++) { + m_localsName << locals[i].literal(); + } + QList variables = KDevelop::ICore::self()->debugController()->variableCollection() + ->locals()->updateLocals(m_localsName); + foreach (Variable *v, variables) { + v->attachMaybe(); + } } } @@ -144,20 +145,20 @@ void handle(const ResultRecord &r) override { - // FIXME: handle error. - - const Value& locals = r["locals"]; - - QStringList localsName; - for (int i = 0; i < locals.size(); i++) { - const Value& var = locals[i]; - localsName << var["name"].literal(); + if (r.hasField("locals")) { + const Value& locals = r["locals"]; + + QStringList localsName; + for (int i = 0; i < locals.size(); i++) { + const Value& var = locals[i]; + localsName << var["name"].literal(); + } + int frame = m_session->frameStackModel()->currentFrame(); + m_session->addCommand(StackListArguments, + //dont'show value, low-frame, high-frame + QString("0 %1 %2").arg(frame).arg(frame), + new StackListArgumentsHandler(localsName)); } - int frame = m_session->frameStackModel()->currentFrame(); - m_session->addCommand(StackListArguments, - //dont'show value, low-frame, high-frame - QString("0 %1 %2").arg(frame).arg(frame), - new StackListArgumentsHandler(localsName)); } private: @@ -223,8 +224,9 @@ void MIVariableController::addWatch(const ResultRecord& r) { - // FIXME: handle error. - if (r.reason == "done" && !r["path_expr"].literal().isEmpty()) { + if (r.reason == "done" + && r.hasField("path_expr") + && !r["path_expr"].literal().isEmpty()) { variableCollection()->watches()->add(r["path_expr"].literal()); } } diff --git a/debuggers/common/widgets/debuggerconsoleview.h b/debuggers/common/widgets/debuggerconsoleview.h new file mode 100644 --- /dev/null +++ b/debuggers/common/widgets/debuggerconsoleview.h @@ -0,0 +1,157 @@ +/* + * Debugger Console View + * + * Copyright 2003 John Birch + * Copyright 2007 Hamish Rodda + * 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 DEBUGGERCONSOLEVIEW_H +#define DEBUGGERCONSOLEVIEW_H + +#include +#include +#include +#include + +#include "dbgglobal.h" + +class QMenu; +class QTextEdit; +class QToolBar; +class KHistoryComboBox; + +namespace KDevelop { +class IDebugSession; +} + +namespace KDevMI { +class MIDebuggerPlugin; + +/** + * @brief A debugger console gives the user direct access to the debugger command line interface. + */ +class DebuggerConsoleView : public QWidget +{ + Q_OBJECT +public: + DebuggerConsoleView(MIDebuggerPlugin *plugin, QWidget *parent = nullptr); + ~DebuggerConsoleView(); + + /** + * Whether show a button allowing user to interrput debugger execution. + */ + void setShowInterrupt(bool enable); + + void setShowInternalCommands(bool enable); + +Q_SIGNALS: + void requestRaise(); + /** + * Proxy signals for DebugSession + */ + void interruptDebugger(); + void sendCommand(const QString &cmd); + + +protected: + void setupUi(); + void setupToolBar(); + + /** + * Arranges for 'line' to be shown to the user. + * Adds 'line' to m_pendingOutput and makes sure + * m_updateTimer is running. + */ + void appendLine(const QString &line); + void updateColors(); + + /** + * escape html meta characters and handle line break + */ + QString toHtmlEscaped(QString text); + + QString colorify(QString text, const QColor &color); + + /** + * Makes 'l' no longer than 'max_size' by + * removing excessive elements from the top. + */ + void trimList(QStringList& l, int max_size); + + void changeEvent(QEvent *e) override; + void focusInEvent(QFocusEvent *e) override; + +protected Q_SLOTS: + void showContextMenu(const QPoint &pos); + void toggleRepeat(bool checked); + void toggleShowInternalCommands(bool checked); + void flushPending(); + void clear(); + + void handleSessionChanged(KDevelop::IDebugSession *session); + void handleDebuggerStateChange(DBGStateFlags oldStatus, DBGStateFlags newStatus); + void receivedInternalCommandStdout(const QString &line); + void receivedUserCommandStdout(const QString &line); + void receivedStdout(const QString &line, bool internal); + void receivedStderr(const QString &line); + + void trySendCommand(QString cmd); +private: + QAction *m_actRepeat; + QAction *m_actInterrupt; + QAction *m_actShowInternal; + QAction *m_actCmdEditor; + + QTextEdit *m_textView; + QMenu *m_contextMenu; + QToolBar *m_toolBar; + KHistoryComboBox *m_cmdEditor; + + bool m_repeatLastCommand; + bool m_showInternalCommands; + bool m_cmdEditorHadFocus; + + /** + * The output from user commands only and from all + * commands. We keep it here so that if we switch + * "Show internal commands" on, we can show previous + * internal commands. + */ + QStringList m_allOutput; + QStringList m_userOutput; + + /** + * For performance reasons, we don't immediately add new text + * to QTExtEdit. Instead we add it to m_pendingOutput and + * flush it on timer. + */ + QString m_pendingOutput; + QTimer m_updateTimer; + + QColor m_stdColor; + QColor m_errorColor; + + int m_maxLines; +}; + +} // end of namespace KDevMI + +#endif // DEBUGGERCONSOLEVIEW_H diff --git a/debuggers/common/widgets/debuggerconsoleview.cpp b/debuggers/common/widgets/debuggerconsoleview.cpp new file mode 100644 --- /dev/null +++ b/debuggers/common/widgets/debuggerconsoleview.cpp @@ -0,0 +1,400 @@ +/* + * Debugger Console View + * + * Copyright 2003 John Birch + * Copyright 2006 Vladimir Prus + * Copyright 2007 Hamish Rodda + * 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 "debuggerconsoleview.h" + +#include "debuglog.h" +#include "midebuggerplugin.h" +#include "midebugsession.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KDevMI; + +DebuggerConsoleView::DebuggerConsoleView(MIDebuggerPlugin *plugin, QWidget *parent) + : QWidget(parent) + , m_repeatLastCommand(false) + , m_showInternalCommands(false) + , m_cmdEditorHadFocus(false) + , m_maxLines(5000) +{ + setWindowIcon(QIcon::fromTheme("dialog-scripts")); + setWindowTitle(i18n("Debugger Console")); + setWhatsThis(i18n("Debugger Console

" + "Shows all debugger commands being executed. " + "You can also issue any other debugger command while debugging.

")); + + setupUi(); + + m_actRepeat = new QAction(QIcon::fromTheme("edit-redo"), + i18n("Repeat last command when hit Return"), + this); + m_actRepeat->setCheckable(true); + m_actRepeat->setChecked(m_repeatLastCommand); + connect(m_actRepeat, &QAction::toggled, this, &DebuggerConsoleView::toggleRepeat); + m_toolBar->insertAction(m_actCmdEditor, m_actRepeat); + + m_actInterrupt = new QAction(QIcon::fromTheme("media-playback-pause"), + i18n("Pause execution of the app to enter gdb commands"), + this); + connect(m_actInterrupt, &QAction::triggered, this, &DebuggerConsoleView::interruptDebugger); + m_toolBar->insertAction(m_actCmdEditor, m_actInterrupt); + setShowInterrupt(true); + + m_actShowInternal = new QAction(i18n("Show Internal Commands")); + m_actShowInternal->setCheckable(true); + m_actShowInternal->setChecked(m_showInternalCommands); + m_actShowInternal->setWhatsThis(i18n( + "Controls if commands issued internally by KDevelop " + "will be shown or not.
" + "This option will affect only future commands, it will not " + "add or remove already issued commands from the view.")); + connect(m_actShowInternal, &QAction::toggled, + this, &DebuggerConsoleView::toggleShowInternalCommands); + + handleDebuggerStateChange(s_none, s_dbgNotStarted); + + m_updateTimer.setSingleShot(true); + connect(&m_updateTimer, &QTimer::timeout, this, &DebuggerConsoleView::flushPending); + + connect(plugin->core()->debugController(), &KDevelop::IDebugController::currentSessionChanged, + this, &DebuggerConsoleView::handleSessionChanged); + + connect(plugin, &MIDebuggerPlugin::reset, this, &DebuggerConsoleView::clear); + connect(plugin, &MIDebuggerPlugin::raiseDebuggerConsoleViews, + this, &DebuggerConsoleView::requestRaise); + + handleSessionChanged(plugin->core()->debugController()->currentSession()); + + updateColors(); +} + +void DebuggerConsoleView::changeEvent(QEvent *event) +{ + if (event->type() == QEvent::PaletteChange) { + updateColors(); + } +} + +void DebuggerConsoleView::updateColors() +{ + KColorScheme scheme(QPalette::Active); + m_stdColor = scheme.foreground(KColorScheme::LinkText).color(); + m_errorColor = scheme.foreground(KColorScheme::NegativeText).color(); +} + +void DebuggerConsoleView::setupUi() +{ + setupToolBar(); + + m_textView = new QTextEdit; + m_textView->setReadOnly(true); + m_textView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_textView, &QTextEdit::customContextMenuRequested, + this, &DebuggerConsoleView::showContextMenu); + + auto vbox = new QVBoxLayout; + vbox->addWidget(m_textView); + vbox->addWidget(m_toolBar); + + setLayout(vbox); + + m_cmdEditor = new KHistoryComboBox(this); + m_cmdEditor->setDuplicatesEnabled(false); + connect(m_cmdEditor, + static_cast(&KHistoryComboBox::returnPressed), + this, &DebuggerConsoleView::trySendCommand); + + auto label = new QLabel(i18n("&Command:"), this); + label->setBuddy(m_cmdEditor); + + auto hbox = new QHBoxLayout; + hbox->addWidget(label); + hbox->addWidget(m_cmdEditor); + hbox->setStretchFactor(m_cmdEditor, 1); + hbox->setContentsMargins(0, 0, 0, 0); + + auto cmdEditor = new QWidget(this); + cmdEditor->setLayout(hbox); + m_actCmdEditor = m_toolBar->addWidget(cmdEditor); +} + +void DebuggerConsoleView::setupToolBar() +{ + m_toolBar = new QToolBar(this); + int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize); + m_toolBar->setIconSize(QSize(iconSize, iconSize)); + m_toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); + m_toolBar->setFloatable(false); + m_toolBar->setMovable(false); + m_toolBar->setWindowTitle(i18n("%1 Command Bar", windowTitle())); + m_toolBar->setContextMenuPolicy(Qt::PreventContextMenu); + + // remove margins, to make command editor nicely aligned with the output + m_toolBar->layout()->setContentsMargins(0, 0, 0, 0); +} + +void DebuggerConsoleView::focusInEvent(QFocusEvent*) +{ + m_textView->verticalScrollBar()->setValue(m_textView->verticalScrollBar()->maximum()); + m_cmdEditor->setFocus(); +} + +DebuggerConsoleView::~DebuggerConsoleView() +{ +} + +void DebuggerConsoleView::setShowInterrupt(bool enable) +{ + m_actInterrupt->setVisible(enable); +} + +void DebuggerConsoleView::setShowInternalCommands(bool enable) +{ + if (enable != m_showInternalCommands) + { + m_showInternalCommands = enable; + + // Set of strings to show changes, text edit still has old + // set. Refresh. + m_textView->clear(); + QStringList& newList = m_showInternalCommands ? m_allOutput : m_userOutput; + + for (const auto &line : newList) { + // Note that color formatting is already applied to 'line'. + appendLine(line); + } + } +} + +void DebuggerConsoleView::showContextMenu(const QPoint &pos) +{ + QScopedPointer popup(m_textView->createStandardContextMenu(pos)); + + popup->addSeparator(); + popup->addAction(m_actShowInternal); + + popup->exec(m_textView->mapToGlobal(pos)); +} + +void DebuggerConsoleView::toggleRepeat(bool checked) +{ + m_repeatLastCommand = checked; +} + +void DebuggerConsoleView::toggleShowInternalCommands(bool checked) +{ + setShowInternalCommands(checked); +} + +void DebuggerConsoleView::appendLine(const QString& line) +{ + m_pendingOutput += line; + + // To improve performance, we update the view after some delay. + if (!m_updateTimer.isActive()) + { + m_updateTimer.start(100); + } +} + +void DebuggerConsoleView::flushPending() +{ + m_textView->setUpdatesEnabled(false); + + QTextDocument *document = m_textView->document(); + QTextCursor cursor(document); + cursor.movePosition(QTextCursor::End); + cursor.insertHtml(m_pendingOutput); + m_pendingOutput.clear(); + + m_textView->verticalScrollBar()->setValue(m_textView->verticalScrollBar()->maximum()); + m_textView->setUpdatesEnabled(true); + m_textView->update(); + if (m_cmdEditorHadFocus) { + m_cmdEditor->setFocus(); + } +} + +void DebuggerConsoleView::clear() +{ + if (m_textView) + m_textView->clear(); + + if (m_cmdEditor) + m_cmdEditor->clear(); + + m_userOutput.clear(); + m_allOutput.clear(); +} + +void DebuggerConsoleView::handleDebuggerStateChange(DBGStateFlags, DBGStateFlags newStatus) +{ + if (newStatus & s_dbgNotStarted) { + m_actInterrupt->setEnabled(false); + m_cmdEditor->setEnabled(false); + return; + } else { + m_actInterrupt->setEnabled(true); + } + + if (newStatus & s_dbgBusy) { + if (m_cmdEditor->isEnabled()) { + m_cmdEditorHadFocus = m_cmdEditor->hasFocus(); + } + m_cmdEditor->setEnabled(false); + } else { + m_cmdEditor->setEnabled(true); + } +} + +QString DebuggerConsoleView::toHtmlEscaped(QString text) +{ + text = text.toHtmlEscaped(); + + text.replace('\n', "
"); + return text; +} + + +QString DebuggerConsoleView::colorify(QString text, const QColor& color) +{ + text = "" + text + ""; + return text; +} + +void DebuggerConsoleView::receivedInternalCommandStdout(const QString& line) +{ + receivedStdout(line, true); +} + +void DebuggerConsoleView::receivedUserCommandStdout(const QString& line) +{ + receivedStdout(line, false); +} + +void DebuggerConsoleView::receivedStdout(const QString& line, bool internal) +{ + QString colored = toHtmlEscaped(line); + if (colored.startsWith("(gdb)")) { + colored = colorify(colored, m_stdColor); + } + + m_allOutput.append(colored); + trimList(m_allOutput, m_maxLines); + + if (!internal) { + m_userOutput.append(colored); + trimList(m_userOutput, m_maxLines); + } + + if (!internal || m_showInternalCommands) + appendLine(colored); +} + +void DebuggerConsoleView::receivedStderr(const QString& line) +{ + QString colored = toHtmlEscaped(line); + colored = colorify(colored, m_errorColor); + + // Errors are shown inside user commands too. + m_allOutput.append(colored); + trimList(m_allOutput, m_maxLines); + + m_userOutput.append(colored); + trimList(m_userOutput, m_maxLines); + + appendLine(colored); +} + +void DebuggerConsoleView::trimList(QStringList& l, int max_size) +{ + int length = l.count(); + if (length > max_size) + { + for(int to_delete = length - max_size; to_delete; --to_delete) + { + l.erase(l.begin()); + } + } +} + +void DebuggerConsoleView::trySendCommand(QString cmd) +{ + if (m_repeatLastCommand && cmd.isEmpty()) { + cmd = m_cmdEditor->historyItems().last(); + } + if (!cmd.isEmpty()) + { + m_cmdEditor->addToHistory(cmd); + m_cmdEditor->clearEditText(); + + emit sendCommand(cmd); + } +} + +void DebuggerConsoleView::handleSessionChanged(KDevelop::IDebugSession* s) +{ + MIDebugSession *session = qobject_cast(s); + if (!session) return; + + connect(this, &DebuggerConsoleView::sendCommand, + session, &MIDebugSession::addUserCommand); + connect(this, &DebuggerConsoleView::interruptDebugger, + session, &MIDebugSession::interruptDebugger); + + connect(session, &MIDebugSession::debuggerInternalCommandOutput, + this, &DebuggerConsoleView::receivedInternalCommandStdout); + connect(session, &MIDebugSession::debuggerUserCommandOutput, + this, &DebuggerConsoleView::receivedUserCommandStdout); + connect(session, &MIDebugSession::debuggerInternalOutput, + this, &DebuggerConsoleView::receivedStderr); + + connect(session, &MIDebugSession::debuggerStateChanged, + this, &DebuggerConsoleView::handleDebuggerStateChange); + + handleDebuggerStateChange(s_none, session->debuggerState()); +} diff --git a/debuggers/common/widgets/debuggerconsoleview.ui b/debuggers/common/widgets/debuggerconsoleview.ui new file mode 100644 --- /dev/null +++ b/debuggers/common/widgets/debuggerconsoleview.ui @@ -0,0 +1,85 @@ + + + MainWindow + + + + 0 + 0 + 605 + 330 + + + + Debugger Console + + + + + + + + + + + toolBar + + + false + + + Qt::BottomToolBarArea + + + + 22 + 22 + + + + false + + + TopToolBarArea + + + false + + + + + + + true + + + + + + Interrupt + + + Pause execution of the app to enter gdb commands + + + + + true + + + true + + + + + + Repeat + + + Repeat last sent command when hit <Return> + + + + + + diff --git a/debuggers/common/widgets/disassemblewidget.h b/debuggers/common/widgets/disassemblewidget.h --- a/debuggers/common/widgets/disassemblewidget.h +++ b/debuggers/common/widgets/disassemblewidget.h @@ -93,8 +93,6 @@ QActionGroup* m_disassemblyFlavorActionGroup; }; -class Breakpoint; -class DebugSession; class MIDebuggerPlugin; diff --git a/debuggers/common/widgets/disassemblewidget.cpp b/debuggers/common/widgets/disassemblewidget.cpp --- a/debuggers/common/widgets/disassemblewidget.cpp +++ b/debuggers/common/widgets/disassemblewidget.cpp @@ -533,6 +533,8 @@ disassemblyFlavor = DisassemblyFlavorATT; } else if (value.literal() == "intel") { disassemblyFlavor = DisassemblyFlavorIntel; + } else if (value.literal() == "default") { + disassemblyFlavor = DisassemblyFlavorATT; } m_disassembleWindow->setDisassemblyFlavor(disassemblyFlavor); } diff --git a/debuggers/gdb/CMakeLists.txt b/debuggers/gdb/CMakeLists.txt --- a/debuggers/gdb/CMakeLists.txt +++ b/debuggers/gdb/CMakeLists.txt @@ -1,24 +1,6 @@ project(gdb) add_definitions(-DTRANSLATION_DOMAIN=\"kdevgdb\") -function(add_debuggable_executable target) - cmake_parse_arguments(add_debuggable_executable "" "" "SRCS" ${ARGN}) - add_executable(${target} ${add_debuggable_executable_SRCS}) - # force debug symbols for our debuggees, disable optimizations - if (WIN32) - set(_flags "/0d") - else() - set(_flags "-O0") - endif() - set_target_properties(${target} PROPERTIES COMPILE_FLAGS "${CMAKE_CXX_FLAGS_DEBUG} ${_flags}") -endfunction() - -if (CMAKE_VERSION VERSION_GREATER "2.9" OR NOT CMAKE_GENERATOR MATCHES "Ninja") - set(HAVE_PATH_WITH_SPACES_TEST TRUE) -else() - message(WARNING "Disabling 'path with spaces' test, this CMake version would create a faulty build.ninja file. Upgrade to at least CMake v3.0") -endif() - add_subdirectory(unittests) add_subdirectory(printers) diff --git a/debuggers/gdb/debuggerplugin.h b/debuggers/gdb/debuggerplugin.h --- a/debuggers/gdb/debuggerplugin.h +++ b/debuggers/gdb/debuggerplugin.h @@ -70,11 +70,9 @@ CppDebuggerPlugin(QObject *parent, const QVariantList & = QVariantList()); ~CppDebuggerPlugin() override; - DebugSession *createSession() const override; - void unload() override; - -private: - void setupToolviews(); + DebugSession *createSession() override; + void unloadToolviews() override; + void setupToolviews() override; private: DebuggerToolFactory* disassemblefactory; diff --git a/debuggers/gdb/debuggerplugin.cpp b/debuggers/gdb/debuggerplugin.cpp --- a/debuggers/gdb/debuggerplugin.cpp +++ b/debuggers/gdb/debuggerplugin.cpp @@ -48,9 +48,24 @@ CppDebuggerPlugin::CppDebuggerPlugin(QObject *parent, const QVariantList &) : MIDebuggerPlugin("kdevgdb", parent) + , disassemblefactory(nullptr) + , gdbfactory(nullptr) + , memoryviewerfactory(nullptr) { setXMLFile("kdevgdbui.rc"); + QList plugins = KDevelop::ICore::self()->pluginController()->allPluginsForExtension("org.kdevelop.IExecutePlugin"); + foreach(IPlugin* plugin, plugins) { + IExecutePlugin* iface = plugin->extension(); + Q_ASSERT(iface); + KDevelop::LaunchConfigurationType* type = core()->runController()->launchConfigurationTypeForId( iface->nativeAppConfigTypeId() ); + Q_ASSERT(type); + type->addLauncher( new GdbLauncher( this, iface ) ); + } +} + +void CppDebuggerPlugin::setupToolviews() +{ disassemblefactory = new DebuggerToolFactory( this, "org.kdevelop.debugger.DisassemblerView", Qt::BottomDockWidgetArea); @@ -74,31 +89,31 @@ i18n("Memory"), memoryviewerfactory); #endif - - QList plugins = KDevelop::ICore::self()->pluginController()->allPluginsForExtension("org.kdevelop.IExecutePlugin"); - foreach(IPlugin* plugin, plugins) { - IExecutePlugin* iface = plugin->extension(); - Q_ASSERT(iface); - KDevelop::LaunchConfigurationType* type = core()->runController()->launchConfigurationTypeForId( iface->nativeAppConfigTypeId() ); - Q_ASSERT(type); - type->addLauncher( new GdbLauncher( this, iface ) ); - } } -void CppDebuggerPlugin::unload() +void CppDebuggerPlugin::unloadToolviews() { - core()->uiController()->removeToolView(disassemblefactory); - core()->uiController()->removeToolView(gdbfactory); - core()->uiController()->removeToolView(memoryviewerfactory); + if (disassemblefactory) { + core()->uiController()->removeToolView(disassemblefactory); + disassemblefactory = nullptr; + } + if (gdbfactory) { + core()->uiController()->removeToolView(gdbfactory); + gdbfactory = nullptr; + } + if (memoryviewerfactory) { + core()->uiController()->removeToolView(memoryviewerfactory); + memoryviewerfactory = nullptr; + } } CppDebuggerPlugin::~CppDebuggerPlugin() { } -DebugSession* CppDebuggerPlugin::createSession() const +DebugSession* CppDebuggerPlugin::createSession() { - DebugSession *session = new DebugSession(); + DebugSession *session = new DebugSession(this); KDevelop::ICore::self()->debugController()->addSession(session); connect(session, &DebugSession::showMessage, this, &CppDebuggerPlugin::showStatusMessage); connect(session, &DebugSession::reset, this, &CppDebuggerPlugin::reset); diff --git a/debuggers/gdb/debugsession.h b/debuggers/gdb/debugsession.h --- a/debuggers/gdb/debugsession.h +++ b/debuggers/gdb/debugsession.h @@ -61,11 +61,12 @@ namespace GDB { +class CppDebuggerPlugin; class DebugSession : public MIDebugSession { Q_OBJECT public: - DebugSession(); + explicit DebugSession(CppDebuggerPlugin *plugin = nullptr); ~DebugSession() override; BreakpointController * breakpointController() const override; @@ -80,9 +81,10 @@ protected: GdbDebugger *createDebugger() const override; void initializeDebugger() override; - bool execInferior(KDevelop::ILaunchConfiguration *cfg, const QString &executable) override; + bool execInferior(KDevelop::ILaunchConfiguration *cfg, IExecutePlugin *iexec, + const QString &executable) override; - void configure(KDevelop::ILaunchConfiguration *cfg); + void configure(KDevelop::ILaunchConfiguration *cfg, IExecutePlugin *iexec); private Q_SLOTS: void handleVersion(const QStringList& s); diff --git a/debuggers/gdb/debugsession.cpp b/debuggers/gdb/debugsession.cpp --- a/debuggers/gdb/debugsession.cpp +++ b/debuggers/gdb/debugsession.cpp @@ -26,6 +26,7 @@ #include "debugsession.h" #include "debuglog.h" +#include "debuggerplugin.h" #include "gdb.h" #include "gdbbreakpointcontroller.h" #include "gdbframestackmodel.h" @@ -35,9 +36,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -53,20 +56,23 @@ using namespace KDevMI::MI; using namespace KDevelop; -DebugSession::DebugSession() - : MIDebugSession() +DebugSession::DebugSession(CppDebuggerPlugin *plugin) + : MIDebugSession(plugin) , m_breakpointController(nullptr) , m_variableController(nullptr) , m_frameStackModel(nullptr) , m_autoDisableASLR(false) { m_breakpointController = new BreakpointController(this); m_variableController = new VariableController(this); m_frameStackModel = new GdbFrameStackModel(this); + + if (m_plugin) m_plugin->setupToolviews(); } DebugSession::~DebugSession() { + if (m_plugin) m_plugin->unloadToolviews(); } void DebugSession::setAutoDisableASLR(bool enable) @@ -133,13 +139,13 @@ qCDebug(DEBUGGERGDB) << "Initialized GDB"; } -void DebugSession::configure(ILaunchConfiguration *cfg) +void DebugSession::configure(ILaunchConfiguration *cfg, IExecutePlugin *iexec) { // Read Configuration values KConfigGroup grp = cfg->config(); - bool breakOnStart = grp.readEntry(KDevMI::breakOnStartEntry, false); - bool displayStaticMembers = grp.readEntry(KDevMI::staticMembersEntry, false); - bool asmDemangle = grp.readEntry(KDevMI::demangleNamesEntry, true); + bool breakOnStart = grp.readEntry(KDevMI::Config::BreakOnStartEntry, false); + bool displayStaticMembers = grp.readEntry(Config::StaticMembersEntry, false); + bool asmDemangle = grp.readEntry(Config::DemangleNamesEntry, true); if (breakOnStart) { BreakpointModel* m = ICore::self()->debugController()->breakpointModel(); @@ -171,20 +177,34 @@ addCommand(MI::GdbSet, "print asm-demangle off"); } + // Set the environment variables + 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(); + } + for (const auto &envvar : l.createEnvironment(envgrp, {})) { + addCommand(GdbSet, "environment " + envvar); + } + qCDebug(DEBUGGERGDB) << "Per inferior configuration done"; } -bool DebugSession::execInferior(ILaunchConfiguration *cfg, const QString &executable) +bool DebugSession::execInferior(ILaunchConfiguration *cfg, IExecutePlugin *iexec, + const QString &executable) { qCDebug(DEBUGGERGDB) << "Executing inferior"; // debugger specific config - configure(cfg); + configure(cfg, iexec); KConfigGroup grp = cfg->config(); - QUrl configGdbScript = grp.readEntry(KDevMI::remoteGdbConfigEntry, QUrl()); - QUrl runShellScript = grp.readEntry(KDevMI::remoteGdbShellEntry, QUrl()); - QUrl runGdbScript = grp.readEntry(KDevMI::remoteGdbRunEntry, QUrl()); + QUrl configGdbScript = grp.readEntry(Config::RemoteGdbConfigEntry, QUrl()); + QUrl runShellScript = grp.readEntry(Config::RemoteGdbShellEntry, QUrl()); + QUrl runGdbScript = grp.readEntry(Config::RemoteGdbRunEntry, QUrl()); // handle remote debug if (configGdbScript.isValid()) { diff --git a/debuggers/gdb/gdb.cpp b/debuggers/gdb/gdb.cpp --- a/debuggers/gdb/gdb.cpp +++ b/debuggers/gdb/gdb.cpp @@ -51,7 +51,7 @@ bool GdbDebugger::start(KConfigGroup& config, const QStringList& extraArguments) { // FIXME: verify that default value leads to something sensible - QUrl gdbUrl = config.readEntry(gdbPathEntry, QUrl()); + QUrl gdbUrl = config.readEntry(Config::GdbPathEntry, QUrl()); if (gdbUrl.isEmpty()) { debuggerBinary_ = "gdb"; } else { @@ -62,7 +62,7 @@ QStringList arguments = extraArguments; arguments << "--interpreter=mi2" << "-quiet"; - QUrl shell = config.readEntry(debuggerShellEntry, QUrl()); + QUrl shell = config.readEntry(Config::DebuggerShellEntry, QUrl()); if(!shell.isEmpty()) { qCDebug(DEBUGGERGDB) << "have shell" << shell; QString shell_without_args = shell.toLocalFile().split(QChar(' ')).first(); diff --git a/debuggers/gdb/gdbconfigpage.cpp b/debuggers/gdb/gdbconfigpage.cpp --- a/debuggers/gdb/gdbconfigpage.cpp +++ b/debuggers/gdb/gdbconfigpage.cpp @@ -44,6 +44,7 @@ #include #include +#include "dbgglobal.h" #include "debugsession.h" #include "debuggerplugin.h" #include "midebugjobs.h" @@ -53,6 +54,7 @@ #include using namespace KDevelop; +namespace Config = KDevMI::GDB::Config; GdbConfigPage::GdbConfigPage( QWidget* parent ) : LaunchConfigurationPage(parent), ui( new Ui::GdbConfigPage ) @@ -88,27 +90,27 @@ void GdbConfigPage::loadFromConfiguration( const KConfigGroup& cfg, KDevelop::IProject* ) { bool block = blockSignals( true ); - ui->kcfg_gdbPath->setUrl( cfg.readEntry( KDevMI::gdbPathEntry, QUrl() ) ); - ui->kcfg_debuggingShell->setUrl( cfg.readEntry( KDevMI::debuggerShellEntry, QUrl() ) ); - ui->kcfg_configGdbScript->setUrl( cfg.readEntry( KDevMI::remoteGdbConfigEntry, QUrl() ) ); - ui->kcfg_runShellScript->setUrl( cfg.readEntry( KDevMI::remoteGdbShellEntry, QUrl() ) ); - ui->kcfg_runGdbScript->setUrl( cfg.readEntry( KDevMI::remoteGdbRunEntry, QUrl() ) ); - ui->kcfg_displayStaticMembers->setChecked( cfg.readEntry(KDevMI::staticMembersEntry, false) ); - ui->kcfg_asmDemangle->setChecked( cfg.readEntry( KDevMI::demangleNamesEntry, true) ); - ui->kcfg_startWith->setCurrentIndex( ui->kcfg_startWith->findData( cfg.readEntry( KDevMI::startWithEntry, "ApplicationOutput" ) ) ); + ui->kcfg_gdbPath->setUrl( cfg.readEntry( Config::GdbPathEntry, QUrl() ) ); + ui->kcfg_debuggingShell->setUrl( cfg.readEntry( Config::DebuggerShellEntry, QUrl() ) ); + ui->kcfg_configGdbScript->setUrl( cfg.readEntry( Config::RemoteGdbConfigEntry, QUrl() ) ); + ui->kcfg_runShellScript->setUrl( cfg.readEntry( Config::RemoteGdbShellEntry, QUrl() ) ); + ui->kcfg_runGdbScript->setUrl( cfg.readEntry( Config::RemoteGdbRunEntry, QUrl() ) ); + ui->kcfg_displayStaticMembers->setChecked( cfg.readEntry( Config::StaticMembersEntry, false ) ); + ui->kcfg_asmDemangle->setChecked( cfg.readEntry( Config::DemangleNamesEntry, true) ); + ui->kcfg_startWith->setCurrentIndex( ui->kcfg_startWith->findData( cfg.readEntry( KDevMI::Config::StartWithEntry, "ApplicationOutput" ) ) ); blockSignals( block ); } void GdbConfigPage::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* ) const { - cfg.writeEntry(KDevMI::gdbPathEntry, ui->kcfg_gdbPath->url() ); - cfg.writeEntry(KDevMI::debuggerShellEntry, ui->kcfg_debuggingShell->url() ); - cfg.writeEntry(KDevMI::remoteGdbConfigEntry, ui->kcfg_configGdbScript->url() ); - cfg.writeEntry(KDevMI::remoteGdbShellEntry, ui->kcfg_runShellScript->url() ); - cfg.writeEntry(KDevMI::remoteGdbRunEntry, ui->kcfg_runGdbScript->url() ); - cfg.writeEntry(KDevMI::staticMembersEntry, ui->kcfg_displayStaticMembers->isChecked() ); - cfg.writeEntry(KDevMI::demangleNamesEntry, ui->kcfg_asmDemangle->isChecked() ); - cfg.writeEntry(KDevMI::startWithEntry, ui->kcfg_startWith->itemData( ui->kcfg_startWith->currentIndex() ).toString() ); + cfg.writeEntry(Config::GdbPathEntry, ui->kcfg_gdbPath->url() ); + cfg.writeEntry(Config::DebuggerShellEntry, ui->kcfg_debuggingShell->url() ); + cfg.writeEntry(Config::RemoteGdbConfigEntry, ui->kcfg_configGdbScript->url() ); + cfg.writeEntry(Config::RemoteGdbShellEntry, ui->kcfg_runShellScript->url() ); + cfg.writeEntry(Config::RemoteGdbRunEntry, ui->kcfg_runGdbScript->url() ); + cfg.writeEntry(Config::StaticMembersEntry, ui->kcfg_displayStaticMembers->isChecked() ); + cfg.writeEntry(Config::DemangleNamesEntry, ui->kcfg_asmDemangle->isChecked() ); + cfg.writeEntry(KDevMI::Config::StartWithEntry, ui->kcfg_startWith->itemData( ui->kcfg_startWith->currentIndex() ).toString() ); } QString GdbConfigPage::title() const diff --git a/debuggers/gdb/unittests/CMakeLists.txt b/debuggers/gdb/unittests/CMakeLists.txt --- a/debuggers/gdb/unittests/CMakeLists.txt +++ b/debuggers/gdb/unittests/CMakeLists.txt @@ -5,6 +5,7 @@ add_debuggable_executable(debugeespace SRCS "debugee space.cpp") add_debuggable_executable(debugeemultilocbreakpoint SRCS debugeemultilocbreakpoint.cpp) add_debuggable_executable(debugeemultiplebreakpoint SRCS debugeemultiplebreakpoint.cpp) +add_debuggable_executable(debugeeechoenv SRCS debugeeechoenv.cpp) add_debuggable_executable(debugeethreads SRCS debugeethreads.cpp) target_link_libraries(debugeethreads Qt5::Core) diff --git a/debuggers/gdb/unittests/debugeeechoenv.cpp b/debuggers/gdb/unittests/debugeeechoenv.cpp new file mode 100644 --- /dev/null +++ b/debuggers/gdb/unittests/debugeeechoenv.cpp @@ -0,0 +1,40 @@ +/* + * 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 +#include + +int main() { + char * value = std::getenv("VariableA"); + if (value) { + std::cout << value << std::endl; + } else { + std::cout << "Not found!" << std::endl; + } + + value = std::getenv("VariableB"); + if (value) { + std::cout << value << std::endl; + } else { + std::cout << "Not found!" << std::endl; + } + return 0; +} diff --git a/debuggers/gdb/unittests/test_gdb.h b/debuggers/gdb/unittests/test_gdb.h --- a/debuggers/gdb/unittests/test_gdb.h +++ b/debuggers/gdb/unittests/test_gdb.h @@ -39,6 +39,7 @@ void init(); void testStdOut(); + void testEnvironmentSet(); void testBreakpoint(); void testDisableBreakpoint(); void testChangeLocationBreakpoint(); diff --git a/debuggers/gdb/unittests/test_gdb.cpp b/debuggers/gdb/unittests/test_gdb.cpp --- a/debuggers/gdb/unittests/test_gdb.cpp +++ b/debuggers/gdb/unittests/test_gdb.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -126,6 +127,16 @@ vc->watches()->clear(); } +class WritableEnvironmentGroupList : public KDevelop::EnvironmentGroupList +{ +public: + explicit WritableEnvironmentGroupList(KConfig* config) : EnvironmentGroupList(config) {} + + using EnvironmentGroupList::variables; + using EnvironmentGroupList::saveSettings; + using EnvironmentGroupList::removeGroup; +}; + class TestLaunchConfiguration : public KDevelop::ILaunchConfiguration { public: @@ -147,6 +158,8 @@ QString name() const override { return QString("Test-Launch"); } KDevelop::IProject* project() const override { return 0; } KDevelop::LaunchConfigurationType* type() const override { return 0; } + + KConfig *rootConfig() { return c; } private: KConfigGroup cfg; KConfig *c; @@ -294,6 +307,38 @@ } } +void GdbTest::testEnvironmentSet() +{ + TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration cfg(findExecutable("debugeeechoenv")); + + cfg.config().writeEntry("EnvironmentGroup", "GdbTestGroup"); + + WritableEnvironmentGroupList envGroups(cfg.rootConfig()); + envGroups.removeGroup("GdbTestGroup"); + auto &envs = envGroups.variables("GdbTestGroup"); + envs["VariableA"] = "-A' \" complex --value"; + envs["VariableB"] = "-B' \" complex --value"; + envGroups.saveSettings(cfg.rootConfig()); + + QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); + + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); + + QVERIFY(outputSpy.count() > 0); + + QStringList outputLines; + while (outputSpy.count() > 0) { + QList arguments = outputSpy.takeFirst(); + for (const auto &item : arguments) { + outputLines.append(item.toStringList()); + } + } + QCOMPARE(outputLines, QStringList() << "-A' \" complex --value" + << "-B' \" complex --value"); +} + void GdbTest::testBreakpoint() { TestDebugSession *session = new TestDebugSession; @@ -990,7 +1035,7 @@ TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - cfg.config().writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(findSourceFile("gdb_script_empty"))); + cfg.config().writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(findSourceFile("gdb_script_empty"))); QVERIFY(session->startDebugging(&cfg, m_iface)); session->addCommand(MI::NonMI, QString("attach %0").arg(debugeeProcess.pid())); @@ -1479,7 +1524,7 @@ TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); - grp.writeEntry(remoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); + grp.writeEntry(Config::RemoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile("debugee.cpp"), 31); QVERIFY(session->startDebugging(&cfg, m_iface)); @@ -1500,7 +1545,7 @@ TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); - grp.writeEntry(remoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); + grp.writeEntry(Config::RemoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); for (int i = 0; i < 2; ++i) { @@ -1526,7 +1571,7 @@ TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); - grp.writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); + grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); @@ -1564,8 +1609,8 @@ TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); - grp.writeEntry(remoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); - grp.writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); + grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); + grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); @@ -1608,8 +1653,8 @@ TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); - grp.writeEntry(remoteGdbShellEntry, QUrl::fromLocalFile(shellScript.fileName()+"-copy")); - grp.writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); + grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile(shellScript.fileName()+"-copy")); + grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); @@ -1659,8 +1704,8 @@ TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); - grp.writeEntry(remoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); - grp.writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); + grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); + grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); @@ -1775,26 +1820,31 @@ WAIT_FOR_STATE(session, DebugSession::EndedState); } -//TODO: figure out why do we need this test? And do we need it at all?? void GdbTest::testThreadAndFrameInfo() { + // Check if --thread is added to user commands + TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeethreads")); QString fileName = findSourceFile("debugeethreads.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(1000); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QSignalSpy outputSpy(session, &TestDebugSession::debuggerUserCommandOutput); - session->addCommand( - new MI::UserCommand(MI::ThreadInfo,"")); + session->addCommand(new MI::UserCommand(MI::ThreadInfo,"")); session->addCommand(new MI::UserCommand(MI::StackListLocals, QLatin1String("0"))); - QTest::qWait(1000); - QCOMPARE(outputSpy.count(), 2); - QVERIFY(outputSpy.last().at(0).toString().contains(QLatin1String("--thread 1"))); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // wait for command finish + + // outputs should be + // 1. -thread-info + // 2. ^done for thread-info + // 3. -stack-list-locals + // 4. ^done for -stack-list-locals + QCOMPARE(outputSpy.count(), 4); + QVERIFY(outputSpy.at(2).at(0).toString().contains(QLatin1String("--thread 1"))); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); diff --git a/debuggers/lldb/CMakeLists.txt b/debuggers/lldb/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/debuggers/lldb/CMakeLists.txt @@ -0,0 +1,78 @@ +project(lldb) +add_definitions(-DTRANSLATION_DOMAIN=\"kdevlldb\") + +set(kdevlldb_SRCS + lldbdebugger.cpp + lldbcommand.cpp + debugsession.cpp + controllers/breakpointcontroller.cpp + controllers/variablecontroller.cpp + controllers/variable.cpp + controllers/framestackmodel.cpp + widgets/lldbconfigpage.cpp + lldblauncher.cpp +) + +set(kdevlldb_UI + widgets/lldbconfigpage.ui +) + +ki18n_wrap_ui(kdevlldb_SRCS ${kdevlldb_UI}) +qt5_add_resources(kdevlldb_SRCS kdevlldb.qrc) + +# common code used by plugin and unit test +add_library(kdevlldb_static STATIC ${kdevlldb_SRCS}) +target_link_libraries(kdevlldb_static + PUBLIC + kdevdebuggercommon + KDev::Debugger + KDev::Interfaces + PRIVATE + Qt5::Core + Qt5::Gui + KF5::KIOWidgets + KDev::Shell +) + +# The actual plugin +kdevplatform_add_plugin(kdevlldb JSON kdevlldb.json SOURCES debuggerplugin.cpp) +target_link_libraries(kdevlldb + PUBLIC + kdevlldb_static + KDev::Interfaces + KDev::Language + KDev::Debugger + KDev::OutputView + KDev::Project + KDev::Util + KF5::TextEditor + PRIVATE + KDev::Sublime +) + +# Unit tests +set(test_lldb_SRCS + unittests/test_lldb.cpp + unittests/testhelper.cpp +) +ecm_add_test(${test_lldb_SRCS} + TEST_NAME test_lldb + LINK_LIBRARIES + kdevlldb_static + Qt5::Core + Qt5::Test + KDev::Shell + KDev::Interfaces + KDev::Project + KDev::Debugger + KDev::Tests + KDev::Util + KF5::KIOWidgets + KF5::TextEditor + KF5::Parts +) +if (HAVE_PATH_WITH_SPACES_TEST) + set_target_properties(test_lldb PROPERTIES COMPILE_FLAGS "-DHAVE_PATH_WITH_SPACES_TEST") +endif() + +add_subdirectory(unittests/debugees) diff --git a/debuggers/lldb/Messages.sh b/debuggers/lldb/Messages.sh new file mode 100644 --- /dev/null +++ b/debuggers/lldb/Messages.sh @@ -0,0 +1,4 @@ +#!/bin/sh +$EXTRACTRC `find . -name \*.rc` `find . -name \*.ui` >>rc.cpp +find . \( -name \*.cc -o -name \*.cpp -o -name \*.h \) -print0 | xargs -0 $XGETTEXT -o $podir/kdevlldb.pot +rm -f rc.cpp diff --git a/debuggers/lldb/TODO.txt b/debuggers/lldb/TODO.txt new file mode 100644 --- /dev/null +++ b/debuggers/lldb/TODO.txt @@ -0,0 +1,121 @@ +- [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 + + Check if lldb supports unicode correctly + * 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 + +- Clean up extra actions provided by gdb/lldb plugins + - Add lldb actions for attach to process and examine core file + +- Remove duplicate breakpoint: look at breakpointController()->setDeleteDuplicateBreakpoints(true) + +- LLDB data formatter for Qt types +- Data formatter for lldb + * if this works in MI? + * print static member? + +- Show application exit reason in the Debug View + +- New config page options + * Inherit environment for inferior (related settings: inherit-env) + +- Clean up tool views + * register views + + controller + + tool view + * disassembly widget + * memory view + +- [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)") + +- 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 + + -break-enable has no effect + * breakpoint hit doesn't generate corresponding breakpoint-modified notification + (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 + * sliently stop when attaching to process, which confuses the controller + * [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 + * [PATCHED] -data-disassemble start and end address parameter doesn't accept expressions + * -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 + * [SUBMITED] -stack-list-locals shows empty list + - https://llvm.org/bugs/show_bug.cgi?id=28621 + * -data-list-register-values output format doesn't conform to spec + * 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, which also causes break on start not function + - 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/breakpointcontroller.h b/debuggers/lldb/controllers/breakpointcontroller.h new file mode 100644 --- /dev/null +++ b/debuggers/lldb/controllers/breakpointcontroller.h @@ -0,0 +1,43 @@ +/* + * LLDB Breakpoint Controller + * 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_BREAKPOINTCONTROLLER_H +#define LLDB_BREAKPOINTCONTROLLER_H + +#include "mibreakpointcontroller.h" + +namespace KDevMI { namespace LLDB { + +class DebugSession; +class BreakpointController : public MIBreakpointController +{ + Q_OBJECT + +public: + BreakpointController(DebugSession *parent); +private: +}; + +} // end of namespace LLDB +} // end of namespace KDevMI + +#endif // LLDB_BREAKPOINTCONTROLLER_H diff --git a/debuggers/lldb/controllers/breakpointcontroller.cpp b/debuggers/lldb/controllers/breakpointcontroller.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/controllers/breakpointcontroller.cpp @@ -0,0 +1,32 @@ +/* + * LLDB Breakpoint Controller + * 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 "breakpointcontroller.h" + +#include "debugsession.h" + +using namespace KDevMI::LLDB; + +BreakpointController::BreakpointController(DebugSession* parent) + : MIBreakpointController(parent) +{ +} diff --git a/debuggers/lldb/controllers/framestackmodel.h b/debuggers/lldb/controllers/framestackmodel.h new file mode 100644 --- /dev/null +++ b/debuggers/lldb/controllers/framestackmodel.h @@ -0,0 +1,60 @@ +/* + * LLDB-specific implementation of frame stack model + * 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_FRAMESTACKMODEL_H +#define LLDB_FRAMESTACKMODEL_H + +#include "miframestackmodel.h" + +namespace KDevMI { + +namespace MI { +struct AsyncRecord; +} + +namespace LLDB { + +class DebugSession; +class LldbFrameStackModel : public MIFrameStackModel +{ + Q_OBJECT +public: + LldbFrameStackModel(DebugSession* session); + + DebugSession* session(); + +protected: + void fetchThreads() override; + +private Q_SLOTS: + void inferiorStopped(const MI::AsyncRecord& r); + +private: + void handleThreadInfo(const MI::ResultRecord& r); + + int stoppedAtThread; +}; + +} // end of namespace LLDB +} // end of namespace KDevMI + +#endif // LLDB_FRAMESTACKMODEL_H diff --git a/debuggers/lldb/controllers/framestackmodel.cpp b/debuggers/lldb/controllers/framestackmodel.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/controllers/framestackmodel.cpp @@ -0,0 +1,118 @@ +/* + * LLDB-specific implementation of frame stack model + * 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 "framestackmodel.h" + +#include "debuglog.h" +#include "debugsession.h" +#include "mi/micommand.h" + +#include + +namespace { + +QString getFunctionOrAddress(const KDevMI::MI::Value &frame) +{ + if (frame.hasField("func")) + return frame["func"].literal(); + else + return frame["addr"].literal(); +} + +} + +using namespace KDevMI::LLDB; +using namespace KDevMI::MI; + +LldbFrameStackModel::LldbFrameStackModel(DebugSession *session) + : MIFrameStackModel(session) + , stoppedAtThread(-1) +{ + connect(session, &DebugSession::inferiorStopped, this, &LldbFrameStackModel::inferiorStopped); +} + +DebugSession* LldbFrameStackModel::session() +{ + return static_cast(FrameStackModel::session()); +} + +void LldbFrameStackModel::inferiorStopped(const MI::AsyncRecord& r) +{ + if (session()->debuggerStateIsOn(s_shuttingDown)) return; + + if (r.hasField("thread-id")) { + stoppedAtThread = r["thread-id"].toInt(); + } +} + +void LldbFrameStackModel::fetchThreads() +{ + // TODO: preliminary test shows there might be a bug in lldb-mi + // that's causing std::logic_error when executing -thread-info with + // more than one threads. Find a workaround for this (and report bug + // if it truely is). + session()->addCommand(ThreadInfo, "", this, &LldbFrameStackModel::handleThreadInfo); +} + +void LldbFrameStackModel::handleThreadInfo(const ResultRecord& r) +{ + const Value& threads = r["threads"]; + + QList threadsList; + for (int gidx = 0; gidx != threads.size(); ++gidx) { + FrameStackModel::ThreadItem i; + const Value & threadMI = threads[gidx]; + i.nr = threadMI["id"].toInt(); + if (threadMI["state"].literal() == "stopped") { + // lldb-mi returns multiple frame entry for each thread + // so can't directly use threadMI["frame"] + auto &th = dynamic_cast(threadMI); + Value *topFrame = nullptr; + for (auto res : th.results) { + if (res->variable == "frame") { + if (!topFrame || (*res->value)["level"].toInt() < (*topFrame)["level"].toInt()) { + topFrame = res->value; + } + } + } + i.name = getFunctionOrAddress(*topFrame); + } else { + i.name = i18n("(running)"); + } + threadsList << i; + } + setThreads(threadsList); + if (r.hasField("current-thread-id")) { + int currentThreadId = r["current-thread-id"].toInt(); + + setCurrentThread(currentThreadId); + + if (session()->hasCrashed()) { + setCrashedThreadIndex(currentThreadId); + } + } + // lldb-mi doesn't have current-thread-id field. Use the thread-id field when inferiorStopped + if (stoppedAtThread != -1) { + setCurrentThread(stoppedAtThread); + } + stoppedAtThread = -1; +} diff --git a/debuggers/lldb/controllers/variable.h b/debuggers/lldb/controllers/variable.h new file mode 100644 --- /dev/null +++ b/debuggers/lldb/controllers/variable.h @@ -0,0 +1,56 @@ +/* + * 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); + + // For testing + using KDevelop::Variable::childCount; + using KDevelop::Variable::child; + +protected: + void formatChanged() override; +}; + +} // end of namespace LLDB +} // end of namespace KDevMI + +#endif // LLDB_VARIABLE_H diff --git a/debuggers/lldb/controllers/variable.cpp b/debuggers/lldb/controllers/variable.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/controllers/variable.cpp @@ -0,0 +1,75 @@ +/* + * 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" + +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::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); + } + } + }); + } + } +} diff --git a/debuggers/lldb/controllers/variablecontroller.h b/debuggers/lldb/controllers/variablecontroller.h new file mode 100644 --- /dev/null +++ b/debuggers/lldb/controllers/variablecontroller.h @@ -0,0 +1,50 @@ +/* + * LLDB-specific variable controller implementation + * 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_VARIABLECONTROLLER_H +#define LLDB_VARIABLECONTROLLER_H + +#include "mivariablecontroller.h" +#include "variable.h" + +namespace KDevMI { namespace LLDB { + +class DebugSession; +class VariableController : public MIVariableController +{ + Q_OBJECT + +public: + VariableController(DebugSession* parent); + + void update() override; + LldbVariable* createVariable(KDevelop::TreeModel* model, KDevelop::TreeItem* parent, + const QString& expression, + const QString& display = "") override; +private: + DebugSession* debugSession() const; +}; + +} // end of namespace LLDB +} // end of namespace KDevMI + +#endif // LLDB_VARIABLECONTROLLER_H diff --git a/debuggers/lldb/controllers/variablecontroller.cpp b/debuggers/lldb/controllers/variablecontroller.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/controllers/variablecontroller.cpp @@ -0,0 +1,66 @@ +/* + * LLDB-specific variable controller implementation + * 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 "variablecontroller.h" + +#include "debugsession.h" +#include "debuglog.h" +#include "mi/micommand.h" + +#include + +using namespace KDevelop; +using namespace KDevMI::LLDB; + +VariableController::VariableController(DebugSession *parent) + : MIVariableController(parent) +{ +} + +DebugSession *VariableController::debugSession() const +{ + return static_cast(const_cast(QObject::parent())); +} + +LldbVariable* VariableController::createVariable(TreeModel* model, TreeItem* parent, + const QString& expression, const QString& display) +{ + return new LldbVariable(debugSession(), model, parent, expression, display); +} + +void VariableController::update() +{ + qCDebug(DEBUGGERLLDB) << "autoUpdate =" << autoUpdate(); + if (autoUpdate() & UpdateWatches) { + variableCollection()->watches()->reinstall(); + } + + if (autoUpdate() & UpdateLocals) { + updateLocals(); + } + + if ((autoUpdate() & UpdateLocals) || + ((autoUpdate() & UpdateWatches) && variableCollection()->watches()->childCount() > 0)) + { + debugSession()->updateAllVariables(); + } +} diff --git a/debuggers/lldb/debuggerplugin.h b/debuggers/lldb/debuggerplugin.h new file mode 100644 --- /dev/null +++ b/debuggers/lldb/debuggerplugin.h @@ -0,0 +1,66 @@ +/* + * 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 . + * + */ + +#ifndef LLDB_DEBUGGERPLUGIN_H +#define LLDB_DEBUGGERPLUGIN_H + +#include "midebuggerplugin.h" + +#include "debugsession.h" +#include "widgets/debuggerconsoleview.h" +#include "widgets/disassemblewidget.h" + +namespace KDevMI { namespace LLDB { + +class NonInterruptDebuggerConsoleView : public DebuggerConsoleView +{ +public: + NonInterruptDebuggerConsoleView(MIDebuggerPlugin *plugin, QWidget *parent = nullptr) + : DebuggerConsoleView(plugin, parent) + { + setShowInterrupt(false); + } +}; + +class LldbDebuggerPlugin : public MIDebuggerPlugin +{ + Q_OBJECT + +public: + friend class KDevMI::LLDB::DebugSession; + + LldbDebuggerPlugin(QObject *parent, const QVariantList & = QVariantList()); + ~LldbDebuggerPlugin() override; + + DebugSession *createSession() override; + void unloadToolviews() override; + void setupToolviews() override; + +private: + DebuggerToolFactory *m_consoleFactory; + DebuggerToolFactory *m_disassembleFactory; +}; + +} // end of namespace LLDB +} // end of namespace KDevMI + +#endif // LLDB_DEBUGGERPLUGIN_H diff --git a/debuggers/lldb/debuggerplugin.cpp b/debuggers/lldb/debuggerplugin.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/debuggerplugin.cpp @@ -0,0 +1,100 @@ +/* + * 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 "debuggerplugin.h" + +#include "debuglog.h" +#include "lldblauncher.h" +#include "widgets/lldbconfigpage.h" +#include "widgets/debuggerconsoleview.h" + +#include +#include +#include +#include +#include + +#include + +using namespace KDevMI::LLDB; + +K_PLUGIN_FACTORY_WITH_JSON(LldbDebuggerFactory, "kdevlldb.json", registerPlugin(); ) + +LldbDebuggerPlugin::LldbDebuggerPlugin(QObject *parent, const QVariantList &) + : MIDebuggerPlugin("kdevlldb", parent) + , m_consoleFactory(nullptr) + , m_disassembleFactory(nullptr) +{ + setXMLFile("kdevlldbui.rc"); + + auto plugins = core()->pluginController()->allPluginsForExtension("org.kdevelop.IExecutePlugin"); + for (auto plugin : plugins) { + auto iexec = plugin->extension(); + Q_ASSERT(iexec); + + auto type = core()->runController()->launchConfigurationTypeForId(iexec->nativeAppConfigTypeId()); + Q_ASSERT(type); + type->addLauncher(new LldbLauncher(this, iexec)); + } +} + +void LldbDebuggerPlugin::setupToolviews() +{ + m_consoleFactory = new DebuggerToolFactory(this, + "org.kdevelop.debugger.LldbConsole", Qt::BottomDockWidgetArea); + core()->uiController()->addToolView(i18n("LLDB Console"), m_consoleFactory); + /* + m_disassembleFactory = new DebuggerToolFactory(this, + "org.kdevelop.debugger.LldbDisassemble", Qt::BottomDockWidgetArea); + core()->uiController()->addToolView(i18n("LLDB Disassemble/Register"), m_disassembleFactory); + */ +} + +void LldbDebuggerPlugin::unloadToolviews() +{ + if (m_consoleFactory) { + qCDebug(DEBUGGERLLDB) << "Removing toolview"; + core()->uiController()->removeToolView(m_consoleFactory); + m_consoleFactory = nullptr; + } + /* + core()->uiController()->removeToolView(m_disassembleFactory); + core()->uiController()->removeToolView(memoryviewerfactory); + */ +} + +LldbDebuggerPlugin::~LldbDebuggerPlugin() +{ +} + +DebugSession* LldbDebuggerPlugin::createSession() +{ + DebugSession *session = new DebugSession(this); + core()->debugController()->addSession(session); + connect(session, &DebugSession::showMessage, this, &LldbDebuggerPlugin::showStatusMessage); + connect(session, &DebugSession::reset, this, &LldbDebuggerPlugin::reset); + connect(session, &DebugSession::raiseDebuggerConsoleViews, + this, &LldbDebuggerPlugin::raiseDebuggerConsoleViews); + return session; +} + +#include "debuggerplugin.moc" diff --git a/debuggers/lldb/debugsession.h b/debuggers/lldb/debugsession.h new file mode 100644 --- /dev/null +++ b/debuggers/lldb/debugsession.h @@ -0,0 +1,91 @@ +/* + * 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 . + * + */ + +#ifndef LLDB_DEBUGSESSION_H +#define LLDB_DEBUGSESSION_H + +#include "midebugsession.h" + +#include "controllers/breakpointcontroller.h" +#include "controllers/framestackmodel.h" +#include "controllers/variablecontroller.h" +#include "lldbdebugger.h" + +namespace KDevelop { +class ILaunchConfiguration; +} + +namespace KDevMI { + +namespace MI { +struct ResultRecord; +} + +namespace LLDB { + +class LldbDebuggerPlugin; +class DebugSession : public MIDebugSession +{ + Q_OBJECT +public: + explicit DebugSession(LldbDebuggerPlugin *plugin = nullptr); + ~DebugSession() override; + + BreakpointController * breakpointController() const override; + VariableController * variableController() const override; + LldbFrameStackModel * frameStackModel() const override; + + MI::MICommand *createCommand(MI::CommandType type, const QString &arguments, + MI::CommandFlags flags) const override; + + void updateAllVariables(); + +public Q_SLOTS: + void interruptDebugger() override; + +protected: + LldbDebugger *createDebugger() const override; + void initializeDebugger() override; + bool execInferior(KDevelop::ILaunchConfiguration *cfg, IExecutePlugin *iexec, + const QString &executable) override; + + void configure(KDevelop::ILaunchConfiguration *cfg, IExecutePlugin *iexec); + + void ensureDebuggerListening() override; + + void setupToolviews(); + void unloadToolviews(); + +private Q_SLOTS: + void handleFileExecAndSymbols(const MI::ResultRecord& r); + void handleTargetSelect(const MI::ResultRecord& r); + +private: + BreakpointController *m_breakpointController; + VariableController *m_variableController; + LldbFrameStackModel *m_frameStackModel; +}; + +} // end of namespace GDB +} // end of namespace KDevMI + +#endif // LLDB_DEBUGSESSION_H diff --git a/debuggers/lldb/debugsession.cpp b/debuggers/lldb/debugsession.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/debugsession.cpp @@ -0,0 +1,298 @@ +/* + * 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 +#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; + +QString doubleQuoteArg(QString arg) +{ + arg.replace("\"", "\\\""); + return '"' + arg + '"'; +} + +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(); +} + +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::initializeDebugger() +{ + //addCommand(MI::EnableTimings, "yes"); + + // TODO: lldb data formatter + /* + QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, + "kdevlldb/printers/lldbinit"); + if (!fileName.isEmpty()) { + QFileInfo fileInfo(fileName); + QString quotedPrintersPath = fileInfo.dir().path() + .replace('\\', "\\\\") + .replace('"', "\\\""); + queueCmd(new MICommand(MI::NonMI, + QString("python sys.path.insert(0, \"%0\")").arg(quotedPrintersPath))); + queueCmd(new MICommand(MI::NonMI, "source " + fileName)); + } + */ + + // 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 *) +{ + // Read Configuration values + KConfigGroup grp = cfg->config(); + QUrl configLldbScript = grp.readEntry(Config::LldbConfigScriptEntry, QUrl()); + + // 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); + + // custom config script + if (configLldbScript.isValid()) { + addCommand(MI::NonMI, "command source -s TRUE " + KShell::quoteArg(configLldbScript.toLocalFile())); + } + + qCDebug(DEBUGGERGDB) << "Per inferior configuration done"; +} + +bool DebugSession::execInferior(ILaunchConfiguration *cfg, IExecutePlugin *iexec, + const QString &executable) +{ + qCDebug(DEBUGGERGDB) << "Executing inferior"; + + // debugger specific config + configure(cfg, iexec); + + // config that can't be placed in configure() + KConfigGroup grp = cfg->config(); + + QString filesymbols = doubleQuoteArg(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 " + doubleQuoteArg(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(doubleQuoteArg(remoteDir))); + addCommand(MI::NonMI, QStringLiteral("platform put-file %0 %1") + .arg(doubleQuoteArg(executable), doubleQuoteArg(remoteExe))); + } else { + addCommand(MI::FileExecAndSymbols, filesymbols, + this, &DebugSession::handleFileExecAndSymbols, + CmdHandlesError); + } + + raiseEvent(connected_to_program); + + // 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(), doubleQuoteArg(it.value()))); + } + // actually using lldb command 'settings set target.env-vars' which accepts multiple values + addCommand(GdbSet, "environment " + vars.join(" ")); + + addCommand(new SentinelCommand([this, remoteDebugging]() { + breakpointController()->initSendBreakpoints(); + + 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. + // FIXME: 'process launch' doesn't provide thread-group-started notification which MIDebugSession + // relies on to know the inferior has been started + QPointer guarded_this(this); + addCommand(MI::NonMI, + QStringLiteral("process launch --stdin %0 --stdout %0 --stderr %0").arg(m_tty->getSlave()), + [guarded_this](const MI::ResultRecord &r) { + qCDebug(DEBUGGERLLDB) << "process launched:" << r.reason; + if (guarded_this) + guarded_this->setDebuggerStateOff(s_appNotStarted | s_programExited); + }, + CmdMaybeStartsRunning); + } else { + // what is the expected behavior for using external terminal when doing remote debugging? + addCommand(MI::ExecRun, QString(), CmdMaybeStartsRunning); + } + }, CmdMaybeStartsRunning)); + return true; +} + +void DebugSession::interruptDebugger() +{ + // lldb always uses async mode and prompt is always available. + // no need to interrupt + return; +} + +void DebugSession::ensureDebuggerListening() +{ + // lldb always uses async mode and prompt is always available. + // no need to interrupt + // 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. + return; +} + +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() +{ + 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); + } +} diff --git a/debuggers/lldb/kdevlldb.json b/debuggers/lldb/kdevlldb.json new file mode 100644 --- /dev/null +++ b/debuggers/lldb/kdevlldb.json @@ -0,0 +1,44 @@ +{ + "KPlugin": { + "Category": "Debugging", + "Description": "This plugin provides a frontend for LLDB, a next generation, high-performance C/Obj-C/C++ debugger.", + "Description[x-test]": "xxThis plugin provides a frontend for LLDB, a next generation, high-performance C/Obj-C/C++ debugger.xx", + "Description[zh_CN]": "此插件提供了 LLDB 前端,LLDB 是新一代、高性能的 C、Obj-C、C++ 调试器。", + "Icon": "debugger", + "Id": "kdevlldb", + "License": "GPL", + "Name": "LLDB Support", + "Name[bs]": "LLDB podrška", + "Name[ca@valencia]": "Implementació del LLDB", + "Name[ca]": "Implementació del LLDB", + "Name[cs]": "Podpora LLDB", + "Name[de]": "Unterstützung für LLDB", + "Name[es]": "Implementación de LLDB", + "Name[fi]": "LLDB-tuki", + "Name[gl]": "Integración de LLDB", + "Name[hu]": "LLDB támogatás", + "Name[it]": "Supporto LLDB", + "Name[nl]": "Ondersteuning voor LLDB", + "Name[pl]": "Obsługa LLDB", + "Name[pt]": "Suporte ao LLDB", + "Name[pt_BR]": "Suporte ao LLDB", + "Name[sk]": "Podpora LLDB", + "Name[sl]": "Podpora za LLDB", + "Name[sv]": "LLDB-stöd", + "Name[tr]": "LLDB Desteği", + "Name[uk]": "Підтримка LLDB", + "Name[x-test]": "xxLLDB Supportxx", + "Name[zh_CN]": "LLDB 支持", + "ServiceTypes": [ + "KDevelop/Plugin" + ] + }, + "X-KDevelop-Category": "Global", + "X-KDevelop-IRequired": [ + "org.kdevelop.IExecutePlugin" + ], + "X-KDevelop-Interfaces": [ + "org.kdevelop.IStatus" + ], + "X-KDevelop-Mode": "GUI" +} diff --git a/debuggers/lldb/kdevlldb.qrc b/debuggers/lldb/kdevlldb.qrc new file mode 100644 --- /dev/null +++ b/debuggers/lldb/kdevlldb.qrc @@ -0,0 +1,6 @@ + + + + kdevlldbui.rc + + diff --git a/debuggers/lldb/kdevlldbui.rc b/debuggers/lldb/kdevlldbui.rc new file mode 100644 --- /dev/null +++ b/debuggers/lldb/kdevlldbui.rc @@ -0,0 +1,22 @@ + + + + + Run + + + + + + + + + + + Debugger Toolbar + + + + + + diff --git a/debuggers/lldb/lldbcommand.h b/debuggers/lldb/lldbcommand.h new file mode 100644 --- /dev/null +++ b/debuggers/lldb/lldbcommand.h @@ -0,0 +1,54 @@ +/* + * LLDB specific version of MI command + * 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 LLDBCOMMAND_H +#define LLDBCOMMAND_H + +#include "mi/micommand.h" + +namespace KDevMI { namespace LLDB { + +/** + * LLDB specific version of MICommand, when LLDB-MI implements all + * needed mi command, this class would be no longer needed. + */ +class DebugSession; +class LldbCommand : public MI::MICommand +{ +protected: + LldbCommand(MI::CommandType type, const QString& arguments = QString(), + MI::CommandFlags flags = 0); + friend class KDevMI::LLDB::DebugSession; +public: + ~LldbCommand(); + + QString cmdToSend() override; + QString miCommand() const override; + +private: + QString overrideCmd; +}; + +} // end of namespace LLDB +} // end of namespace KDevMI + +#endif // LLDBCOMMAND_H diff --git a/debuggers/lldb/lldbcommand.cpp b/debuggers/lldb/lldbcommand.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/lldbcommand.cpp @@ -0,0 +1,229 @@ +/* + * LLDB specific version of MI command + * 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 "lldbcommand.h" + +using namespace KDevMI::LLDB; +using namespace KDevMI::MI; + +LldbCommand::LldbCommand(CommandType type, const QString& arguments, CommandFlags flags) + : MICommand(type, arguments, flags) +{ + +} + +LldbCommand::~LldbCommand() +{ +} + +QString LldbCommand::miCommand() const +{ + if (!overrideCmd.isEmpty()) { + return overrideCmd; + } + + QString command; + bool isMI = false; + + // TODO: find alternatives to the following command which are not supported in lldb-mi + switch(type()) { + case BreakCommands: + command = ""; + break; + case BreakInfo: + command = ""; + break; + case BreakInsert: // in lldb-mi, '-f' must be the last option switch right before location + command = "break-insert"; + isMI = true; + break; + case BreakList: + command = ""; + break; + case BreakWatch: + command = "break set var"; + break; + + case DataListChangedRegisters: + command = "data-list-changed-registers"; + break; + case DataReadMemory: // not implemented, deprecated + command = "data-read-memory"; + break; + case DataWriteRegisterVariables: + command = "data-write-register-values"; + break; + + case EnableTimings: + command = "enable-timings"; + break; + + case EnvironmentDirectory: + command = "environment-directory"; + break; + case EnvironmentPath: + command = "environment-path"; + break; + case EnvironmentPwd: + command = "environment-pwd"; + break; + + case ExecUntil: + // TODO: write test case for this + command = "thread until"; + break; + + case FileExecFile: + command = "file-exec-file";//"exec-file" + break; + case FileListExecSourceFile: + command = "file-list-exec-source-file"; + break; + case FileListExecSourceFiles: + command = "file-list-exec-source-files"; + break; + case FileSymbolFile: + command = "file-symbol-file";//"symbol-file" + break; + + case GdbVersion: + command = "gdb-version";//"show version" + break; + + case InferiorTtyShow: + command = "inferior-tty-show"; + break; + + case SignalHandle: + command = "process handle"; + break; + + case TargetDisconnect: + command = "target-disconnect";//"disconnect" + break; + case TargetDownload: + command = "target-download"; + break; + + case ThreadListIds: + command = "thread-list-ids"; + break; + case ThreadSelect: + command = "thread-select"; + break; + + case TraceFind: + command = "trace-find"; + break; + case TraceStart: + command = "trace-start"; + break; + case TraceStop: + command = "trace-stop"; + break; + + case VarInfoNumChildren: + command = "var-info-num-children"; + break; + case VarInfoType: + command = "var-info-type"; + break; + case VarSetFrozen: + command = "var-set-frozen"; + break; + case VarShowFormat: + command = "var-show-format"; + break; + default: + return MICommand::miCommand(); + } + + if (isMI) { + command.prepend('-'); + } + return command; +} + +QString LldbCommand::cmdToSend() +{ + switch (type()) { + // -gdb-set is only partially implemented + case GdbSet: { + QString env_name = "environment "; + QString disassembly_flavor = "disassembly-flavor "; + if (command_.startsWith(env_name)) { + command_ = command_.mid(env_name.length()); + overrideCmd = "settings set target.env-vars"; + } else if (command_.startsWith(disassembly_flavor)) { + command_ = command_.mid(disassembly_flavor.length()); + overrideCmd = "settings set target.x86-disassembly-flavor"; + } + break; + } + // find the postion to insert '-f' + case BreakInsert: { + if (!overrideCmd.isEmpty()) { + // already done + break; + } + int p = command_.length() - 1; + bool quoted = false; + if (command_[p] == '"') { + quoted = true; // should always be the case + } + --p; + for (; p >= 0; --p) { + // find next '"' or ' ' + if (quoted) { + if (command_[p] == '"' && (p == 0 || command_[p-1] != '\\')) + break; + } else { + if (command_[p] == ' ') + break; + } + } + if (p < 0) p = 0; // this means the command is malformated, we proceed anyway. + + // move other switches like '-d' '-c' into miCommand part + overrideCmd = miCommand() + " " + command_.left(p); + command_ = "-f " + command_.mid(p, command_.length()); + break; + } + case BreakWatch: + if (command_.startsWith("-r ")) { + command_ = "-w read " + command_.mid(3); + } else if (command_.startsWith("-a ")) { + command_ = "-w read_write " + command_.mid(3); + } + break; + case StackListArguments: + // some times when adding the command, the current frame is invalid, + // but is valid at sending time + if (command_.endsWith("-1 -1")) { + command_.replace("-1 -1", QStringLiteral("%1 %1").arg(frame())); + } + break; + default: + break; + } + return MICommand::cmdToSend(); +} diff --git a/debuggers/lldb/lldbdebugger.h b/debuggers/lldb/lldbdebugger.h new file mode 100644 --- /dev/null +++ b/debuggers/lldb/lldbdebugger.h @@ -0,0 +1,46 @@ +/* + * Low level LLDB interface + * 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 LLDBDEBUGGER_H +#define LLDBDEBUGGER_H + +#include "midebugger.h" + +namespace KDevMI { namespace LLDB { + +class LldbDebugger : public MIDebugger +{ + Q_OBJECT +public: + explicit LldbDebugger(QObject* parent = 0); + ~LldbDebugger() override; + + bool start(KConfigGroup& config, const QStringList& extraArguments = {}) override; + +private: + bool checkVersion(); +}; + +} // end of namespace LLDB +} // end of namespace KDevMI + +#endif // LLDBDEBUGGER_H diff --git a/debuggers/lldb/lldbdebugger.cpp b/debuggers/lldb/lldbdebugger.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/lldbdebugger.cpp @@ -0,0 +1,142 @@ +/* + * Low level LLDB interface. + * 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 "lldbdebugger.h" + +#include "dbgglobal.h" +#include "debuglog.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace KDevelop; +using namespace KDevMI::LLDB; +using namespace KDevMI::MI; + +LldbDebugger::LldbDebugger(QObject* parent) + : MIDebugger(parent) +{ +} + +LldbDebugger::~LldbDebugger() +{ +} + +bool LldbDebugger::start(KConfigGroup& config, const QStringList& extraArguments) +{ + // Get path to executable + QUrl lldbUrl = config.readEntry(Config::LldbExecutableEntry, QUrl()); + if (!lldbUrl.isValid() || !lldbUrl.isLocalFile()) { + debuggerBinary_ = "lldb-mi"; + } else { + debuggerBinary_ = lldbUrl.toLocalFile(); + } + + if (!checkVersion()) { + return false; + } + + // Get arguments + QStringList arguments = extraArguments; + //arguments << "-quiet"; + arguments.append(KShell::splitArgs(config.readEntry(Config::LldbArgumentsEntry, QString()))); + + // Get environment + const EnvironmentGroupList egl(config.config()); + const auto &envs = egl.variables(config.readEntry(Config::LldbEnvironmentEntry, egl.defaultGroup())); + QProcessEnvironment processEnv; + if (config.readEntry(Config::LldbInheritSystemEnvEntry, true)) { + processEnv = QProcessEnvironment::systemEnvironment(); + } + for (auto it = envs.begin(), ite = envs.end(); it != ite; ++it) { + processEnv.insert(it.key(), it.value()); + } + + // Start! + process_->setProcessEnvironment(processEnv); + process_->setProgram(debuggerBinary_, arguments); + process_->start(); + + qCDebug(DEBUGGERLLDB) << "Starting LLDB with command" << debuggerBinary_ + ' ' + arguments.join(' '); + qCDebug(DEBUGGERLLDB) << "LLDB process pid:" << process_->pid(); + emit userCommandOutput(debuggerBinary_ + ' ' + arguments.join(' ') + '\n'); + + return true; +} + +bool LldbDebugger::checkVersion() +{ + KProcess process; + process.setProgram(debuggerBinary_, {"--versionLong"}); + process.setOutputChannelMode(KProcess::MergedChannels); + process.start(); + process.waitForFinished(5000); + auto output = QString::fromLatin1(process.readAll()); + qCDebug(DEBUGGERLLDB) << output; + + QRegularExpression rx("^Version: (\\d+).(\\d+).(\\d+).(\\d+)$", QRegularExpression::MultilineOption); + auto match = rx.match(output); + int version[] = {0, 0, 0, 0}; + if (match.hasMatch()) { + for (int i = 0; i != 4; ++i) { + version[i] = match.captured(i+1).toInt(); + } + } + + // minimal version is 1.0.0.9 + bool ok = true; + const int min_ver[] = {1, 0, 0, 9}; + for (int i = 0; i < 4; ++i) { + if (version[i] < min_ver[i]) { + ok = false; + break; + } else if (version[i] > min_ver[i]) { + ok = true; + break; + } + } + + if (!ok) { + if (!qobject_cast(qApp)) { + //for unittest + qFatal("You need a graphical application."); + } + + KMessageBox::error( + qApp->activeWindow(), + i18n("You need lldb-mi 1.0.0.9 or higher.
" + "You are using: %1", output), + i18n("LLDB Error")); + } + + return ok; +} diff --git a/debuggers/lldb/lldblauncher.h b/debuggers/lldb/lldblauncher.h new file mode 100644 --- /dev/null +++ b/debuggers/lldb/lldblauncher.h @@ -0,0 +1,52 @@ +/* + * 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 . + * + */ + +#ifndef LLDBLAUNCHER_H +#define LLDBLAUNCHER_H + +#include + +class IExecutePlugin; +namespace KDevMI { namespace LLDB { + +class LldbDebuggerPlugin; +class LldbLauncher : public KDevelop::ILauncher +{ +public: + LldbLauncher(LldbDebuggerPlugin *plugin, IExecutePlugin *iexec); + QList configPages() const override; + QString description() const override; + QString id() override; + QString name() const override; + KJob* start(const QString &launchMode, KDevelop::ILaunchConfiguration *cfg) override; + QStringList supportedModes() const override; + +private: + QList m_factoryList; + LldbDebuggerPlugin *m_plugin; + IExecutePlugin *m_iexec; +}; + +} // end of namespace LLDB +} // end of namespace KDevMI + +#endif // LLDBLAUNCHER_H diff --git a/debuggers/lldb/lldblauncher.cpp b/debuggers/lldb/lldblauncher.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/lldblauncher.cpp @@ -0,0 +1,105 @@ +/* + * 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 "lldblauncher.h" + +#include "debuggerplugin.h" +#include "debuglog.h" +#include "midebugjobs.h" +#include "widgets/lldbconfigpage.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace KDevelop; +using namespace KDevMI; +using namespace KDevMI::LLDB; + +KDevMI::LLDB::LldbLauncher::LldbLauncher(LldbDebuggerPlugin *plugin, IExecutePlugin *iexec) + : m_plugin(plugin) + , m_iexec(iexec) +{ + m_factoryList << new LldbConfigPageFactory(); +} + +QString LldbLauncher::id() +{ + return QStringLiteral("lldb"); +} + +QString LldbLauncher::name() const +{ + return i18n("LLDB"); +} + +QString LldbLauncher::description() const +{ + return i18n("Debug a native application in LLDB"); +} + +QStringList LldbLauncher::supportedModes() const +{ + return {"debug"}; +} + +QList< KDevelop::LaunchConfigurationPageFactory * > LldbLauncher::configPages() const +{ + return m_factoryList; +} + +KJob *LldbLauncher::start(const QString &launchMode, KDevelop::ILaunchConfiguration *cfg) +{ + qCDebug(DEBUGGERLLDB) << "LldbLauncher: starting debugging"; + if (!cfg) { + qCWarning(DEBUGGERLLDB) << "LldbLauncher: can't start with null configuration"; + return nullptr; + } + + if (launchMode == "debug") { + if (ICore::self()->debugController()->currentSession()) { + auto ans = KMessageBox::warningYesNo(qApp->activeWindow(), + i18n("A program is already being debugged. Do you want to abort the " + "currently running debug session and continue with the launch?")); + if (ans == KMessageBox::No) + return nullptr; + } + + QList l; + auto depJob = m_iexec->dependencyJob(cfg); + if (depJob) + l << depJob; + l << new MIDebugJob(m_plugin, cfg, m_iexec); + return new ExecuteCompositeJob(ICore::self()->runController(), l); + } + + qCWarning(DEBUGGERLLDB) << "Unknown launch mode" << launchMode << "for config:" << cfg->name(); + return nullptr; +} diff --git a/debuggers/lldb/unittests/debugees/CMakeLists.txt b/debuggers/lldb/unittests/debugees/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/CMakeLists.txt @@ -0,0 +1,21 @@ +add_debuggable_executable(lldb_debugee SRCS debugee.cpp) +add_debuggable_executable(lldb_debugeeslow SRCS debugeeslow.cpp) +add_debuggable_executable(lldb_debugeecrash SRCS debugeecrash.cpp) +add_debuggable_executable(lldb_debugeerecursion SRCS debugeerecursion.cpp) +add_debuggable_executable(lldb_debugeespace SRCS "debugee space.cpp") +add_debuggable_executable(lldb_debugeemultilocbreakpoint SRCS debugeemultilocbreakpoint.cpp) +add_debuggable_executable(lldb_debugeemultiplebreakpoint SRCS debugeemultiplebreakpoint.cpp) +add_debuggable_executable(lldb_debugeeechoenv SRCS debugeeechoenv.cpp) + +add_debuggable_executable(lldb_debugeethreads SRCS debugeethreads.cpp) +target_link_libraries(lldb_debugeethreads Qt5::Core) + +add_debuggable_executable(lldb_debugeeqt SRCS debugeeqt.cpp) +target_link_libraries(lldb_debugeeqt Qt5::Core) + +add_debuggable_executable(lldb_debugeeexception SRCS debugeeexception.cpp) +kde_target_enable_exceptions(lldb_debugeeexception PRIVATE) + +if (HAVE_PATH_WITH_SPACES_TEST) + add_subdirectory("path with space") +endif() diff --git a/debuggers/lldb/unittests/debugees/debugee space.cpp b/debuggers/lldb/unittests/debugees/debugee space.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/debugee space.cpp @@ -0,0 +1,23 @@ +/* + Copyright 2009 Niko Sams + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include + +int main() { + std::cout << "Hello, world!" << std::endl; + return 0; +} diff --git a/debuggers/lldb/unittests/debugees/debugee.cpp b/debuggers/lldb/unittests/debugees/debugee.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/debugee.cpp @@ -0,0 +1,41 @@ +/* + Copyright 2009 Niko Sams + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include +struct testStruct { int a; int b; int c; }; +void noop() {} +void foo() { + static int i=0; + ++i; int j = i; + noop(); + noop(); +} +int main(int argc, char **argv) { + std::cout << "Hello, world!" << std::endl; + foo(); + foo(); + (void)argc;(void)argv; + const char *x = "Hello"; + std::cout << x << std::endl; + + testStruct ts; + ts.a = 0; + ts.b = 1; + ts.c = 2; + ts.a++; + return 0; +} diff --git a/debuggers/lldb/unittests/debugees/debugeecrash.cpp b/debuggers/lldb/unittests/debugees/debugeecrash.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/debugeecrash.cpp @@ -0,0 +1,31 @@ +/* + Copyright 2009 Niko Sams + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +void foo() { + int j = 0; + j++; + j++; + int *i=0; + *i = 0; +} +int main() { + std::cout << "Hello, world!" << std::endl; + foo(); + return 0; +} diff --git a/debuggers/lldb/unittests/debugees/debugeeechoenv.cpp b/debuggers/lldb/unittests/debugees/debugeeechoenv.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/debugeeechoenv.cpp @@ -0,0 +1,40 @@ +/* + * 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 +#include + +int main() { + char * value = std::getenv("VariableA"); + if (value) { + std::cout << value << std::endl; + } else { + std::cout << "Not found!" << std::endl; + } + + value = std::getenv("VariableB"); + if (value) { + std::cout << value << std::endl; + } else { + std::cout << "Not found!" << std::endl; + } + return 0; +} diff --git a/debuggers/lldb/unittests/debugees/debugeeexception.cpp b/debuggers/lldb/unittests/debugees/debugeeexception.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/debugeeexception.cpp @@ -0,0 +1,37 @@ +/* + Copyright 2012 Niko Sams + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +void foo() +{ + int i = 20; + throw i; +} + +int main () +{ + try + { + foo(); + } + catch (int e) + { + std::cout << "An exception occurred. Exception Nr. " << e << std::endl; + } + return 0; +} diff --git a/debuggers/lldb/unittests/debugees/debugeemultilocbreakpoint.cpp b/debuggers/lldb/unittests/debugees/debugeemultilocbreakpoint.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/debugeemultilocbreakpoint.cpp @@ -0,0 +1,35 @@ +/* + Copyright 2012 Niko Sams + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +inline int aPlusB(int a, int b) { + return a+b; +} + +inline int aPlusB(int a) { + return a+1; +} + +int main() +{ + int test1 = aPlusB(1, 1); + (void)test1; + int test4 = aPlusB(2); + (void)test4; + + return 0; +} diff --git a/debuggers/lldb/unittests/debugees/debugeemultiplebreakpoint.cpp b/debuggers/lldb/unittests/debugees/debugeemultiplebreakpoint.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/debugeemultiplebreakpoint.cpp @@ -0,0 +1,53 @@ +/* + Copyright 2013 Vlas Puhov + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include + +class Str2 { + public: void print(void) { + std::cout << "Str2\n"; + } +}; + +class Str { + public: Str() { + S="str\n"; + } + public: void print(void) { + std::cout << S; + } + private: std::string S; +}; + + +void Print ( Str2& c, Str& s, bool b ) +{ + if(b){ + c.print(); + s.print(); + } +} + +int main ( void ) +{ + Str2 c; + Str s; + Print ( c, s, true ); + return 1; +} diff --git a/debuggers/lldb/unittests/debugees/debugeeqt.cpp b/debuggers/lldb/unittests/debugees/debugeeqt.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/debugeeqt.cpp @@ -0,0 +1,31 @@ +/* + Copyright 2011 Niko Sams + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include +#include +#include + +int main() +{ + qDebug(); + for(int i=0; i<10; ++i) { + QString x("foobar"); + x += QString::number(i); + qDebug() << x; + } + return 0; +} diff --git a/debuggers/lldb/unittests/debugees/debugeerecursion.cpp b/debuggers/lldb/unittests/debugees/debugeerecursion.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/debugeerecursion.cpp @@ -0,0 +1,32 @@ +/* + Copyright 2009 Niko Sams + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include + +void foo() { + static int i=0; + ++i; + if (i < 298) + foo(); + else + std::cout << "Hello, world!" << std::endl; +} + +int main() { + foo(); + return 0; +} diff --git a/debuggers/lldb/unittests/debugees/debugeeslow.cpp b/debuggers/lldb/unittests/debugees/debugeeslow.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/debugeeslow.cpp @@ -0,0 +1,37 @@ +/* + Copyright 2009 Niko Sams + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include + +void foo() { + static int i=0; + ++i; + std::cout << i << std::endl; + sleep(2); +} + +int main() { + std::cout << "Hello, world!" << std::endl; + for (int i=0; i<3; i++) { + foo(); + } + + return 0; +} diff --git a/debuggers/lldb/unittests/debugees/debugeethreads.cpp b/debuggers/lldb/unittests/debugees/debugeethreads.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/debugeethreads.cpp @@ -0,0 +1,42 @@ +/* + Copyright 2009 Niko Sams + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include +#include +#include + +class TestThread : public QThread +{ +public: + void run() override { + sleep(1); + std::cout << "Hello, world!" << std::endl; + } +}; + + +int main(int /*argc*/, char **/*argv*/) { + TestThread t1; + TestThread t2; + TestThread t3; + t1.start(); + t2.start(); + t3.start(); + usleep(500000); + usleep(600000); + return 0; +} diff --git a/debuggers/lldb/unittests/debugees/path with space/CMakeLists.txt b/debuggers/lldb/unittests/debugees/path with space/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/path with space/CMakeLists.txt @@ -0,0 +1,2 @@ +add_debuggable_executable(lldb_spacedebugee SRCS "spacedebugee.cpp") +target_link_libraries(lldb_spacedebugee Qt5::Core) diff --git a/debuggers/lldb/unittests/debugees/path with space/spacedebugee.cpp b/debuggers/lldb/unittests/debugees/path with space/spacedebugee.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/debugees/path with space/spacedebugee.cpp @@ -0,0 +1,31 @@ +/* + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + if (QCoreApplication::applicationDirPath() != QDir::currentPath()) { + qWarning() << "Unexpected app dir path: " << QCoreApplication::applicationDirPath() << QDir::currentPath(); + return 1; + } + + return 0; +} diff --git a/debuggers/gdb/unittests/test_gdb.h b/debuggers/lldb/unittests/test_lldb.h copy from debuggers/gdb/unittests/test_gdb.h copy to debuggers/lldb/unittests/test_lldb.h --- a/debuggers/gdb/unittests/test_gdb.h +++ b/debuggers/lldb/unittests/test_lldb.h @@ -1,45 +1,56 @@ /* - Copyright 2009 Niko Sams - Copyright 2013 Vlas Puhov + * Unit tests for LLDB debugger plugin + * 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 . + * + */ - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. +#ifndef LLDBTEST_H +#define LLDBTEST_H - This library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#ifndef GDBTEST_H -#define GDBTEST_H - -#include #include +class IExecutePlugin; + namespace KDevelop { +class BreakpointModel; class TestCore; +class Variable; +class VariableCollection; } -namespace KDevMI { -namespace GDB { -class GdbTest : public QObject +namespace KDevMI { namespace LLDB { + +class LldbTest : public QObject { Q_OBJECT - -private Q_SLOTS: +private slots: void initTestCase(); void cleanupTestCase(); + void init(); + void cleanup(); + + void testStdout(); + void testEnvironmentSet(); - void testStdOut(); void testBreakpoint(); + void testBreakOnStart(); void testDisableBreakpoint(); void testChangeLocationBreakpoint(); void testDeleteBreakpoint(); @@ -56,14 +67,30 @@ void testInsertBreakpointWhileRunningMultiple(); void testInsertBreakpointFunctionName(); void testManualBreakpoint(); + void testInsertAndRemoveBreakpointWhileRunning(); + void testPickupManuallyInsertedBreakpoint(); + void testPickupManuallyInsertedBreakpointOnlyOnce(); + void testBreakpointWithSpaceInPath(); + void testBreakpointDisabledOnStart(); + void testMultipleLocationsBreakpoint(); + void testMultipleBreakpoint(); + void testRegularExpressionBreakpoint(); + void testChangeBreakpointWhileRunning(); + + void testCatchpoint(); + void testShowStepInSource(); + void testStack(); void testStackFetchMore(); void testStackDeactivateAndActive(); void testStackSwitchThread(); + void testAttach(); - void testManualAttach(); + void testRemoteDebugging(); + void testCoreFile(); + void testVariablesLocals(); void testVariablesLocalsStruct(); void testVariablesWatches(); @@ -73,39 +100,36 @@ void testVariablesStartSecondSession(); void testVariablesSwitchFrame(); void testVariablesQuicklySwitchFrame(); + void testSwitchFrameLldbConsole(); + void testSegfaultDebugee(); - void testSwitchFrameGdbConsole(); - void testInsertAndRemoveBreakpointWhileRunning(); + void testCommandOrderFastStepping(); - void testPickupManuallyInsertedBreakpoint(); - void testPickupManuallyInsertedBreakpointOnlyOnce(); - void testPickupCatchThrowOnlyOnce(); - void testRunGdbScript(); - void testRemoteDebug(); - void testRemoteDebugInsertBreakpoint(); - void testRemoteDebugInsertBreakpointPickupOnlyOnce(); - void testBreakpointWithSpaceInPath(); - void testBreakpointDisabledOnStart(); - void testCatchpoint(); - void testThreadAndFrameInfo(); - void parseBug304730(); - void testMultipleLocationsBreakpoint(); + + void testRunLldbScript(); + void testBug301287(); - void testMultipleBreakpoint(); - void testRegularExpressionBreakpoint(); - void testChangeBreakpointWhileRunning(); + void testDebugInExternalTerminal(); - void testPathWithSpace(); + + void testSpecialPath(); private: - bool waitForState(DebugSession *session, - KDevelop::IDebugSession::DebuggerState state, - const char *file, int line, - bool waitForIdle = false); - IExecutePlugin* m_iface; + // convenient access methods + KDevelop::BreakpointModel *breakpoints(); + + KDevelop::VariableCollection *variableCollection(); + KDevelop::Variable *watchVariableAt(int i); + KDevelop::Variable *localVariableAt(int i); + +private: + KDevelop::TestCore *m_core; + IExecutePlugin *m_iface; + + QString m_debugeeFileName; }; -} // end of namespace GDB +} // end of namespace LLDB } // end of namespace KDevMI -#endif // GDBTEST_H +#endif // LLDBTEST_H diff --git a/debuggers/gdb/unittests/test_gdb.cpp b/debuggers/lldb/unittests/test_lldb.cpp copy from debuggers/gdb/unittests/test_gdb.cpp copy to debuggers/lldb/unittests/test_lldb.cpp --- a/debuggers/gdb/unittests/test_gdb.cpp +++ b/debuggers/lldb/unittests/test_lldb.cpp @@ -1,135 +1,99 @@ /* + * Unit tests for LLDB debugger plugin Copyright 2009 Niko Sams Copyright 2013 Vlas Puhov - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#include "test_gdb.h" - + * 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 "test_lldb.h" + +#include "controllers/framestackmodel.h" #include "debugsession.h" -#include "gdbframestackmodel.h" -#include "mi/micommand.h" -#include "mi/milexer.h" -#include "mi/miparser.h" +#include "testhelper.h" #include -#include #include -#include -#include -#include #include #include #include #include #include #include -#include +#include +#include #include -#include #include -#include -#include -#include #include -#include #include -#include +#include +#include #include +#include -using KDevelop::AutoTestShell; - -namespace KDevMI { namespace GDB { +#define WAIT_FOR_STATE(session, state) \ + do { if (!KDevMI::LLDB::waitForState((session), (state), __FILE__, __LINE__)) return; } while (0) -QUrl findExecutable(const QString& name) -{ - QFileInfo info(qApp->applicationDirPath() + "/unittests/" + name); - Q_ASSERT(info.exists()); - Q_ASSERT(info.isExecutable()); - return QUrl::fromLocalFile(info.canonicalFilePath()); -} +#define WAIT_FOR_STATE_AND_IDLE(session, state) \ + do { if (!KDevMI::LLDB::waitForState((session), (state), __FILE__, __LINE__, true)) return; } while (0) -QString findSourceFile(const QString& name) -{ - QFileInfo info(QFileInfo(__FILE__).dir().absoluteFilePath(name)); - Q_ASSERT(info.exists()); - return info.canonicalFilePath(); -} +#define WAIT_FOR_A_WHILE(session, ms) \ + do { if (!KDevMI::LLDB::waitForAWhile((session), (ms), __FILE__, __LINE__)) return; } while (0) -static bool isAttachForbidden(const char * file, int line) -{ - // if on linux, ensure we can actually attach - QFile canRun("/proc/sys/kernel/yama/ptrace_scope"); - if (canRun.exists()) { - if (!canRun.open(QIODevice::ReadOnly)) { - QTest::qFail("Something is wrong: /proc/sys/kernel/yama/ptrace_scope exists but cannot be read", file, line); - return true; - } - if (canRun.read(1).toInt() != 0) { - QTest::qSkip("ptrace attaching not allowed, skipping test. To enable it, set /proc/sys/kernel/yama/ptrace_scope to 0.", file, line); - return true; - } - } +#define WAIT_FOR(session, condition) \ + do { \ + KDevMI::LLDB::TestWaiter w((session), #condition, __FILE__, __LINE__); \ + while (w.waitUnless((condition))) /* nothing */ ; \ + } while(0) - return false; -} +#define COMPARE_DATA(index, expected) \ + do { if (!KDevMI::LLDB::compareData((index), (expected), __FILE__, __LINE__)) return; } while (0) #define SKIP_IF_ATTACH_FORBIDDEN() \ do { \ if (isAttachForbidden(__FILE__, __LINE__)) \ return; \ } while(0) -void GdbTest::initTestCase() -{ - AutoTestShell::init(); - KDevelop::TestCore::initialize(KDevelop::Core::NoUi); - - m_iface = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IExecutePlugin", "kdevexecute")->extension(); - Q_ASSERT(m_iface); -} +#define findSourceFile(name) \ + findSourceFile(__FILE__, (name)) -void GdbTest::cleanupTestCase() -{ - KDevelop::TestCore::shutdown(); -} +using namespace KDevelop; +using namespace KDevMI::LLDB; -void GdbTest::init() +namespace { +class WritableEnvironmentGroupList : public EnvironmentGroupList { - //remove all breakpoints - so we can set our own in the test - KConfigGroup breakpoints = KSharedConfig::openConfig()->group("breakpoints"); - breakpoints.writeEntry("number", 0); - breakpoints.sync(); - - KDevelop::BreakpointModel* m = KDevelop::ICore::self()->debugController()->breakpointModel(); - m->removeRows(0, m->rowCount()); +public: + explicit WritableEnvironmentGroupList(KConfig* config) : EnvironmentGroupList(config) {} - KDevelop::VariableCollection *vc = KDevelop::ICore::self()->debugController()->variableCollection(); - for (int i=0; i < vc->watches()->childCount(); ++i) { - delete vc->watches()->child(i); - } - vc->watches()->clear(); -} + using EnvironmentGroupList::variables; + using EnvironmentGroupList::saveSettings; + using EnvironmentGroupList::removeGroup; +}; -class TestLaunchConfiguration : public KDevelop::ILaunchConfiguration +class TestLaunchConfiguration : public ILaunchConfiguration { public: - TestLaunchConfiguration(const QUrl& executable = findExecutable("debugee"), + TestLaunchConfiguration(const QUrl& executable = findExecutable("lldb_debugee"), const QUrl& workingDirectory = QUrl()) { qDebug() << "FIND" << executable; c = new KConfig(); @@ -147,31 +111,34 @@ QString name() const override { return QString("Test-Launch"); } KDevelop::IProject* project() const override { return 0; } KDevelop::LaunchConfigurationType* type() const override { return 0; } + + KConfig *rootConfig() { return c; } private: KConfigGroup cfg; KConfig *c; }; -class TestFrameStackModel : public GdbFrameStackModel +class TestFrameStackModel : public LldbFrameStackModel { public: TestFrameStackModel(DebugSession* session) - : GdbFrameStackModel(session), fetchFramesCalled(0), fetchThreadsCalled(0) {} + : LldbFrameStackModel(session), fetchFramesCalled(0), fetchThreadsCalled(0) {} - int fetchFramesCalled; - int fetchThreadsCalled; void fetchFrames(int threadNumber, int from, int to) override { fetchFramesCalled++; - GdbFrameStackModel::fetchFrames(threadNumber, from, to); + LldbFrameStackModel::fetchFrames(threadNumber, from, to); } void fetchThreads() override { fetchThreadsCalled++; - GdbFrameStackModel::fetchThreads(); + LldbFrameStackModel::fetchThreads(); } + + int fetchFramesCalled; + int fetchThreadsCalled; }; class TestDebugSession : public DebugSession @@ -181,14 +148,10 @@ TestDebugSession() : DebugSession() { setSourceInitFile(false); - setAutoDisableASLR(false); m_frameStackModel = new TestFrameStackModel(this); KDevelop::ICore::self()->debugController()->addSession(this); } - QUrl url() { return currentUrl(); } - int line() { return currentLine(); } - TestFrameStackModel* frameStackModel() const override { return m_frameStackModel; @@ -198,126 +161,175 @@ TestFrameStackModel* m_frameStackModel; }; -class TestWaiter -{ -public: - TestWaiter(DebugSession * session_, const char * condition_, const char * file_, int line_) - : session(session_) - , condition(condition_) - , file(file_) - , line(line_) - { - stopWatch.start(); - } - - bool waitUnless(bool ok) - { - if (ok) { - qDebug() << "Condition " << condition << " reached in " << file << ':' << line; - return false; - } +} // end of anonymous namespace - if (stopWatch.elapsed() > 5000) { - QTest::qFail(qPrintable(QString("Timeout before reaching condition %0").arg(condition)), - file, line); - return false; - } - QTest::qWait(100); +BreakpointModel* LldbTest::breakpoints() +{ + return m_core->debugController()->breakpointModel(); +} - if (!session) { - QTest::qFail(qPrintable(QString("Session ended without reaching condition %0").arg(condition)), - file, line); - return false; - } +VariableCollection *LldbTest::variableCollection() +{ + return m_core->debugController()->variableCollection(); +} - return true; - } +Variable *LldbTest::watchVariableAt(int i) +{ + auto watchRoot = variableCollection()->indexForItem(variableCollection()->watches(), 0); + auto idx = variableCollection()->index(i, 0, watchRoot); + return dynamic_cast(variableCollection()->itemForIndex(idx)); +} -private: - QTime stopWatch; - QPointer session; - const char * condition; - const char * file; - int line; -}; +Variable *LldbTest::localVariableAt(int i) +{ + auto localRoot = variableCollection()->indexForItem(variableCollection()->locals(), 0); + auto idx = variableCollection()->index(i, 0, localRoot); + return dynamic_cast(variableCollection()->itemForIndex(idx)); +} -#define WAIT_FOR_STATE(session, state) \ - do { if (!waitForState((session), (state), __FILE__, __LINE__)) return; } while (0) +// Called before the first testfunction is executed +void LldbTest::initTestCase() +{ + AutoTestShell::init(); + m_core = TestCore::initialize(Core::NoUi); -#define WAIT_FOR_STATE_AND_IDLE(session, state) \ - do { if (!waitForState((session), (state), __FILE__, __LINE__, true)) return; } while (0) + m_iface = m_core->pluginController() + ->pluginForExtension("org.kdevelop.IExecutePlugin", "kdevexecute") + ->extension(); + Q_ASSERT(m_iface); -#define WAIT_FOR(session, condition) \ - do { \ - TestWaiter w((session), #condition, __FILE__, __LINE__); \ - while (w.waitUnless((condition))) /* nothing */ ; \ - } while(0) + m_debugeeFileName = findSourceFile("debugee.cpp"); +} -#define COMPARE_DATA(index, expected) \ - do { if(!compareData((index), (expected), __FILE__, __LINE__)) return; } while (0) +// Called after the last testfunction was executed +void LldbTest::cleanupTestCase() +{ + TestCore::shutdown(); +} -bool compareData(QModelIndex index, QString expected, const char *file, int line) +// Called before each testfunction is executed +void LldbTest::init() { - QString s = index.model()->data(index, Qt::DisplayRole).toString(); - if (s != expected) { - QTest::qFail(qPrintable(QString("'%0' didn't match expected '%1' in %2:%3") - .arg(s).arg(expected).arg(file).arg(line)), - file, line); - return false; + //remove all breakpoints - so we can set our own in the test + KConfigGroup bpCfg = KSharedConfig::openConfig()->group("breakpoints"); + bpCfg.writeEntry("number", 0); + bpCfg.sync(); + + breakpoints()->removeRows(0, breakpoints()->rowCount()); + + while (variableCollection()->watches()->childCount() > 0) { + auto var = watchVariableAt(0); + if (!var) break; + var->die(); } - return true; } -static const QString debugeeFileName = findSourceFile("debugee.cpp"); - -KDevelop::BreakpointModel* breakpoints() +void LldbTest::cleanup() { - return KDevelop::ICore::self()->debugController()->breakpointModel(); + // Called after every testfunction } -void GdbTest::testStdOut() +void LldbTest::testStdout() { TestDebugSession *session = new TestDebugSession; QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); TestLaunchConfiguration cfg; - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); - { - QCOMPARE(outputSpy.count(), 1); + QVERIFY(outputSpy.count() > 0); + + QStringList outputLines; + while (outputSpy.count() > 0) { + QList arguments = outputSpy.takeFirst(); + for (const auto &item : arguments) { + outputLines.append(item.toStringList()); + } + } + QCOMPARE(outputLines, QStringList() << "Hello, world!" + << "Hello"); +} + +void LldbTest::testEnvironmentSet() +{ + TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration cfg(findExecutable("lldb_debugeeechoenv")); + + cfg.config().writeEntry("EnvironmentGroup", "LldbTestGroup"); + + WritableEnvironmentGroupList envGroups(cfg.rootConfig()); + envGroups.removeGroup("LldbTestGroup"); + auto &envs = envGroups.variables("LldbTestGroup"); + envs["VariableA"] = "-A' \" complex --value"; + envs["VariableB"] = "-B' \" complex --value"; + envGroups.saveSettings(cfg.rootConfig()); + + QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); + + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); + + QVERIFY(outputSpy.count() > 0); + + QStringList outputLines; + while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); - QCOMPARE(arguments.count(), 1); - QCOMPARE(arguments.first().toStringList(), QStringList() << "Hello, world!" << "Hello"); + for (const auto &item : arguments) { + outputLines.append(item.toStringList()); + } } + QCOMPARE(outputLines, QStringList() << "-A' \" complex --value" + << "-B' \" complex --value"); } -void GdbTest::testBreakpoint() +void LldbTest::testBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); + KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); - session->startDebugging(&cfg, m_iface); - WAIT_FOR_STATE(session, DebugSession::PausedState); + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); + QCOMPARE(session->currentLine(), 29); + session->stepInto(); - WAIT_FOR_STATE(session, DebugSession::PausedState); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + session->stepInto(); - WAIT_FOR_STATE(session, DebugSession::PausedState); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); - QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); } -void GdbTest::testDisableBreakpoint() +void LldbTest::testBreakOnStart() +{ + QSKIP("SKipping... Pending breakpoints not work with the current version of lldb-mi"); + + TestDebugSession *session = new TestDebugSession; + + TestLaunchConfiguration cfg; + cfg.config().writeEntry(KDevMI::Config::BreakOnStartEntry, true); + + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 28); // line 28 is the start of main function in debugee.cpp + + session->run(); + WAIT_FOR_STATE(session, DebugSession::EndedState); +} + +void LldbTest::testDisableBreakpoint() { + QSKIP("Skipping... In lldb-mi -d flag has no effect when mixed with -f"); //Description: We must stop only on the third breakpoint int firstBreakLine=28; @@ -331,151 +343,162 @@ KDevelop::Breakpoint *b; - b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), firstBreakLine); + b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), firstBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //this is needed to emulate debug from GUI. If we are in edit mode, the debugSession doesn't exist. - KDevelop::ICore::self()->debugController()->breakpointModel()->blockSignals(true); - b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), secondBreakLine); + m_core->debugController()->breakpointModel()->blockSignals(true); + b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), secondBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //all disabled breakpoints were added - KDevelop::Breakpoint * thirdBreak = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), thirdBreakLine); - KDevelop::ICore::self()->debugController()->breakpointModel()->blockSignals(false); + auto *thirdBreak = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), + thirdBreakLine); + m_core->debugController()->breakpointModel()->blockSignals(false); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(session->currentLine(), thirdBreak->line()); //disable existing breakpoint thirdBreak->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //add another disabled breakpoint - b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), fourthBreakLine); - QTest::qWait(300); + b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), fourthBreakLine); + WAIT_FOR_A_WHILE(session, 300); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); - QTest::qWait(300); + WAIT_FOR_A_WHILE(session, 300); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testChangeLocationBreakpoint() +void LldbTest::testChangeLocationBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 27); + auto *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 27); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 27); + QCOMPARE(session->currentLine(), 27); - QTest::qWait(100); + WAIT_FOR_A_WHILE(session, 100); b->setLine(28); - QTest::qWait(100); + WAIT_FOR_A_WHILE(session, 100); session->run(); - QTest::qWait(100); + WAIT_FOR_A_WHILE(session, 100); WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 28); - QTest::qWait(500); - breakpoints()->setData(breakpoints()->index(0, KDevelop::Breakpoint::LocationColumn), QString(debugeeFileName+":30")); + QCOMPARE(session->currentLine(), 28); + WAIT_FOR_A_WHILE(session, 500); + breakpoints()->setData(breakpoints()->index(0, KDevelop::Breakpoint::LocationColumn), QString(m_debugeeFileName+":30")); QCOMPARE(b->line(), 29); - QTest::qWait(100); + WAIT_FOR_A_WHILE(session, 100); QCOMPARE(b->line(), 29); session->run(); - QTest::qWait(100); + WAIT_FOR_A_WHILE(session, 100); WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 29); + QCOMPARE(session->currentLine(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testDeleteBreakpoint() +void LldbTest::testDeleteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; QCOMPARE(breakpoints()->rowCount(), 0); //add breakpoint before startDebugging - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QCOMPARE(breakpoints()->rowCount(), 1); breakpoints()->removeRow(0); QCOMPARE(breakpoints()->rowCount(), 0); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 22); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testPendingBreakpoint() +void LldbTest::testPendingBreakpoint() { + QSKIP("Skipping... Pending breakpoint not work on lldb-mi"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); - KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("test_gdb.cpp")), 10); - QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); + auto * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("test_lldb.cpp")), 10); + QCOMPARE(b->state(), Breakpoint::NotStartedState); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(b->state(), KDevelop::Breakpoint::PendingState); + QCOMPARE(b->state(), Breakpoint::PendingState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testUpdateBreakpoint() +void LldbTest::testUpdateBreakpoint() { + // Description: user might insert breakpoints using lldb console. model should + // pick up the manually set breakpoint TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - // breakpoint 1: line 29 - KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); + // break at line 29 + auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); QCOMPARE(breakpoints()->rowCount(), 1); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); - // breakpoint 2: line 28 - //insert custom command as user might do it using GDB console - session->addCommand(new MI::UserCommand(MI::NonMI, "break "+debugeeFileName+":28")); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 29 - WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 28 session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop after step + + QCOMPARE(session->currentLine(), 23-1); // at the begining of foo():23: ++i; + + session->addUserCommand(QString("break set --file %1 --line %2").arg(m_debugeeFileName).arg(33)); + WAIT_FOR_A_WHILE(session, 20); + QCOMPARE(breakpoints()->rowCount(), 2); b = breakpoints()->breakpoint(1); - QCOMPARE(b->url(), QUrl::fromLocalFile(debugeeFileName)); - QCOMPARE(b->line(), 27); + QCOMPARE(b->url(), QUrl::fromLocalFile(m_debugeeFileName)); + QCOMPARE(b->line(), 33-1); + session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); // stop at line 29 + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 25 + + QCOMPARE(session->currentLine(), 33-1); + session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testIgnoreHitsBreakpoint() +void LldbTest::testIgnoreHitsBreakpoint() { + QSKIP("Skipping... lldb-mi doesn't provide breakpoint hit count update"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - KDevelop::Breakpoint * b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); + KDevelop::Breakpoint * b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); b1->setIgnoreHits(1); - KDevelop::Breakpoint * b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); + KDevelop::Breakpoint * b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 22); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b2->hitCount() == 1); @@ -487,368 +510,641 @@ WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testConditionBreakpoint() +void LldbTest::testConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 39); + auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 39); b->setCondition("x[0] == 'H'"); - b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 23); + b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 23); b->setCondition("i==2"); - b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); + b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR(session, session->state() == DebugSession::PausedState && session->line() == 24); + WAIT_FOR(session, session->state() == DebugSession::PausedState && session->currentLine() == 24); b->setCondition("i == 0"); - QTest::qWait(100); + WAIT_FOR_A_WHILE(session, 100); session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 23); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + QCOMPARE(session->currentLine(), 23); + session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 39); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + QCOMPARE(session->currentLine(), 39); + session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testBreakOnWriteBreakpoint() +void LldbTest::testBreakOnWriteBreakpoint() { + QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 24); + QCOMPARE(session->currentLine(), 24); breakpoints()->addWatchpoint("i"); - QTest::qWait(100); + WAIT_FOR_A_WHILE(session, 100); session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 23); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 23); session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 24); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testBreakOnWriteWithConditionBreakpoint() +void LldbTest::testBreakOnWriteWithConditionBreakpoint() { + QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 24); + QCOMPARE(session->currentLine(), 24); KDevelop::Breakpoint *b = breakpoints()->addWatchpoint("i"); b->setCondition("i==2"); QTest::qWait(100); session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 23); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 23); session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 24); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testBreakOnReadBreakpoint() +void LldbTest::testBreakOnReadBreakpoint() { - /* - test disabled because of gdb bug: http://sourceware.org/bugzilla/show_bug.cgi?id=10136 - + QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - KDevelop::Breakpoint *b = breakpoints()->addReadWatchpoint("foo::i"); + breakpoints()->addReadWatchpoint("foo::i"); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 23); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); - */ } -void GdbTest::testBreakOnReadBreakpoint2() +void LldbTest::testBreakOnReadBreakpoint2() { + QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 24); + QCOMPARE(session->currentLine(), 24); breakpoints()->addReadWatchpoint("i"); session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 22); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 22); session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 24); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testBreakOnAccessBreakpoint() +void LldbTest::testBreakOnAccessBreakpoint() { + QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 24); + QCOMPARE(session->currentLine(), 24); breakpoints()->addAccessWatchpoint("i"); session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 22); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 22); session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 23); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 23); session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 24); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testInsertBreakpointWhileRunning() +void LldbTest::testInsertBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; - TestLaunchConfiguration cfg(findExecutable("debugeeslow")); + TestLaunchConfiguration cfg(findExecutable("lldb_debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); - QTest::qWait(2000); + WAIT_FOR_A_WHILE(session, 2000); + qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); - QTest::qWait(500); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(500); - QCOMPARE(session->line(), 25); + WAIT_FOR_A_WHILE(session, 500); + + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + WAIT_FOR_A_WHILE(session, 500); + + QCOMPARE(session->currentLine(), 25); b->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testInsertBreakpointWhileRunningMultiple() +void LldbTest::testInsertBreakpointWhileRunningMultiple() { TestDebugSession *session = new TestDebugSession; - TestLaunchConfiguration cfg(findExecutable("debugeeslow")); + TestLaunchConfiguration cfg(findExecutable("lldb_debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); - QTest::qWait(2000); + WAIT_FOR_A_WHILE(session, 2000); + qDebug() << "adding breakpoint"; - KDevelop::Breakpoint *b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 24); - KDevelop::Breakpoint *b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); - QTest::qWait(500); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(500); - QCOMPARE(session->line(), 24); + auto b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 24); + auto b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); + + WAIT_FOR_A_WHILE(session, 500); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + WAIT_FOR_A_WHILE(session, 500); + QCOMPARE(session->currentLine(), 24); + session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(500); - QCOMPARE(session->line(), 25); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + WAIT_FOR_A_WHILE(session, 500); + QCOMPARE(session->currentLine(), 25); b1->setDeleted(); b2->setDeleted(); + session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testInsertBreakpointFunctionName() +void LldbTest::testInsertBreakpointFunctionName() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 27); + QCOMPARE(session->currentLine(), 27); + session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testManualBreakpoint() +void LldbTest::testManualBreakpoint() { + QSKIP("Skipping... lldb-mi output malformated response which breaks this"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 27); + QCOMPARE(session->currentLine(), 27); breakpoints()->removeRows(0, 1); - WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + WAIT_FOR_A_WHILE(session, 100); QCOMPARE(breakpoints()->rowCount(), 0); - session->addCommand(MI::NonMI, "break debugee.cpp:23"); - WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + session->addCommand(MI::NonMI, "break set --file debugee.cpp --line 23"); + WAIT_FOR_A_WHILE(session, 100); QCOMPARE(breakpoints()->rowCount(), 1); - KDevelop::Breakpoint* b = breakpoints()->breakpoint(0); + auto b = breakpoints()->breakpoint(0); QCOMPARE(b->line(), 22); - session->addCommand(MI::NonMI, "disable 2"); - session->addCommand(MI::NonMI, "condition 2 i == 1"); - session->addCommand(MI::NonMI, "ignore 2 1"); - WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + session->addCommand(MI::NonMI, "break disable 2"); + session->addCommand(MI::NonMI, "break modify -c 'i == 1' 2"); + session->addCommand(MI::NonMI, "break modify -i 1 2"); + WAIT_FOR_A_WHILE(session, 1000); QCOMPARE(b->enabled(), false); QCOMPARE(b->condition(), QString("i == 1")); QCOMPARE(b->ignoreHits(), 1); - session->addCommand(MI::NonMI, "delete 2"); - WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + session->addCommand(MI::NonMI, "break delete 2"); + WAIT_FOR_A_WHILE(session, 100); QCOMPARE(breakpoints()->rowCount(), 0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testShowStepInSource() +//Bug 201771 +void LldbTest::testInsertAndRemoveBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration cfg(findExecutable("lldb_debugeeslow")); - QSignalSpy showStepInSourceSpy(session, &TestDebugSession::showStepInSource); - - TestLaunchConfiguration cfg; + QString fileName = findSourceFile("debugeeslow.cpp"); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); - session->startDebugging(&cfg, m_iface); - WAIT_FOR_STATE(session, DebugSession::PausedState); - session->stepInto(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - session->stepInto(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - session->run(); - WAIT_FOR_STATE(session, DebugSession::EndedState); + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE(session, DebugSession::ActiveState); + WAIT_FOR_A_WHILE(session, 1000); - { - QCOMPARE(showStepInSourceSpy.count(), 3); - QList arguments = showStepInSourceSpy.takeFirst(); - QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); - QCOMPARE(arguments.at(1).toInt(), 29); + KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); + WAIT_FOR_A_WHILE(session, 200); // wait for feedback notification from lldb-mi + b->setDeleted(); - arguments = showStepInSourceSpy.takeFirst(); - QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); - QCOMPARE(arguments.at(1).toInt(), 22); - arguments = showStepInSourceSpy.takeFirst(); - QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); - QCOMPARE(arguments.at(1).toInt(), 23); - } + WAIT_FOR_A_WHILE(session, 3000); // give slow debugee extra time to run + WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testStack() +void LldbTest::testPickupManuallyInsertedBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - TestFrameStackModel *stackModel = session->frameStackModel(); - - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); + breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QModelIndex tIdx = stackModel->index(0,0); - QCOMPARE(stackModel->rowCount(QModelIndex()), 1); - QCOMPARE(stackModel->columnCount(QModelIndex()), 3); - COMPARE_DATA(tIdx, "#1 at foo"); + session->addCommand(MI::NonMI, "break set --file debugee.cpp --line 32"); + session->stepInto(); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(breakpoints()->breakpoints().count(), 2); + QCOMPARE(breakpoints()->rowCount(), 2); + KDevelop::Breakpoint *b = breakpoints()->breakpoint(1); + QVERIFY(b); + QCOMPARE(b->line(), 31); //we start with 0, gdb with 1 + QCOMPARE(b->url().fileName(), QString("debugee.cpp")); +} - QCOMPARE(stackModel->rowCount(tIdx), 2); - QCOMPARE(stackModel->columnCount(tIdx), 3); - COMPARE_DATA(tIdx.child(0, 0), "0"); - COMPARE_DATA(tIdx.child(0, 1), "foo"); - COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":23"); - COMPARE_DATA(tIdx.child(1, 0), "1"); - COMPARE_DATA(tIdx.child(1, 1), "main"); - COMPARE_DATA(tIdx.child(1, 2), debugeeFileName+":29"); +//Bug 270970 +void LldbTest::testPickupManuallyInsertedBreakpointOnlyOnce() +{ + TestDebugSession *session = new TestDebugSession; + + QString sourceFile = findExecutable("lldb_debugee").toLocalFile(); + //inject here, so it behaves similar like a command from .lldbinit + QTemporaryFile configScript; + configScript.open(); + configScript.write(QString("file %0\n").arg(sourceFile).toLocal8Bit()); + configScript.write(QString("break set --file %0 --line 32\n").arg(sourceFile).toLocal8Bit()); + configScript.close(); + + TestLaunchConfiguration cfg; + KConfigGroup grp = cfg.config(); + grp.writeEntry(Config::LldbConfigScriptEntry, QUrl::fromLocalFile(configScript.fileName())); + + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile("debugee.cpp"), 31); + + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + QCOMPARE(breakpoints()->breakpoints().count(), 1); + + session->run(); + WAIT_FOR_STATE(session, DebugSession::EndedState); +} + +void LldbTest::testBreakpointWithSpaceInPath() +{ + TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration cfg(findExecutable("lldb_debugeespace")); + + KConfigGroup grp = cfg.config(); + QString fileName = findSourceFile("debugee space.cpp"); + + KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 20); + QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); + + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + QCOMPARE(session->currentLine(), 20); + session->run(); + WAIT_FOR_STATE(session, DebugSession::EndedState); +} + +void LldbTest::testBreakpointDisabledOnStart() +{ + TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration cfg; + + auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 23); + b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); + b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 34); + b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); + + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + QCOMPARE(session->currentLine(), 29); + b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); + + session->run(); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 34); + + session->run(); + WAIT_FOR_STATE(session, DebugSession::EndedState); +} + +void LldbTest::testMultipleLocationsBreakpoint() +{ + TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration cfg(findExecutable("lldb_debugeemultilocbreakpoint")); + + breakpoints()->addCodeBreakpoint("aPlusB"); + + //TODO check if the additional location breakpoint is added + + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 19); + + session->run(); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 23); + + session->run(); + WAIT_FOR_STATE(session, DebugSession::EndedState); +} + +void LldbTest::testMultipleBreakpoint() +{ + TestDebugSession *session = new TestDebugSession; + + //there'll be about 3-4 breakpoints, but we treat it like one. + TestLaunchConfiguration c(findExecutable("lldb_debugeemultiplebreakpoint")); + auto b = breakpoints()->addCodeBreakpoint("debugeemultiplebreakpoint.cpp:52"); + + QVERIFY(session->startDebugging(&c, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + QCOMPARE(breakpoints()->breakpoints().count(), 1); + + b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); + session->run(); + WAIT_FOR_STATE(session, DebugSession::EndedState); +} + +void LldbTest::testRegularExpressionBreakpoint() +{ + QSKIP("Skipping... lldb has only one breakpoint for multiple locations" + " (and lldb-mi returns the first one), not support this yet"); + TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration c(findExecutable("lldb_debugeemultilocbreakpoint")); + + breakpoints()->addCodeBreakpoint("main"); + QVERIFY(session->startDebugging(&c, m_iface)); + + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + session->addCommand(MI::NonMI, "break set --func-regex .*aPl.*B"); + session->run(); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + QCOMPARE(breakpoints()->breakpoints().count(), 3); + + session->addCommand(MI::BreakDelete, ""); + session->run(); + WAIT_FOR_STATE(session, DebugSession::EndedState); +} + +void LldbTest::testChangeBreakpointWhileRunning() +{ + QSKIP("Skipping... lldb-mi command -break-enable doesn't enable breakpoint"); + TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration c(findExecutable("lldb_debugeeslow")); + + KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint("debugeeslow.cpp:25"); + QVERIFY(session->startDebugging(&c, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + QVERIFY(session->currentLine() >= 24 && session->currentLine() <= 26 ); + + session->run(); + WAIT_FOR_STATE(session, DebugSession::ActiveState); + qDebug() << "Disabling breakpoint"; + b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); + //to make one loop + WAIT_FOR_A_WHILE(session, 2500); + qDebug() << "Waiting for active"; + WAIT_FOR_STATE(session, DebugSession::ActiveState); + qDebug() << "Enabling breakpoint"; + + // Use native user command works, but not through -break-enable, which is triggered by setData + session->addCommand(MI::NonMI, "break enable"); + //b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); + + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); + + session->run(); + WAIT_FOR_STATE(session, DebugSession::EndedState); +} + +void LldbTest::testCatchpoint() +{ + TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration cfg(findExecutable("lldb_debugeeexception")); + + session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); + TestFrameStackModel* fsModel = session->frameStackModel(); + + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp")), 29); + + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + QCOMPARE(fsModel->currentFrame(), 0); + QCOMPARE(session->currentLine(), 29); + + session->addCommand(MI::NonMI, "break set -E c++"); + session->run(); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + const auto frames = fsModel->frames(fsModel->currentThread()); + QVERIFY(frames.size() >= 2); + // frame 0 is somewhere inside libstdc++ + QCOMPARE(frames[1].file, QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp"))); + QCOMPARE(frames[1].line, 22); + + QCOMPARE(breakpoints()->rowCount(),2); + QVERIFY(!breakpoints()->breakpoint(0)->location().isEmpty()); + QVERIFY(!breakpoints()->breakpoint(1)->location().isEmpty()); + + session->run(); + WAIT_FOR_STATE(session, DebugSession::EndedState); +} + +void LldbTest::testShowStepInSource() +{ + TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration cfg; + + QSignalSpy showStepInSourceSpy(session, &TestDebugSession::showStepInSource); + + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); + + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + session->stepInto(); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + session->stepInto(); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + session->run(); + WAIT_FOR_STATE(session, DebugSession::EndedState); + + { + QCOMPARE(showStepInSourceSpy.count(), 3); + QList arguments = showStepInSourceSpy.takeFirst(); + QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(m_debugeeFileName)); + QCOMPARE(arguments.at(1).toInt(), 29); + + arguments = showStepInSourceSpy.takeFirst(); + QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(m_debugeeFileName)); + QCOMPARE(arguments.at(1).toInt(), 22); + + arguments = showStepInSourceSpy.takeFirst(); + QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(m_debugeeFileName)); + QCOMPARE(arguments.at(1).toInt(), 23); + } +} + +void LldbTest::testStack() +{ + TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration cfg; + + TestFrameStackModel *stackModel = session->frameStackModel(); + + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); + + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + QModelIndex tIdx = stackModel->index(0,0); + QCOMPARE(stackModel->rowCount(QModelIndex()), 1); + QCOMPARE(stackModel->columnCount(QModelIndex()), 3); + COMPARE_DATA(tIdx, "#1 at foo()"); + + QCOMPARE(stackModel->rowCount(tIdx), 4); + QCOMPARE(stackModel->columnCount(tIdx), 3); + COMPARE_DATA(tIdx.child(0, 0), "0"); + COMPARE_DATA(tIdx.child(0, 1), "foo()"); + COMPARE_DATA(tIdx.child(0, 2), m_debugeeFileName+":23"); + COMPARE_DATA(tIdx.child(1, 0), "1"); + COMPARE_DATA(tIdx.child(1, 1), "main"); + COMPARE_DATA(tIdx.child(1, 2), m_debugeeFileName+":29"); + COMPARE_DATA(tIdx.child(2, 0), "2"); + COMPARE_DATA(tIdx.child(2, 1), "__libc_start_main"); + COMPARE_DATA(tIdx.child(3, 0), "3"); + COMPARE_DATA(tIdx.child(3, 1), "_start"); session->stepOut(); - WAIT_FOR_STATE(session, DebugSession::PausedState); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); - QCOMPARE(stackModel->rowCount(tIdx), 1); + QCOMPARE(stackModel->rowCount(tIdx), 3); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); - COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":30"); + COMPARE_DATA(tIdx.child(0, 2), m_debugeeFileName+":30"); + COMPARE_DATA(tIdx.child(1, 0), "1"); + COMPARE_DATA(tIdx.child(1, 1), "__libc_start_main"); + COMPARE_DATA(tIdx.child(2, 0), "2"); + COMPARE_DATA(tIdx.child(2, 1), "_start"); session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testStackFetchMore() +void LldbTest::testStackFetchMore() { TestDebugSession *session = new TestDebugSession; - TestLaunchConfiguration cfg(findExecutable("debugeerecursion")); + TestLaunchConfiguration cfg(findExecutable("lldb_debugeerecursion")); QString fileName = findSourceFile("debugeerecursion.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->frameStackModel()->fetchFramesCalled, 1); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); - COMPARE_DATA(tIdx, "#1 at foo"); + COMPARE_DATA(tIdx, "#1 at foo()"); QCOMPARE(stackModel->rowCount(tIdx), 21); COMPARE_DATA(tIdx.child(0, 0), "0"); - COMPARE_DATA(tIdx.child(0, 1), "foo"); + COMPARE_DATA(tIdx.child(0, 1), "foo()"); COMPARE_DATA(tIdx.child(0, 2), fileName+":26"); COMPARE_DATA(tIdx.child(1, 0), "1"); - COMPARE_DATA(tIdx.child(1, 1), "foo"); + COMPARE_DATA(tIdx.child(1, 1), "foo()"); COMPARE_DATA(tIdx.child(1, 2), fileName+":24"); COMPARE_DATA(tIdx.child(2, 0), "2"); - COMPARE_DATA(tIdx.child(2, 1), "foo"); + COMPARE_DATA(tIdx.child(2, 1), "foo()"); COMPARE_DATA(tIdx.child(2, 2), fileName+":24"); COMPARE_DATA(tIdx.child(19, 0), "19"); COMPARE_DATA(tIdx.child(20, 0), "20"); stackModel->fetchMoreFrames(); - QTest::qWait(200); + WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 2); QCOMPARE(stackModel->rowCount(tIdx), 41); COMPARE_DATA(tIdx.child(20, 0), "20"); @@ -858,7 +1154,7 @@ COMPARE_DATA(tIdx.child(40, 0), "40"); stackModel->fetchMoreFrames(); - QTest::qWait(200); + WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 3); QCOMPARE(stackModel->rowCount(tIdx), 121); COMPARE_DATA(tIdx.child(40, 0), "40"); @@ -868,57 +1164,66 @@ COMPARE_DATA(tIdx.child(120, 0), "120"); stackModel->fetchMoreFrames(); - QTest::qWait(200); + WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); COMPARE_DATA(tIdx.child(120, 0), "120"); COMPARE_DATA(tIdx.child(121, 0), "121"); COMPARE_DATA(tIdx.child(122, 0), "122"); + COMPARE_DATA(tIdx.child(298, 0), "298"); + COMPARE_DATA(tIdx.child(298, 1), "main"); + COMPARE_DATA(tIdx.child(298, 2), fileName+":30"); + COMPARE_DATA(tIdx.child(299, 0), "299"); + COMPARE_DATA(tIdx.child(299, 1), "__libc_start_main"); COMPARE_DATA(tIdx.child(300, 0), "300"); - COMPARE_DATA(tIdx.child(300, 1), "main"); - COMPARE_DATA(tIdx.child(300, 2), fileName+":30"); + COMPARE_DATA(tIdx.child(300, 1), "_start"); stackModel->fetchMoreFrames(); //nothing to fetch, we are at the end - QTest::qWait(200); + WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testStackDeactivateAndActive() +void LldbTest::testStackDeactivateAndActive() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); session->stepOut(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(200); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); - QCOMPARE(stackModel->rowCount(tIdx), 1); + QCOMPARE(stackModel->rowCount(tIdx), 3); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); - COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":30"); + COMPARE_DATA(tIdx.child(0, 2), m_debugeeFileName+":30"); + COMPARE_DATA(tIdx.child(1, 0), "1"); + COMPARE_DATA(tIdx.child(1, 1), "__libc_start_main"); + COMPARE_DATA(tIdx.child(2, 0), "2"); + COMPARE_DATA(tIdx.child(2, 1), "_start"); session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testStackSwitchThread() +void LldbTest::testStackSwitchThread() { + QSKIP("Skipping... lldb-mi crashes when break at a location with multiple threads running"); TestDebugSession *session = new TestDebugSession; - TestLaunchConfiguration cfg(findExecutable("debugeethreads")); + TestLaunchConfiguration cfg(findExecutable("lldb_debugeethreads")); QString fileName = findSourceFile("debugeethreads.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); @@ -939,85 +1244,86 @@ tIdx = stackModel->index(1,0); QVERIFY(stackModel->data(tIdx).toString().startsWith("#2 at ")); stackModel->setCurrentThread(2); - QTest::qWait(200); + WAIT_FOR_A_WHILE(session, 200); int rows = stackModel->rowCount(tIdx); QVERIFY(rows > 3); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testAttach() +void LldbTest::testAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile("debugeeslow.cpp"); KProcess debugeeProcess; - debugeeProcess << "nice" << findExecutable("debugeeslow").toLocalFile(); + debugeeProcess << "nice" << findExecutable("lldb_debugeeslow").toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); QTest::qWait(100); TestDebugSession *session = new TestDebugSession; session->attachToProcess(debugeeProcess.pid()); - WAIT_FOR_STATE(session, DebugSession::PausedState); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 34); - QTest::qWait(100); - session->run(); - QTest::qWait(2000); - WAIT_FOR_STATE(session, DebugSession::PausedState); - if (session->line() < 34 || session->line() < 35) { - QCOMPARE(session->line(), 34); - } + WAIT_FOR_A_WHILE(session, 100); + + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 35); + + // lldb-mi sliently stops when attaching to a process. Force it continue to run. + session->addCommand(MI::ExecContinue, QString(), MI::CmdMaybeStartsRunning); + WAIT_FOR_A_WHILE(session, 2000); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + QCOMPARE(session->currentLine(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testManualAttach() +void LldbTest::testRemoteDebugging() { - SKIP_IF_ATTACH_FORBIDDEN(); + KProcess gdbServer; + gdbServer << "lldb-server" << "gdbserver" << "*:1234"; + gdbServer.start(); + QVERIFY(gdbServer.waitForStarted()); - QString fileName = findSourceFile("debugeeslow.cpp"); + TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration cfg; - KProcess debugeeProcess; - debugeeProcess << "nice" << findExecutable("debugeeslow").toLocalFile(); - debugeeProcess.start(); - QVERIFY(debugeeProcess.waitForStarted()); + cfg.config().writeEntry(Config::LldbRemoteDebuggingEntry, true); + cfg.config().writeEntry(Config::LldbRemoteServerEntry, "localhost:1234"); + cfg.config().writeEntry(Config::LldbRemotePathEntry, "/tmp"); - TestDebugSession *session = new TestDebugSession; + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 34); - TestLaunchConfiguration cfg; - cfg.config().writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(findSourceFile("gdb_script_empty"))); QVERIFY(session->startDebugging(&cfg, m_iface)); - - session->addCommand(MI::NonMI, QString("attach %0").arg(debugeeProcess.pid())); - WAIT_FOR_STATE(session, DebugSession::PausedState); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); - QTest::qWait(2000); // give the slow inferior some extra time to run WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testCoreFile() +void LldbTest::testCoreFile() { + // TODO: support for coredumpctl + QFile f("core"); if (f.exists()) f.remove(); KProcess debugeeProcess; debugeeProcess.setOutputChannelMode(KProcess::MergedChannels); - debugeeProcess << "bash" << "-c" << "ulimit -c unlimited; " + findExecutable("debugeecrash").toLocalFile(); + debugeeProcess << "bash" << "-c" << "ulimit -c unlimited; " + findExecutable("lldb_debugeecrash").toLocalFile(); debugeeProcess.start(); debugeeProcess.waitForFinished(); qDebug() << debugeeProcess.readAll(); if (!QFile::exists("core")) { QSKIP("no core dump found, check your system configuration (see /proc/sys/kernel/core_pattern).", SkipSingle); } TestDebugSession *session = new TestDebugSession; - session->examineCoreFile(findExecutable("debugeecrash"), QUrl::fromLocalFile(QDir::currentPath()+"/core")); + session->examineCoreFile(findExecutable("lldb_debugeecrash"), QUrl::fromLocalFile(QDir::currentPath()+"/core")); TestFrameStackModel *stackModel = session->frameStackModel(); @@ -1032,50 +1338,47 @@ WAIT_FOR_STATE(session, DebugSession::EndedState); } - -KDevelop::VariableCollection *variableCollection() -{ - return KDevelop::ICore::self()->debugController()->variableCollection(); -} - -void GdbTest::testVariablesLocals() +void LldbTest::testVariablesLocals() { TestDebugSession *session = new TestDebugSession; - session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); - TestLaunchConfiguration cfg; - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); + session->variableController()->setAutoUpdate(IVariableController::UpdateLocals); + + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); + QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(1000); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(variableCollection()->rowCount(), 2); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); + QCOMPARE(variableCollection()->rowCount(i), 1); - COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); - COMPARE_DATA(variableCollection()->index(0, 1, i), "0"); - session->run(); - QTest::qWait(1000); - WAIT_FOR_STATE(session, DebugSession::PausedState); - COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); + COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); + + session->run(); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); + COMPARE_DATA(variableCollection()->index(0, 1, i), "2"); + session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testVariablesLocalsStruct() +void LldbTest::testVariablesLocalsStruct() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(1000); + WAIT_FOR_A_WHILE(session, 1000); QModelIndex i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); @@ -1092,7 +1395,7 @@ QModelIndex ts = variableCollection()->index(structIndex, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); - QTest::qWait(100); + WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); @@ -1102,37 +1405,37 @@ session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(1000); + WAIT_FOR_A_WHILE(session, 1000); COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testVariablesWatches() +void LldbTest::testVariablesWatches() { TestDebugSession *session = new TestDebugSession; - KDevelop::ICore::self()->debugController()->variableCollection()->variableWidgetShown(); - TestLaunchConfiguration cfg; - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); + m_core->debugController()->variableCollection()->variableWidgetShown(); + + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("ts"); - QTest::qWait(300); + WAIT_FOR_A_WHILE(session, 300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); - QTest::qWait(100); + WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); @@ -1142,212 +1445,210 @@ session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(100); + WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testVariablesWatchesQuotes() +void LldbTest::testVariablesWatchesQuotes() { TestDebugSession *session = new TestDebugSession; - session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); - TestLaunchConfiguration cfg; + session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); + const QString testString("test"); const QString quotedTestString("\"" + testString + "\""); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(quotedTestString); //just a constant string - QTest::qWait(300); + WAIT_FOR_A_WHILE(session, 3000); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), quotedTestString); - COMPARE_DATA(variableCollection()->index(0, 1, i), "[" + QString::number(testString.length() + 1) + "]"); + COMPARE_DATA(variableCollection()->index(0, 1, i), QStringLiteral("[%0]").arg(testString.length() + 1)); QModelIndex testStr = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, testStr), "..."); variableCollection()->expanded(testStr); - QTest::qWait(100); + WAIT_FOR_A_WHILE(session, 100); int len = testString.length(); for (int ind = 0; ind < len; ind++) { - COMPARE_DATA(variableCollection()->index(ind, 0, testStr), QString::number(ind)); + COMPARE_DATA(variableCollection()->index(ind, 0, testStr), QStringLiteral("[%0]").arg(ind)); QChar c = testString.at(ind); QString value = QString::number(c.toLatin1()) + " '" + c + "'"; COMPARE_DATA(variableCollection()->index(ind, 1, testStr), value); } - COMPARE_DATA(variableCollection()->index(len, 0, testStr), QString::number(len)); - COMPARE_DATA(variableCollection()->index(len, 1, testStr), "0 '\\000'"); + COMPARE_DATA(variableCollection()->index(len, 0, testStr), QStringLiteral("[%0]").arg(len)); + COMPARE_DATA(variableCollection()->index(len, 1, testStr), "0 '\\0'"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testVariablesWatchesTwoSessions() +void LldbTest::testVariablesWatchesTwoSessions() { TestDebugSession *session = new TestDebugSession; - session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); - TestLaunchConfiguration cfg; - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); + session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); + + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE(session, DebugSession::PausedState); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); variableCollection()->watches()->add("ts"); - QTest::qWait(300); + WAIT_FOR_A_WHILE(session, 300); QModelIndex ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); variableCollection()->expanded(ts); - QTest::qWait(100); + WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope QCOMPARE(variableCollection()->watches()->childCount(), 1); - KDevelop::Variable* v = dynamic_cast(variableCollection()->watches()->child(0)); + auto v = dynamic_cast(watchVariableAt(0)); QVERIFY(v); QVERIFY(!v->inScope()); QCOMPARE(v->childCount(), 3); - v = dynamic_cast(v->child(0)); + v = dynamic_cast(v->child(0)); QVERIFY(!v->inScope()); //start a second debug session session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(300); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(variableCollection()->watches()->childCount(), 1); ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); - v = dynamic_cast(variableCollection()->watches()->child(0)); + v = dynamic_cast(watchVariableAt(0)); QVERIFY(v); QVERIFY(v->inScope()); QCOMPARE(v->childCount(), 3); - - v = dynamic_cast(v->child(0)); + v = dynamic_cast(v->child(0)); QVERIFY(v->inScope()); - QCOMPARE(v->data(1, Qt::DisplayRole).toString(), QString::number(0)); + COMPARE_DATA(variableCollection()->indexForItem(v, 1), QString::number(0)); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope - v = dynamic_cast(variableCollection()->watches()->child(0)); + v = dynamic_cast(watchVariableAt(0)); QVERIFY(!v->inScope()); QVERIFY(!dynamic_cast(v->child(0))->inScope()); } -void GdbTest::testVariablesStopDebugger() +void LldbTest::testVariablesStopDebugger() { TestDebugSession *session = new TestDebugSession; - session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); - TestLaunchConfiguration cfg; - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); + session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); + + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE(session, DebugSession::PausedState); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stopDebugger(); - QTest::qWait(300); + WAIT_FOR_STATE(session, DebugSession::EndedState); } - -void GdbTest::testVariablesStartSecondSession() +void LldbTest::testVariablesStartSecondSession() { - QPointer session = new TestDebugSession; - session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); - + TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); + session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); + + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE(session, DebugSession::PausedState); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QPointer session2 = new TestDebugSession; - session2->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); + session = new TestDebugSession; + session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); - QVERIFY(session2->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE(session2, DebugSession::PausedState); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - session2->run(); - WAIT_FOR_STATE(session2, DebugSession::EndedState); + session->run(); + WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testVariablesSwitchFrame() +void LldbTest::testVariablesSwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); + TestFrameStackModel *stackModel = session->frameStackModel(); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(500); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); - COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); + COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); // only non-static variable works COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); - QTest::qWait(200); + WAIT_FOR_A_WHILE(session, 200); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); COMPARE_DATA(variableCollection()->index(2, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(2, 1, i), "1"); COMPARE_DATA(variableCollection()->index(3, 0, i), "argv"); breakpoints()->removeRow(0); + session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testVariablesQuicklySwitchFrame() +void LldbTest::testVariablesQuicklySwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); + TestFrameStackModel *stackModel = session->frameStackModel(); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(500); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); - COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); + COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); // only non-static variable works COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); - QTest::qWait(300); + WAIT_FOR_A_WHILE(session, 300); stackModel->setCurrentFrame(0); - QTest::qWait(1); + WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(1); - QTest::qWait(1); + WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(0); - QTest::qWait(1); + WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(1); - QTest::qWait(500); + WAIT_FOR_A_WHILE(session, 500); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); @@ -1364,506 +1665,106 @@ WAIT_FOR_STATE(session, DebugSession::EndedState); } - -void GdbTest::testSegfaultDebugee() +void LldbTest::testSwitchFrameLldbConsole() { TestDebugSession *session = new TestDebugSession; - session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); - TestLaunchConfiguration cfg(findExecutable("debugeecrash")); - QString fileName = findSourceFile("debugeecrash.cpp"); - - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 23); - - QVERIFY(session->startDebugging(&cfg, m_iface)); - - WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 23); - session->run(); - - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 24); - - session->stopDebugger(); - WAIT_FOR_STATE(session, DebugSession::EndedState); -} - -void GdbTest::testSwitchFrameGdbConsole() -{ - TestDebugSession *session = new TestDebugSession; - TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(stackModel->currentFrame(), 0); stackModel->setCurrentFrame(1); QCOMPARE(stackModel->currentFrame(), 1); - QTest::qWait(500); + WAIT_FOR_A_WHILE(session, 500); QCOMPARE(stackModel->currentFrame(), 1); - session->addUserCommand("print x"); - QTest::qWait(500); + session->addUserCommand("print i"); + WAIT_FOR_A_WHILE(session, 500); //currentFrame must not reset to 0; Bug 222882 QCOMPARE(stackModel->currentFrame(), 1); - -} - -//Bug 201771 -void GdbTest::testInsertAndRemoveBreakpointWhileRunning() -{ - TestDebugSession *session = new TestDebugSession; - TestLaunchConfiguration cfg(findExecutable("debugeeslow")); - QString fileName = findSourceFile("debugeeslow.cpp"); - - session->startDebugging(&cfg, m_iface); - - WAIT_FOR_STATE(session, DebugSession::ActiveState); - QTest::qWait(2000); - qDebug() << "adding breakpoint"; - KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); - b->setDeleted(); - WAIT_FOR_STATE(session, DebugSession::EndedState); -} - -//Bug 274390 -void GdbTest::testCommandOrderFastStepping() -{ - TestDebugSession *session = new TestDebugSession; - - TestLaunchConfiguration cfg(findExecutable("debugeeqt")); - - breakpoints()->addCodeBreakpoint("main"); - QVERIFY(session->startDebugging(&cfg, m_iface)); - for(int i=0; i<20; i++) { - session->stepInto(); - } - WAIT_FOR_STATE(session, DebugSession::PausedState); - session->run(); - WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testPickupManuallyInsertedBreakpoint() +void LldbTest::testSegfaultDebugee() { TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration cfg(findExecutable("lldb_debugeecrash")); - TestLaunchConfiguration cfg; - - breakpoints()->addCodeBreakpoint("main"); - QVERIFY(session->startDebugging(&cfg, m_iface)); - session->addCommand(MI::NonMI, "break debugee.cpp:32"); - session->stepInto(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(1000); //wait for breakpoints update - QCOMPARE(breakpoints()->breakpoints().count(), 2); - QCOMPARE(breakpoints()->rowCount(), 2); - KDevelop::Breakpoint *b = breakpoints()->breakpoint(1); - QVERIFY(b); - QCOMPARE(b->line(), 31); //we start with 0, gdb with 1 - QCOMPARE(b->url().fileName(), QString("debugee.cpp")); -} - -//Bug 270970 -void GdbTest::testPickupManuallyInsertedBreakpointOnlyOnce() -{ - TestDebugSession *session = new TestDebugSession; - - //inject here, so it behaves similar like a command from .gdbinit - QTemporaryFile configScript; - configScript.open(); - configScript.write(QString("file %0\n").arg(findExecutable("debugee").toLocalFile()).toLocal8Bit()); - configScript.write("break debugee.cpp:32\n"); - configScript.close(); + session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); - TestLaunchConfiguration cfg; - KConfigGroup grp = cfg.config(); - grp.writeEntry(remoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); + QString fileName = findSourceFile("debugeecrash.cpp"); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 23); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile("debugee.cpp"), 31); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(breakpoints()->breakpoints().count(), 1); - + QCOMPARE(session->currentLine(), 23); session->run(); - WAIT_FOR_STATE(session, DebugSession::EndedState); -} - -void GdbTest::testPickupCatchThrowOnlyOnce() -{ - QTemporaryFile configScript; - configScript.open(); - configScript.write("catch throw\n"); - configScript.close(); - - TestLaunchConfiguration cfg; - KConfigGroup grp = cfg.config(); - grp.writeEntry(remoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); - - - for (int i = 0; i < 2; ++i) { - TestDebugSession* session = new TestDebugSession; - QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE(session, DebugSession::EndedState); - } - - QCOMPARE(breakpoints()->rowCount(), 1); //one from kdevelop, one from runScript -} - -void GdbTest::testRunGdbScript() -{ - TestDebugSession *session = new TestDebugSession; - - QTemporaryFile runScript; - runScript.open(); - - runScript.write("file " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); - runScript.write("break main\n"); - runScript.write("run\n"); - runScript.close(); - - TestLaunchConfiguration cfg; - KConfigGroup grp = cfg.config(); - grp.writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); - - QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(session->currentLine(), 24); - QCOMPARE(session->line(), 27); - - session->run(); + session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testRemoteDebug() +//Bug 274390 +void LldbTest::testCommandOrderFastStepping() { - const QString gdbserverExecutable = QStandardPaths::findExecutable("gdbserver"); - if (gdbserverExecutable.isEmpty()) { - QSKIP("Skipping, gdbserver not available", SkipSingle); - } - TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration cfg(findExecutable("lldb_debugeeqt")); - QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); - shellScript.open(); - shellScript.write("gdbserver localhost:2345 " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); - shellScript.close(); - shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); - QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) - - QTemporaryFile runScript(QDir::currentPath()+"/runscript"); - runScript.open(); - runScript.write("file " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); - runScript.write("target remote localhost:2345\n"); - runScript.write("break debugee.cpp:30\n"); - runScript.write("continue\n"); - runScript.close(); - - TestLaunchConfiguration cfg; - KConfigGroup grp = cfg.config(); - grp.writeEntry(remoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); - grp.writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); - + breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startDebugging(&cfg, m_iface)); - - WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - - QCOMPARE(session->line(), 29); - - session->run(); - WAIT_FOR_STATE(session, DebugSession::EndedState); - - QFile::remove(shellScript.fileName()+"-copy"); -} - -void GdbTest::testRemoteDebugInsertBreakpoint() -{ - const QString gdbserverExecutable = QStandardPaths::findExecutable("gdbserver"); - if (gdbserverExecutable.isEmpty()) { - QSKIP("Skipping, gdbserver not available", SkipSingle); + for(int i=0; i<20; i++) { + session->stepInto(); } - - TestDebugSession *session = new TestDebugSession; - - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 35); - - QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); - shellScript.open(); - shellScript.write("gdbserver localhost:2345 " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); - shellScript.close(); - shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); - QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) - - QTemporaryFile runScript(QDir::currentPath()+"/runscript"); - runScript.open(); - runScript.write("file " + findExecutable("debugee").toLocalFile().toUtf8() + '\n'); - runScript.write("target remote localhost:2345\n"); - runScript.write("break debugee.cpp:30\n"); - runScript.write("continue\n"); - runScript.close(); - - TestLaunchConfiguration cfg; - KConfigGroup grp = cfg.config(); - grp.writeEntry(remoteGdbShellEntry, QUrl::fromLocalFile(shellScript.fileName()+"-copy")); - grp.writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); - - QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - - QCOMPARE(session->line(), 29); - - QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript - - session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - - QCOMPARE(session->line(), 35); - session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); - - QFile::remove(shellScript.fileName()+"-copy"); } - -void GdbTest::testRemoteDebugInsertBreakpointPickupOnlyOnce() +void LldbTest::testRunLldbScript() { - const QString gdbserverExecutable = QStandardPaths::findExecutable("gdbserver"); - if (gdbserverExecutable.isEmpty()) { - QSKIP("Skipping, gdbserver not available", SkipSingle); - } - TestDebugSession *session = new TestDebugSession; - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 35); - - QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); - shellScript.open(); - shellScript.write("gdbserver localhost:2345 "+findExecutable("debugee").toLocalFile().toLatin1()+"\n"); - shellScript.close(); - shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); - QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) - - QTemporaryFile runScript(QDir::currentPath()+"/runscript"); + QTemporaryFile runScript; runScript.open(); - runScript.write("file "+findExecutable("debugee").toLocalFile().toLatin1()+"\n"); - runScript.write("target remote localhost:2345\n"); - runScript.write("break debugee.cpp:30\n"); - runScript.write("continue\n"); + + runScript.write(QStringLiteral("break set --file %1 --line 35\n").arg(findSourceFile("debugee.cpp")).toUtf8()); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); - grp.writeEntry(remoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); - grp.writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); + grp.writeEntry(Config::LldbConfigScriptEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 29); - - QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript - - session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - - QCOMPARE(session->line(), 35); + QCOMPARE(session->currentLine(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); - - //************************** second session - session = new TestDebugSession; - QVERIFY(session->startDebugging(&cfg, m_iface)); - - WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - - QCOMPARE(session->line(), 29); - - QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript - - session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - - QCOMPARE(session->line(), 35); - - session->run(); - WAIT_FOR_STATE(session, DebugSession::EndedState); - - QFile::remove(shellScript.fileName()+"-copy"); } -void GdbTest::testBreakpointWithSpaceInPath() +void LldbTest::testBug301287() { TestDebugSession *session = new TestDebugSession; - - TestLaunchConfiguration cfg(findExecutable("debugeespace")); - KConfigGroup grp = cfg.config(); - QString fileName = findSourceFile("debugee space.cpp"); - - KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 20); - QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); - - session->startDebugging(&cfg, m_iface); - WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 20); - session->run(); - WAIT_FOR_STATE(session, DebugSession::EndedState); -} - -void GdbTest::testBreakpointDisabledOnStart() -{ - TestDebugSession *session = new TestDebugSession; - TestLaunchConfiguration cfg; - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28) - ->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); - KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 31); - b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); - - session->startDebugging(&cfg, m_iface); - WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 29); - b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); - session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 31); - session->run(); - WAIT_FOR_STATE(session, DebugSession::EndedState); -} - -void GdbTest::testCatchpoint() -{ - TestDebugSession *session = new TestDebugSession; - session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); - - TestLaunchConfiguration cfg(findExecutable("debugeeexception")); - - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp")), 29); - - session->startDebugging(&cfg, m_iface); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(1000); - TestFrameStackModel* fsModel = session->frameStackModel(); - QCOMPARE(fsModel->currentFrame(), 0); - QCOMPARE(session->line(), 29); - - session->addCommand(MI::NonMI, "catch throw"); - session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(1000); - - const QList frames = fsModel->frames(fsModel->currentThread()); - QVERIFY(frames.size() >= 2); - // frame 0 is somewhere inside libstdc++ - QCOMPARE(frames[1].file, QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp"))); - QCOMPARE(frames[1].line, 22); - - QCOMPARE(breakpoints()->rowCount(),2); - QVERIFY(!breakpoints()->breakpoint(0)->location().isEmpty()); - QVERIFY(!breakpoints()->breakpoint(1)->location().isEmpty()); - - session->run(); - WAIT_FOR_STATE(session, DebugSession::EndedState); -} + session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); -//TODO: figure out why do we need this test? And do we need it at all?? -void GdbTest::testThreadAndFrameInfo() -{ - TestDebugSession *session = new TestDebugSession; - TestLaunchConfiguration cfg(findExecutable("debugeethreads")); - QString fileName = findSourceFile("debugeethreads.cpp"); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(1000); - - QSignalSpy outputSpy(session, &TestDebugSession::debuggerUserCommandOutput); - - session->addCommand( - new MI::UserCommand(MI::ThreadInfo,"")); - session->addCommand(new MI::UserCommand(MI::StackListLocals, QLatin1String("0"))); - QTest::qWait(1000); - QCOMPARE(outputSpy.count(), 2); - QVERIFY(outputSpy.last().at(0).toString().contains(QLatin1String("--thread 1"))); - - session->run(); - WAIT_FOR_STATE(session, DebugSession::EndedState); -} - -void GdbTest::parseBug304730() -{ - MI::FileSymbol file; - file.contents = QByteArray("^done,bkpt={" - "number=\"1\",type=\"breakpoint\",disp=\"keep\",enabled=\"y\",addr=\"\",times=\"0\"," - "original-location=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp:231\"}," - "{number=\"1.1\",enabled=\"y\",addr=\"0x081d84aa\"," - "func=\"PatchMatch, 2u> >" - "::Propagation(ForwardPropagationNeighbors)\"," - "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," - "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}," - "{number=\"1.2\",enabled=\"y\",addr=\"0x081d8ae2\"," - "func=\"PatchMatch, 2u> >" - "::Propagation(BackwardPropagationNeighbors)\"," - "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," - "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}," - "{number=\"1.3\",enabled=\"y\",addr=\"0x081d911a\"," - "func=\"PatchMatch, 2u> >" - "::Propagation(AllowedPropagationNeighbors)\"," - "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," - "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}"); - - MI::MIParser parser; - - std::unique_ptr record(parser.parse(&file)); - QVERIFY(record.get() != nullptr); -} - -void GdbTest::testMultipleLocationsBreakpoint() -{ - TestDebugSession *session = new TestDebugSession; - - TestLaunchConfiguration cfg(findExecutable("debugeemultilocbreakpoint")); - - breakpoints()->addCodeBreakpoint("aPlusB"); - - //TODO check if the additional location breakpoint is added - - session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 19); - - session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(session->line(), 23); - - session->run(); - WAIT_FOR_STATE(session, DebugSession::EndedState); -} - -void GdbTest::testBug301287() -{ - TestDebugSession *session = new TestDebugSession; - session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); - - TestLaunchConfiguration cfg; - - breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); - - session->startDebugging(&cfg, m_iface); - WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("argc"); - QTest::qWait(300); + WAIT_FOR_A_WHILE(session, 300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); @@ -1877,10 +1778,8 @@ session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); - session->startDebugging(&cfg, m_iface); - WAIT_FOR_STATE(session, DebugSession::PausedState); - - QTest::qWait(300); + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); @@ -1891,153 +1790,60 @@ WAIT_FOR_STATE(session, DebugSession::EndedState); } -void GdbTest::testMultipleBreakpoint() -{ - TestDebugSession *session = new TestDebugSession; - - //there'll be about 3-4 breakpoints, but we treat it like one. - TestLaunchConfiguration c(findExecutable("debugeemultiplebreakpoint")); - KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint("debugeemultiplebreakpoint.cpp:52"); - session->startDebugging(&c, m_iface); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(breakpoints()->breakpoints().count(), 1); - - b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); - session->run(); - WAIT_FOR_STATE(session, DebugSession::EndedState); -} - -void GdbTest::testRegularExpressionBreakpoint() -{ - TestDebugSession *session = new TestDebugSession; - - TestLaunchConfiguration c(findExecutable("debugeemultilocbreakpoint")); - breakpoints()->addCodeBreakpoint("main"); - session->startDebugging(&c, m_iface); - WAIT_FOR_STATE(session, DebugSession::PausedState); - session->addCommand(MI::NonMI, "rbreak .*aPl.*B"); - QTest::qWait(100); - session->run(); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QCOMPARE(breakpoints()->breakpoints().count(), 3); - - session->addCommand(MI::BreakDelete, ""); - session->run(); - WAIT_FOR_STATE(session, DebugSession::EndedState); -} - -void GdbTest::testChangeBreakpointWhileRunning() { - - TestDebugSession *session = new TestDebugSession; - - TestLaunchConfiguration c(findExecutable("debugeeslow")); - KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint("debugeeslow.cpp:25"); - session->startDebugging(&c, m_iface); - - WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); - QVERIFY(session->currentLine() >= 24 && session->currentLine() <= 26 ); - session->run(); - WAIT_FOR_STATE(session, DebugSession::ActiveState); - b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); - //to make one loop - QTest::qWait(2000); - WAIT_FOR_STATE(session, DebugSession::ActiveState); - - b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); - QTest::qWait(100); - WAIT_FOR_STATE(session, DebugSession::PausedState); - b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); - session->run(); - QTest::qWait(100); - WAIT_FOR_STATE(session, DebugSession::EndedState); -} - -void GdbTest::testDebugInExternalTerminal() +void LldbTest::testDebugInExternalTerminal() { TestLaunchConfiguration cfg; foreach (const QString & console, QStringList() << "konsole" << "xterm" << "xfce4-terminal" << "gnome-terminal") { - TestDebugSession* session = 0; if (QStandardPaths::findExecutable(console).isEmpty()) { continue; } - session = new TestDebugSession(); + TestDebugSession* session = new TestDebugSession(); cfg.config().writeEntry("External Terminal"/*ExecutePlugin::terminalEntry*/, console); cfg.config().writeEntry("Use External Terminal"/*ExecutePlugin::useTerminalEntry*/, true); - KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); + KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); - session->startDebugging(&cfg, m_iface); + QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); + session->stepInto(); - WAIT_FOR_STATE(session, DebugSession::PausedState); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } } -// see: https://bugs.kde.org/show_bug.cgi?id=339231 -void GdbTest::testPathWithSpace() +void LldbTest::testSpecialPath() { -#ifdef HAVE_PATH_WITH_SPACES_TEST +#ifndef HAVE_PATH_WITH_SPACES_TEST + QSKIP("Skipping... special path test," + " this CMake version would create a faulty build.ninja file. Upgrade to at least CMake v3.0"); +#endif + QSKIP("Skipping... lldb-mi itself can't handle path with space in application dir"); + TestDebugSession* session = new TestDebugSession; - auto debugee = findExecutable("path with space/spacedebugee"); + auto debugee = findExecutable("path with space/lldb_spacedebugee"); TestLaunchConfiguration c(debugee, KIO::upUrl(debugee)); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint("spacedebugee.cpp:30"); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); - session->startDebugging(&c, m_iface); - WAIT_FOR_STATE(session, DebugSession::PausedState); + QVERIFY(session->startDebugging(&c, m_iface)); + + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); + session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); -#endif -} - -bool GdbTest::waitForState(DebugSession *session, DebugSession::DebuggerState state, - const char *file, int line, bool waitForIdle) -{ - QPointer s(session); //session can get deleted in DebugController - QTime stopWatch; - stopWatch.start(); - - // legacy behavior for tests that implicitly may require waiting for idle, - // but which were written before waitForIdle was added - waitForIdle = waitForIdle || state != MIDebugSession::EndedState; - - while (s && (s->state() != state || (waitForIdle && s->debuggerStateIsOn(s_dbgBusy)))) { - if (stopWatch.elapsed() > 5000) { - qWarning() << "current state" << s->state() << "waiting for" << state; - QTest::qFail(qPrintable(QString("Timeout before reaching state %0").arg(state)), - file, line); - return false; - } - QTest::qWait(20); - } - - // NOTE: don't wait anymore after leaving the loop. Waiting re-enters event loop and - // may change session state. - - if (!s && state != MIDebugSession::EndedState) { - QTest::qFail(qPrintable(QString("Session ended before reaching state %0").arg(state)), - file, line); - return false; - } - - qDebug() << "Reached state " << state << " in " << file << ':' << line; - return true; } -} // end of namespace GDB -} // end of namespace KDevMI - -QTEST_MAIN(KDevMI::GDB::GdbTest) - -#include "test_gdb.moc" -#include "moc_test_gdb.cpp" +QTEST_MAIN(KDevMI::LLDB::LldbTest); +#include "test_lldb.moc" diff --git a/debuggers/lldb/unittests/testhelper.h b/debuggers/lldb/unittests/testhelper.h new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/testhelper.h @@ -0,0 +1,69 @@ +/* + * Helpers for LLDB debugger unit tests + * 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_UNITTEST_H +#define LLDB_UNITTEST_H + +#include + +#include +#include +#include +#include + +namespace KDevMI { + +class MIDebugSession; + +namespace LLDB { + +QUrl findExecutable(const QString& name); +QString findSourceFile(const char *file, const QString& name); +bool isAttachForbidden(const char *file, int line); + +bool compareData(QModelIndex index, QString expected, const char *file, int line); + +bool waitForState(MIDebugSession *session, KDevelop::IDebugSession::DebuggerState state, + const char *file, int line, bool waitForIdle = false); + +bool waitForAWhile(MIDebugSession *session, int ms, const char *file, int line); + +class TestWaiter +{ +public: + TestWaiter(MIDebugSession * session_, const char * condition_, const char * file_, int line_); + + bool waitUnless(bool ok); + +private: + QTime stopWatch; + QPointer session; + const char * condition; + const char * file; + int line; +}; + + +} // end of namespace LLDB +} // end of namespace KDevMI + +#endif // LLDB_UNITTEST_H diff --git a/debuggers/lldb/unittests/testhelper.cpp b/debuggers/lldb/unittests/testhelper.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/unittests/testhelper.cpp @@ -0,0 +1,169 @@ +/* + * Common helpers for MI debugger unit tests + * 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 "testhelper.h" + +#include "midebugsession.h" + +#include +#include +#include +#include +#include + +namespace KDevMI { namespace LLDB { + +QUrl findExecutable(const QString& name) +{ + QFileInfo info(qApp->applicationDirPath() + "/unittests/debugees/" + name); + Q_ASSERT(info.exists()); + Q_ASSERT(info.isExecutable()); + return QUrl::fromLocalFile(info.canonicalFilePath()); +} + +// Try to find file in the same folder as `file`, +// if not found, go down to debugees folder. +QString findSourceFile(const char *file, const QString& name) +{ + QDir baseDir = QFileInfo(file).dir(); + QFileInfo info(baseDir.absoluteFilePath(name)); + if (info.exists()) { + return info.canonicalFilePath(); + } + + baseDir.cd("debugees"); + info = baseDir.absoluteFilePath(name); + Q_ASSERT(info.exists()); + return info.canonicalFilePath(); +} + +bool isAttachForbidden(const char *file, int line) +{ + // if on linux, ensure we can actually attach + QFile canRun("/proc/sys/kernel/yama/ptrace_scope"); + if (canRun.exists()) { + if (!canRun.open(QIODevice::ReadOnly)) { + QTest::qFail("Something is wrong: /proc/sys/kernel/yama/ptrace_scope exists but cannot be read", file, line); + return true; + } + if (canRun.read(1).toInt() != 0) { + QTest::qSkip("ptrace attaching not allowed, skipping test. To enable it, set /proc/sys/kernel/yama/ptrace_scope to 0.", file, line); + return true; + } + } + + return false; +} + +bool compareData(QModelIndex index, QString expected, const char *file, int line) +{ + QString s = index.model()->data(index, Qt::DisplayRole).toString(); + if (s != expected) { + QTest::qFail(qPrintable(QString("'%0' didn't match expected '%1' in %2:%3") + .arg(s).arg(expected).arg(file).arg(line)), + file, line); + return false; + } + return true; +} + +bool waitForAWhile(MIDebugSession *session, int ms, const char *file, int line) +{ + QPointer s(session); //session can get deleted in DebugController + QTest::qWait(ms); + if (!s) { + QTest::qFail("Session ended while waiting", file, line); + return false; + } + return true; +} + +bool waitForState(MIDebugSession *session, KDevelop::IDebugSession::DebuggerState state, + const char *file, int line, bool waitForIdle) +{ + QPointer s(session); //session can get deleted in DebugController + QTime stopWatch; + stopWatch.start(); + + // legacy behavior for tests that implicitly may require waiting for idle, + // but which were written before waitForIdle was added + waitForIdle = waitForIdle || state != MIDebugSession::EndedState; + + while (s && (s->state() != state + || (waitForIdle && s->debuggerStateIsOn(s_dbgBusy)))) { + if (stopWatch.elapsed() > 5000) { + qWarning() << "current state" << s->state() << "waiting for" << state; + QTest::qFail(qPrintable(QString("Timeout before reaching state %0").arg(state)), + file, line); + return false; + } + QTest::qWait(20); + } + + // NOTE: don't wait anymore after leaving the loop. Waiting reenters event loop and + // may change session state. + + if (!s && state != MIDebugSession::EndedState) { + QTest::qFail(qPrintable(QString("Session ended before reaching state %0").arg(state)), + file, line); + return false; + } + + qDebug() << "Reached state " << state << " in " << file << ':' << line; + return true; +} + +TestWaiter::TestWaiter(MIDebugSession * session_, const char * condition_, const char * file_, int line_) + : session(session_) + , condition(condition_) + , file(file_) + , line(line_) +{ + stopWatch.start(); +} + +bool TestWaiter::waitUnless(bool ok) +{ + if (ok) { + qDebug() << "Condition " << condition << " reached in " << file << ':' << line; + return false; + } + + if (stopWatch.elapsed() > 5000) { + QTest::qFail(qPrintable(QString("Timeout before reaching condition %0").arg(condition)), + file, line); + return false; + } + + QTest::qWait(100); + + if (!session) { + QTest::qFail(qPrintable(QString("Session ended without reaching condition %0").arg(condition)), + file, line); + return false; + } + + return true; +} + +} // end of namespace LLDB +} // end of namespace KDevMI diff --git a/debuggers/lldb/widgets/lldbconfigpage.h b/debuggers/lldb/widgets/lldbconfigpage.h new file mode 100644 --- /dev/null +++ b/debuggers/lldb/widgets/lldbconfigpage.h @@ -0,0 +1,56 @@ +/* + * 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 . + * + */ + +#ifndef LLDBCONFIGPAGE_H +#define LLDBCONFIGPAGE_H + +#include + +namespace Ui +{ +class LldbConfigPage; +} + +class LldbConfigPage : public KDevelop::LaunchConfigurationPage +{ + Q_OBJECT +public: + LldbConfigPage( QWidget* parent = 0 ); + ~LldbConfigPage() override; + + QIcon icon() const override; + QString title() const override; + void loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject *proj = nullptr) override; + void saveToConfiguration(KConfigGroup cfg, KDevelop::IProject *proj = nullptr) const override; + +private: + Ui::LldbConfigPage* ui; +}; + +class LldbConfigPageFactory : public KDevelop::LaunchConfigurationPageFactory +{ +public: + KDevelop::LaunchConfigurationPage* createWidget(QWidget* parent) override; +}; + + +#endif // LLDBCONFIGPAGE_H diff --git a/debuggers/lldb/widgets/lldbconfigpage.cpp b/debuggers/lldb/widgets/lldbconfigpage.cpp new file mode 100644 --- /dev/null +++ b/debuggers/lldb/widgets/lldbconfigpage.cpp @@ -0,0 +1,121 @@ +/* + * 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 "lldbconfigpage.h" +#include "ui_lldbconfigpage.h" + +#include "dbgglobal.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace KDevelop; +namespace Config = KDevMI::LLDB::Config; + +LldbConfigPage::LldbConfigPage(QWidget* parent) + : LaunchConfigurationPage(parent) + , ui(new Ui::LldbConfigPage) +{ + ui->setupUi(this); + ui->lineDebuggerExec->setMode(KFile::File|KFile::ExistingOnly|KFile::LocalOnly); + ui->lineConfigScript->setMode(KFile::File|KFile::ExistingOnly|KFile::LocalOnly); + + QRegularExpression rx(R"([^:]+:\d+)"); + ui->lineRemoteServer->setValidator(new QRegularExpressionValidator(rx, this)); + + ui->comboStartWith->setItemData(0, "ApplicationOutput"); + ui->comboStartWith->setItemData(1, "GdbConsole"); + ui->comboStartWith->setItemData(2, "FrameStack"); + + connect(ui->lineDebuggerExec, &KUrlRequester::textChanged, this, &LldbConfigPage::changed); + connect(ui->lineDebuggerArgs, &QLineEdit::textChanged, this, &LldbConfigPage::changed); + connect(ui->comboEnv, &EnvironmentSelectionWidget::currentProfileChanged, + this, &LldbConfigPage::changed); + + connect(ui->lineConfigScript, &KUrlRequester::textChanged, this, &LldbConfigPage::changed); + connect(ui->comboStartWith, static_cast(&QComboBox::currentIndexChanged), + this, &LldbConfigPage::changed); + + connect(ui->groupRemote, &QGroupBox::clicked, this, &LldbConfigPage::changed); + connect(ui->lineRemoteServer, &QLineEdit::textChanged, this, &LldbConfigPage::changed); + connect(ui->lineOnDevPath, &QLineEdit::textChanged, this, &LldbConfigPage::changed); +} + +LldbConfigPage::~LldbConfigPage() +{ + delete ui; +} + +QIcon LldbConfigPage::icon() const +{ + return {}; +} + +QString LldbConfigPage::title() const +{ + return i18n("LLDB Configuration"); +} + +void LldbConfigPage::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject *) +{ + bool block = blockSignals(true); + ui->lineDebuggerExec->setUrl(cfg.readEntry(Config::LldbExecutableEntry, QUrl())); + ui->lineDebuggerArgs->setText(cfg.readEntry(Config::LldbArgumentsEntry, QString())); + ui->comboEnv->setCurrentProfile(cfg.readEntry(Config::LldbEnvironmentEntry, QString())); + ui->checkInheritSystem->setChecked(cfg.readEntry(Config::LldbInheritSystemEnvEntry, true)); + ui->lineConfigScript->setUrl(cfg.readEntry(Config::LldbConfigScriptEntry, QUrl())); + ui->checkBreakOnStart->setChecked(cfg.readEntry(KDevMI::Config::BreakOnStartEntry, false)); + ui->comboStartWith->setCurrentIndex(ui->comboStartWith->findData( + cfg.readEntry(KDevMI::Config::StartWithEntry, "ApplicationOutput"))); + ui->groupRemote->setChecked(cfg.readEntry(Config::LldbRemoteDebuggingEntry, false)); + ui->lineRemoteServer->setText(cfg.readEntry(Config::LldbRemoteServerEntry, QString())); + ui->lineOnDevPath->setText(cfg.readEntry(Config::LldbRemotePathEntry, QString())); + blockSignals(block); +} + +void LldbConfigPage::saveToConfiguration(KConfigGroup cfg, KDevelop::IProject *) const +{ + cfg.writeEntry(Config::LldbExecutableEntry, ui->lineDebuggerExec->url()); + cfg.writeEntry(Config::LldbArgumentsEntry, ui->lineDebuggerArgs->text()); + cfg.writeEntry(Config::LldbEnvironmentEntry, ui->comboEnv->currentProfile()); + cfg.writeEntry(Config::LldbInheritSystemEnvEntry, ui->checkInheritSystem->isChecked()); + cfg.writeEntry(Config::LldbConfigScriptEntry, ui->lineConfigScript->url()); + cfg.writeEntry(KDevMI::Config::BreakOnStartEntry, ui->checkBreakOnStart->isChecked()); + cfg.writeEntry(KDevMI::Config::StartWithEntry, ui->comboStartWith->currentData().toString()); + cfg.writeEntry(Config::LldbRemoteDebuggingEntry, ui->groupRemote->isChecked()); + cfg.writeEntry(Config::LldbRemoteServerEntry, ui->lineRemoteServer->text()); + cfg.writeEntry(Config::LldbRemotePathEntry, ui->lineOnDevPath->text()); +} + +KDevelop::LaunchConfigurationPage * LldbConfigPageFactory::createWidget(QWidget* parent) +{ + return new LldbConfigPage(parent); +} diff --git a/debuggers/lldb/widgets/lldbconfigpage.ui b/debuggers/lldb/widgets/lldbconfigpage.ui new file mode 100644 --- /dev/null +++ b/debuggers/lldb/widgets/lldbconfigpage.ui @@ -0,0 +1,226 @@ + + + LldbConfigPage + + + + 0 + 0 + 528 + 535 + + + + Debugger Configuration + + + + + + Debugger + + + + + + Debugger executable: + + + + + + + /path/to/lldb-mi/ + + + + + + + Arguments: + + + + + + + Additional arguments passing to lldb + + + + + + + Environment: + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + Inherit system environment + + + + + + + + + + Options + + + + + + Config script: + + + + + + + /path/to/lldb/config/script + + + + + + + Start debugger with: + + + + + + + + Application Output + + + + + LLDB Console + + + + + Frame Stack + + + + + + + + Break on start: + + + + + + + + + + + + + + + + + Re&mote Debugging + + + true + + + + + + Remote server: + + + + + + + host:port + + + + + + + Remote work path: + + + + + + + /path/to/a/on-device/writable/directory + + + + + + + + + + Qt::Vertical + + + + 20 + 149 + + + + + + + + + KDevelop::EnvironmentConfigureButton + QToolButton +
shell/environmentconfigurebutton.h
+
+ + KDevelop::EnvironmentSelectionWidget + QComboBox +
util/environmentselectionwidget.h
+
+ + KUrlRequester + QLineEdit +
kurlrequester.h
+
+
+ + +