diff --git a/debuggers/CMakeLists.txt b/debuggers/CMakeLists.txt index 0f7f8d73e2..de56902c2d 100644 --- 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 index 09a0cd8230..0392b18439 100644 --- a/debuggers/common/CMakeLists.txt +++ b/debuggers/common/CMakeLists.txt @@ -1,73 +1,76 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevdebuggercommon\") set(debuggercommon_SRCS mi/mi.cpp mi/milexer.cpp mi/miparser.cpp mi/micommand.cpp mi/micommandqueue.cpp dialogs/selectcoredialog.cpp debuglog.cpp # debug session & debugger midebugger.cpp midebugsession.cpp midebuggerplugin.cpp midebugjobs.cpp # controllers mibreakpointcontroller.cpp miframestackmodel.cpp mivariablecontroller.cpp 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 dialogs/processselection.cpp ) endif() 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 # libraries, and executables without exports) on target kdevdebuggercommon (so the # default public visibility is used). # kdevdebuggercommon is used by target test_gdb which is added by ecm_add_test, # which doesn't set CMP0063 so old behavior is used. # If kdevdebuggercommon honors visibility properties (set to hidden), it will cause # linker warnings about direct access to global weak symbol when link against test_gdb if(NOT CMAKE_VERSION VERSION_LESS "3.3") cmake_policy(SET CMP0063 OLD) endif() add_library(kdevdebuggercommon STATIC ${debuggercommon_SRCS}) target_link_libraries(kdevdebuggercommon PUBLIC KDev::Debugger PRIVATE Qt5::Core Qt5::Gui + Qt5::Widgets KDev::Util KDev::Language ) if(KF5SysGuard_FOUND) target_link_libraries(kdevdebuggercommon PUBLIC KF5::ProcessUi ) endif() kde_target_enable_exceptions(kdevdebuggercommon PUBLIC) diff --git a/debuggers/common/dbgglobal.h b/debuggers/common/dbgglobal.h index 7cd3966f02..7e3112fc77 100644 --- a/debuggers/common/dbgglobal.h +++ b/debuggers/common/dbgglobal.h @@ -1,69 +1,88 @@ /*************************************************************************** dbgglobal.h ------------------- begin : Sun Aug 8 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org ***************************************************************************/ /*************************************************************************** * * * 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) any later version. * * * ***************************************************************************/ #ifndef _DBGGLOBAL_H_ #define _DBGGLOBAL_H_ #include namespace KDevMI { enum DBGStateFlag { s_none = 0, s_dbgNotStarted = 1 << 0, s_appNotStarted = 1 << 1, s_programExited = 1 << 2, s_attached = 1 << 3, s_core = 1 << 4, /// Set when 'slotStopDebugger' started executing, to avoid /// entering that function several times. s_shuttingDown = 1 << 6, s_dbgBusy = 1 << 8, s_appRunning = 1 << 9, /// Set when we suspect GDB to be in a state where it does not listen for new commands /// while the inferior is running s_dbgNotListening = 1 << 10, s_interruptSent = 1 << 11, /// Once GDB is completely idle, send an automatic ExecContinue to resume from an interruption /// by CmdImmediately commands s_automaticContinue = 1 << 12, }; Q_DECLARE_FLAGS(DBGStateFlags, DBGStateFlag) enum DataType { typeUnknown, typeValue, typePointer, typeReference, typeStruct, typeArray, typeQString, typeWhitespace, 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 Q_DECLARE_OPERATORS_FOR_FLAGS(KDevMI::DBGStateFlags) #endif // _DBGGLOBAL_H_ diff --git a/debuggers/common/mi/mi.h b/debuggers/common/mi/mi.h index 67d578163f..52b2b331c5 100644 --- a/debuggers/common/mi/mi.h +++ b/debuggers/common/mi/mi.h @@ -1,422 +1,372 @@ /*************************************************************************** * Copyright (C) 2004 by Roberto Raggi * * roberto@kdevelop.org * * Copyright (C) 2005-2006 by Vladimir Prus * * ghost@cs.msu.su * * Copyright (C) 2016 by Aetf * * aetf@unlimitedcodeworks.xyz * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef GDBMI_H #define GDBMI_H #include #include #include /** @author Roberto Raggi @author Vladimir Prus */ namespace KDevMI { namespace MI { enum CommandType { NonMI, BreakAfter, - BreakCatch, BreakCommands, BreakCondition, BreakDelete, BreakDisable, BreakEnable, BreakInfo, BreakInsert, BreakList, BreakWatch, DataDisassemble, DataEvaluateExpression, DataListChangedRegisters, DataListRegisterNames, DataListRegisterValues, DataReadMemory, DataWriteMemory, DataWriteRegisterVariables, EnablePrettyPrinting, EnableTimings, EnvironmentCd, EnvironmentDirectory, EnvironmentPath, EnvironmentPwd, ExecAbort, ExecArguments, ExecContinue, ExecFinish, ExecInterrupt, ExecNext, ExecNextInstruction, - ExecReturn, ExecRun, - ExecShowArguments, - ExecSignal, ExecStep, ExecStepInstruction, ExecUntil, - FileClear, FileExecAndSymbols, FileExecFile, - FileListExecSections, FileListExecSourceFile, FileListExecSourceFiles, - FileListSharedLibraries, - FileListSymbolFiles, FileSymbolFile, - GdbComplete, GdbExit, GdbSet, GdbShow, - GdbSource, GdbVersion, InferiorTtySet, InferiorTtyShow, InterpreterExec, ListFeatures, - OverlayAuto, - OverlayListMappingState, - OverlayListOverlays, - OverlayMap, - OverlayOff, - OverlayOn, - OverlayUnmap, - SignalHandle, - SignalListHandleActions, - SignalListSignalTypes, StackInfoDepth, StackInfoFrame, StackListArguments, - StackListExceptionHandlers, StackListFrames, StackListLocals, StackSelectFrame, - SymbolInfoAddress, - SymbolInfoFile, - SymbolInfoFunction, - SymbolInfoLine, - SymbolInfoSymbol, - SymbolListFunctions, SymbolListLines, - SymbolListTypes, - SymbolListVariables, - SymbolLocate, - SymbolType, TargetAttach, - TargetCompareSections, TargetDetach, TargetDisconnect, TargetDownload, - TargetExecStatus, - TargetListAvailableTargets, - TargetListCurrentTargets, - TargetListParameters, TargetSelect, ThreadInfo, - ThreadListAllThreads, ThreadListIds, ThreadSelect, - TraceActions, - TraceDelete, - TraceDisable, - TraceDump, - TraceEnable, - TraceExists, TraceFind, - TraceFrameNumber, - TraceInfo, - TraceInsert, - TraceList, - TracePassCount, - TraceSave, TraceStart, TraceStop, VarAssign, VarCreate, VarDelete, VarEvaluateExpression, VarInfoPathExpression, - VarInfoExpression, VarInfoNumChildren, VarInfoType, VarListChildren, VarSetFormat, VarSetFrozen, VarShowAttributes, VarShowFormat, VarUpdate }; /** Exception that is thrown when we're trying to invoke an operation that is not supported by specific MI value. For example, trying to index a string literal. Such errors are conceptually the same as assert, but in GUI we can't use regular assert, and Q_ASSERT, which only prints a message, is not suitable either. We need to break processing, and the higher-level code can report "Internal parsing error", or something. Being glorified assert, this exception does not cary any useful information. */ class type_error : public std::logic_error { public: type_error(); }; /** Base class for all MI values. MI values are of three kinds: - String literals - Lists (indexed by integer) - Tuple (set of named values, indexed by name) The structure of response to a specific gdb command is fixed. While any tuples in response may omit certain named fields, the kind of each item never changes. That is, response to specific command can't contains sometimes string and sometimes tuple in specific position. Because of that static structure, it's almost never needed to query dynamic type of a MI value. Most often we know it's say, tuple, and can subscripts it. So, the Value class has methods for accessing all kinds of values. Attempting to call a method that is not applicable to specific value will result in exception. The client code will almost never need to cast from 'Value' to its derived classes. Note also that all methods in this class are const and return const Value&. That's by design -- there's no need to modify gdb responses in GUI. */ struct Value { Value() : kind(StringLiteral) {} private: // Copy disabled to prevent slicing. Value(const Value&); Value& operator=(const Value&); public: virtual ~Value() {} enum { StringLiteral, Tuple, List } kind; /** If this value is a string literals, returns the string value. Othewise, throws type_error. */ virtual QString literal() const; //NOTE: Wouldn't it be better to use literal().toInt and get rid of that? /** If the value is a string literal, converts it to int and returns. If conversion fails, or the value cannot be converted to int, throws type_error. */ virtual int toInt(int base = 10) const; /** If this value is a tuple, returns true if the tuple has a field named 'variable'. Otherwise, throws type_error. */ virtual bool hasField(const QString& variable) const; /** If this value is a tuple, and contains named field 'variable', returns it. Otherwise, throws 'type_error'. This method is virtual, and derived in base class, so that we can save on casting, when we know for sure that instance is TupleValue, or ListValue. */ virtual const Value& operator[](const QString& variable) const; /** If this value is a list, returns true if the list is empty. If this value is not a list, throws 'type_error'. */ virtual bool empty() const; /** If this value is a list, returns it's size. Otherwise, throws 'type_error'. */ virtual int size() const; /** If this value is a list, returns the element at 'index'. Otherwise, throws 'type_error'. */ virtual const Value& operator[](int index) const; }; /** @internal Internal class to represent name-value pair in tuples. */ struct Result { Result() : value(0) {} ~Result() { delete value; value = 0; } QString variable; Value *value; }; struct StringLiteralValue : public Value { StringLiteralValue(const QString &lit) : literal_(lit) { Value::kind = StringLiteral; } public: // Value overrides QString literal() const override; int toInt(int base) const override; private: QString literal_; }; struct TupleValue : public Value { TupleValue() { Value::kind = Tuple; } ~TupleValue(); bool hasField(const QString&) const override; using Value::operator[]; const Value& operator[](const QString& variable) const override; QList results; QMap results_by_name; }; struct ListValue : public Value { ListValue() { Value::kind = List; } ~ListValue(); bool empty() const override; int size() const override; using Value::operator[]; const Value& operator[](int index) const override; QList results; }; struct Record { virtual ~Record() {} virtual QString toString() const { Q_ASSERT( 0 ); return QString::null; } enum { Prompt, Stream, Result, Async } kind; }; struct TupleRecord : public Record, public TupleValue { }; struct ResultRecord : public TupleRecord { ResultRecord(const QString& reason) : token(0) , reason(reason) { Record::kind = Result; } uint32_t token; QString reason; }; struct AsyncRecord : public TupleRecord { enum Subkind { Exec, Status, Notify }; AsyncRecord(Subkind subkind, const QString& reason) : subkind(subkind) , reason(reason) { Record::kind = Async; } Subkind subkind; QString reason; }; struct PromptRecord : public Record { inline PromptRecord() { Record::kind = Prompt; } virtual QString toString() const override { return "(prompt)\n"; } }; struct StreamRecord : public Record { enum Subkind { /// Console stream: usual CLI output of GDB in response to non-MI commands Console, /// Target output stream (stdout/stderr of the inferior process, only in some /// scenarios - usually we get stdout/stderr via other means) Target, /// Log stream: GDB internal messages that should be displayed as part of an error log Log }; StreamRecord(Subkind subkind) : subkind(subkind) { Record::kind = Stream; } Subkind subkind; QString message; }; } // end of namespace MI } // end of namespace KDevMI #endif diff --git a/debuggers/common/mi/micommand.cpp b/debuggers/common/mi/micommand.cpp index b726629a28..d07bf673a8 100644 --- a/debuggers/common/mi/micommand.cpp +++ b/debuggers/common/mi/micommand.cpp @@ -1,637 +1,489 @@ /*************************************************************************** begin : Sun Aug 8 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org ***************************************************************************/ /*************************************************************************** * * * 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) any later version. * * * ***************************************************************************/ #include "micommand.h" #include using namespace KDevMI::MI; FunctionCommandHandler::FunctionCommandHandler(const FunctionCommandHandler::Function& callback, CommandFlags flags) : _flags(flags) , _callback(callback) { } bool FunctionCommandHandler::handlesError() { return _flags & CmdHandlesError; } void FunctionCommandHandler::handle(const ResultRecord& r) { _callback(r); } MICommand::MICommand(CommandType type, const QString& command, CommandFlags flags) : type_(type) , flags_(flags) , command_(command) , commandHandler_(nullptr) , stateReloading_(false) , m_thread(-1) , m_frame(-1) { } MICommand::~MICommand() { if (commandHandler_ && commandHandler_->autoDelete()) { delete commandHandler_; } commandHandler_ = nullptr; } QString MICommand::cmdToSend() { return initialString() + '\n'; } QString MICommand::initialString() const { QString result = QString::number(token()); if (type() == NonMI) { result += command_; } else { result += miCommand(); if (m_thread != -1) result = result + QString(" --thread %1").arg(m_thread); if (m_frame != -1) result = result + QString(" --frame %1").arg(m_frame); if (!command_.isEmpty()) result += ' ' + command_; } return result; } bool MICommand::isUserCommand() const { return false; } void MICommand::setHandler(MICommandHandler* handler) { if (commandHandler_ && commandHandler_->autoDelete()) delete commandHandler_; commandHandler_ = handler; if (!commandHandler_) { flags_ = flags_ & ~CmdHandlesError; } } void MICommand::setHandler(const FunctionCommandHandler::Function& callback) { setHandler(new FunctionCommandHandler(callback, flags())); } bool MICommand::invokeHandler(const ResultRecord& r) { if (commandHandler_) { //ask before calling handler as it might deleted itself in handler bool autoDelete = commandHandler_->autoDelete(); commandHandler_->handle(r); if (autoDelete) { delete commandHandler_; } commandHandler_ = 0; return true; } else { return false; } } void MICommand::newOutput(const QString& line) { lines.push_back(line); } const QStringList& MICommand::allStreamOutput() const { return lines; } bool MICommand::handlesError() const { return commandHandler_ ? commandHandler_->handlesError() : false; } UserCommand::UserCommand(CommandType type, const QString& s) : MICommand(type, s, CmdMaybeStartsRunning) { } bool UserCommand::isUserCommand() const { return true; } QString MICommand::miCommand() const { QString command; switch (type()) { case NonMI: command = ""; break; case BreakAfter: command = "break-after";//"ignore" break; - case BreakCatch: // FIXME: non-exist command - command = "break-catch"; - break; case BreakCommands: command = "break-commands"; break; case BreakCondition: command = "break-condition";//"cond" break; case BreakDelete: command = "break-delete";//"delete breakpoint" break; case BreakDisable: command = "break-disable";//"disable breakpoint" break; case BreakEnable: command = "break-enable";//"enable breakpoint" break; case BreakInfo: command = "break-info";//"info break" break; case BreakInsert: command = "break-insert -f"; break; case BreakList: command = "break-list";//"info break" break; case BreakWatch: command = "break-watch"; break; case DataDisassemble: command = "data-disassemble"; break; case DataEvaluateExpression: command = "data-evaluate-expression"; break; case DataListChangedRegisters: command = "data-list-changed-registers"; break; case DataListRegisterNames: command = "data-list-register-names"; break; case DataListRegisterValues: command = "data-list-register-values"; break; case DataReadMemory: command = "data-read-memory"; break; case DataWriteMemory: command = "data-write-memory"; break; case DataWriteRegisterVariables: command = "data-write-register-values"; break; case EnablePrettyPrinting: command = "enable-pretty-printing"; break; case EnableTimings: command = "enable-timings"; break; case EnvironmentCd: command = "environment-cd"; break; case EnvironmentDirectory: command = "environment-directory"; break; case EnvironmentPath: command = "environment-path"; break; case EnvironmentPwd: command = "environment-pwd"; break; case ExecAbort: command = "exec-abort"; break; case ExecArguments: command = "exec-arguments";//"set args" break; case ExecContinue: command = "exec-continue"; break; case ExecFinish: command = "exec-finish"; break; case ExecInterrupt: command = "exec-interrupt"; break; case ExecNext: command = "exec-next"; break; case ExecNextInstruction: command = "exec-next-instruction"; break; - case ExecReturn: - command = "exec-command ="; - break; case ExecRun: command = "exec-run"; break; - case ExecShowArguments: - command = "exec-show-arguments"; - break; - case ExecSignal: - command = "exec-signal"; - break; case ExecStep: command = "exec-step"; break; case ExecStepInstruction: command = "exec-step-instruction"; break; case ExecUntil: command = "exec-until"; break; - case FileClear: - command = "file-clear"; - break; case FileExecAndSymbols: command = "file-exec-and-symbols";//"file" break; case FileExecFile: command = "file-exec-file";//"exec-file" break; - case FileListExecSections: - 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: - command = "file-list-shared-libraries"; - break; - case FileListSymbolFiles: - command = "file-list-symbol-files"; - break; case FileSymbolFile: command = "file-symbol-file";//"symbol-file" break; - case GdbComplete: - command = "gdb-complete"; - break; case GdbExit: command = "gdb-exit"; break; case GdbSet: command = "gdb-set";//"set" break; case GdbShow: command = "gdb-show";//"show" break; - case GdbSource: - command = "gdb-source"; - break; case GdbVersion: command = "gdb-version";//"show version" break; case InferiorTtySet: command = "inferior-tty-set"; break; case InferiorTtyShow: command = "inferior-tty-show"; break; case InterpreterExec: command = "interpreter-exec"; break; case ListFeatures: command = "list-features"; break; - case OverlayAuto: - command = "overlay-auto"; - break; - case OverlayListMappingState: - command = "overlay-list-mapping-state"; - break; - case OverlayListOverlays: - command = "overlay-list-overlays"; - break; - case OverlayMap: - command = "overlay-map"; - break; - case OverlayOff: - command = "overlay-off"; - break; - case OverlayOn: - command = "overlay-on"; - break; - case OverlayUnmap: - command = "overlay-unmap"; - break; - case SignalHandle: return "handle"; //command = "signal-handle"; break; - case SignalListHandleActions: - command = "signal-list-handle-actions"; - break; - case SignalListSignalTypes: - command = "signal-list-signal-types"; - break; case StackInfoDepth: command = "stack-info-depth"; break; case StackInfoFrame: command = "stack-info-frame"; break; case StackListArguments: command = "stack-list-arguments"; break; - case StackListExceptionHandlers: - command = "stack-list-exception-handlers"; - break; case StackListFrames: command = "stack-list-frames"; break; case StackListLocals: command = "stack-list-locals"; break; case StackSelectFrame: command = "stack-select-frame"; break; - case SymbolInfoAddress: - command = "symbol-info-address"; - break; - case SymbolInfoFile: - command = "symbol-info-file"; - break; - case SymbolInfoFunction: - command = "symbol-info-function"; - break; - case SymbolInfoLine: - command = "symbol-info-line"; - break; - case SymbolInfoSymbol: - command = "symbol-info-symbol"; - break; - case SymbolListFunctions: - command = "symbol-list-functions"; - break; case SymbolListLines: command = "symbol-list-lines"; break; - case SymbolListTypes: - command = "symbol-list-types"; - break; - case SymbolListVariables: - command = "symbol-list-variables"; - break; - case SymbolLocate: - command = "symbol-locate"; - break; - case SymbolType: - command = "symbol-type"; - break; case TargetAttach: command = "target-attach"; break; - case TargetCompareSections: - command = "target-compare-sections"; - break; case TargetDetach: command = "target-detach";//"detach" break; case TargetDisconnect: command = "target-disconnect";//"disconnect" break; case TargetDownload: command = "target-download"; break; - case TargetExecStatus: - command = "target-exec-status"; - break; - case TargetListAvailableTargets: - command = "target-list-available-targets"; - break; - case TargetListCurrentTargets: - command = "target-list-current-targets"; - break; - case TargetListParameters: - command = "target-list-parameters"; - break; case TargetSelect: command = "target-select"; break; case ThreadInfo: command = "thread-info"; break; - case ThreadListAllThreads: - command = "thread-list-all-threads"; - break; case ThreadListIds: command = "thread-list-ids"; break; case ThreadSelect: command = "thread-select"; break; - case TraceActions: - command = "trace-actions"; - break; - case TraceDelete: - command = "trace-delete"; - break; - case TraceDisable: - command = "trace-disable"; - break; - case TraceDump: - command = "trace-dump"; - break; - case TraceEnable: - command = "trace-enable"; - break; - case TraceExists: - command = "trace-exists"; - break; case TraceFind: command = "trace-find"; break; - case TraceFrameNumber: - command = "trace-frame-number"; - break; - case TraceInfo: - command = "trace-info"; - break; - case TraceInsert: - command = "trace-insert"; - break; - case TraceList: - command = "trace-list"; - break; - case TracePassCount: - command = "trace-pass-count"; - break; - case TraceSave: - command = "trace-save"; - break; case TraceStart: command = "trace-start"; break; case TraceStop: command = "trace-stop"; break; case VarAssign: command = "var-assign"; break; case VarCreate: command = "var-create"; break; case VarDelete: command = "var-delete"; break; case VarEvaluateExpression: command = "var-evaluate-expression"; break; case VarInfoPathExpression: command = "var-info-path-expression"; break; - case VarInfoExpression: - command = "var-info-expression"; - break; case VarInfoNumChildren: command = "var-info-num-children"; break; case VarInfoType: command = "var-info-type"; break; case VarListChildren: command = "var-list-children"; break; case VarSetFormat: command = "var-set-format"; break; case VarSetFrozen: command = "var-set-frozen"; break; case VarShowAttributes: command = "var-show-attributes"; break; case VarShowFormat: command = "var-show-format"; break; case VarUpdate: command = "var-update"; break; default: command = "unknown"; break; } return '-' + command; } CommandType MICommand::type() const { return type_; } int MICommand::thread() const { return m_thread; } void MICommand::setThread(int thread) { m_thread = thread; } int MICommand::frame() const { return m_frame; } void MICommand::setFrame(int frame) { m_frame = frame; } QString MICommand::command() const { return command_; } void MICommand::setStateReloading(bool f) { stateReloading_ = f; } bool MICommand::stateReloading() const { return stateReloading_; } void MICommand::markAsEnqueued() { m_enqueueTimestamp = QDateTime::currentMSecsSinceEpoch(); } void MICommand::markAsSubmitted() { m_submitTimestamp = QDateTime::currentMSecsSinceEpoch(); } void MICommand::markAsCompleted() { m_completeTimestamp = QDateTime::currentMSecsSinceEpoch(); } qint64 MICommand::gdbProcessingTime() const { return m_completeTimestamp - m_submitTimestamp; } qint64 MICommand::queueTime() const { return m_submitTimestamp - m_enqueueTimestamp; } qint64 MICommand::totalProcessingTime() const { return m_completeTimestamp - m_enqueueTimestamp; } diff --git a/debuggers/common/mi/micommandqueue.cpp b/debuggers/common/mi/micommandqueue.cpp index 29586293f6..fe2c05179d 100644 --- a/debuggers/common/mi/micommandqueue.cpp +++ b/debuggers/common/mi/micommandqueue.cpp @@ -1,143 +1,142 @@ // ************************************************************************* // gdbcommandqueue.cpp // ------------------- // begin : Wed Dec 5, 2007 // copyright : (C) 2007 by Hamish Rodda // email : rodda@kde.org // ************************************************************************** // // ************************************************************************** // * * // * 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) any later version. * // * * // ************************************************************************** #include "micommandqueue.h" #include "mi.h" #include "micommand.h" #include "debuglog.h" using namespace KDevMI::MI; CommandQueue::CommandQueue() : m_tokenCounter(0) { } CommandQueue::~CommandQueue() { qDeleteAll(m_commandList); } void CommandQueue::enqueue(MICommand* command) { ++m_tokenCounter; if (m_tokenCounter == 0) m_tokenCounter = 1; command->setToken(m_tokenCounter); // take the time when this command was added to the command queue command->markAsEnqueued(); m_commandList.append(command); if (command->flags() & (CmdImmediately | CmdInterrupt)) ++m_immediatelyCounter; rationalizeQueue(command); dumpQueue(); } void CommandQueue::dumpQueue() { qCDebug(DEBUGGERCOMMON) << "Pending commands" << m_commandList.count(); unsigned commandNum = 0; foreach(const MICommand* command, m_commandList) { qCDebug(DEBUGGERCOMMON) << "Command" << commandNum << command->initialString(); ++commandNum; } } void CommandQueue::rationalizeQueue(MICommand* command) { if ((command->type() >= ExecAbort && command->type() <= ExecUntil) && - command->type() != ExecArguments && - command->type() != ExecShowArguments ) { + command->type() != ExecArguments ) { // Changing execution location, abort any variable updates removeVariableUpdates(); // ... and stack list updates removeStackListUpdates(); } } void CommandQueue::removeVariableUpdates() { QMutableListIterator it = m_commandList; while (it.hasNext()) { MICommand* command = it.next(); CommandType type = command->type(); if ((type >= VarEvaluateExpression && type <= VarListChildren) || type == VarUpdate) { if (command->flags() & (CmdImmediately | CmdInterrupt)) --m_immediatelyCounter; it.remove(); delete command; } } } void CommandQueue::removeStackListUpdates() { QMutableListIterator it = m_commandList; while (it.hasNext()) { MICommand* command = it.next(); CommandType type = command->type(); if (type >= StackListArguments && type <= StackListLocals) { if (command->flags() & (CmdImmediately | CmdInterrupt)) --m_immediatelyCounter; it.remove(); delete command; } } } void CommandQueue::clear() { qDeleteAll(m_commandList); m_commandList.clear(); m_immediatelyCounter = 0; } int CommandQueue::count() const { return m_commandList.count(); } bool CommandQueue::isEmpty() const { return m_commandList.isEmpty(); } bool CommandQueue::haveImmediateCommand() const { return m_immediatelyCounter > 0; } MICommand* CommandQueue::nextCommand() { if (m_commandList.isEmpty()) return nullptr; MICommand* command = m_commandList.takeAt(0); if (command->flags() & (CmdImmediately | CmdInterrupt)) --m_immediatelyCounter; return command; } diff --git a/debuggers/common/mi/miparser.cpp b/debuggers/common/mi/miparser.cpp index b5e2510589..165af5f863 100644 --- a/debuggers/common/mi/miparser.cpp +++ b/debuggers/common/mi/miparser.cpp @@ -1,377 +1,380 @@ /*************************************************************************** * Copyright (C) 2004 by Roberto Raggi * * roberto@kdevelop.org * * Copyright (C) 2005-2006 by Vladimir Prus * * ghost@cs.msu.su * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "miparser.h" #include "tokens.h" using namespace KDevMI::MI; #define MATCH(tok) \ do { \ if (m_lex->lookAhead(0) != (tok)) \ return false; \ } while (0) #define ADVANCE(tok) \ do { \ MATCH(tok); \ m_lex->nextToken(); \ } while (0) #define MATCH_PTR(tok) \ do { \ if (m_lex->lookAhead(0) != (tok)) \ return {}; \ } while (0) #define ADVANCE_PTR(tok) \ do { \ MATCH_PTR(tok); \ m_lex->nextToken(); \ } while (0) MIParser::MIParser() : m_lex(0) { } MIParser::~MIParser() { } std::unique_ptr MIParser::parse(FileSymbol *file) { m_lex = 0; TokenStream *tokenStream = m_lexer.tokenize(file); if (!tokenStream) return 0; m_lex = file->tokenStream = tokenStream; uint32_t token = 0; if (m_lex->lookAhead() == Token_number_literal) { token = QString::fromUtf8(m_lex->currentTokenText()).toUInt(); m_lex->nextToken(); } std::unique_ptr record; switch (m_lex->lookAhead()) { case '~': case '@': case '&': record = parseStreamRecord(); break; case '(': record = parsePrompt(); break; case '^': case '*': case '=': case '+': record = parseResultOrAsyncRecord(); break; default: break; } if (record && record->kind == Record::Result) { ResultRecord * result = static_cast(record.get()); result->token = token; } else { Q_ASSERT(token == 0); } return record; } std::unique_ptr MIParser::parsePrompt() { ADVANCE_PTR('('); MATCH_PTR(Token_identifier); if (m_lex->currentTokenText() != "gdb") return {}; m_lex->nextToken(); ADVANCE_PTR(')'); return std::unique_ptr(new PromptRecord); } std::unique_ptr MIParser::parseStreamRecord() { StreamRecord::Subkind subkind; switch (m_lex->lookAhead()) { case '~': subkind = StreamRecord::Console; break; case '@': subkind = StreamRecord::Target; break; case '&': subkind = StreamRecord::Log; break; default: Q_ASSERT(false); return {}; } std::unique_ptr stream(new StreamRecord(subkind)); m_lex->nextToken(); MATCH_PTR(Token_string_literal); stream->message = parseStringLiteral(); return std::move(stream); } std::unique_ptr MIParser::parseResultOrAsyncRecord() { std::unique_ptr result; char c = m_lex->lookAhead(); m_lex->nextToken(); MATCH_PTR(Token_identifier); QString reason = m_lex->currentTokenText(); m_lex->nextToken(); if (c == '^') { result.reset(new ResultRecord(reason)); } else { AsyncRecord::Subkind subkind; switch (c) { case '*': subkind = AsyncRecord::Exec; break; case '=': subkind = AsyncRecord::Notify; break; case '+': subkind = AsyncRecord::Status; break; default: Q_ASSERT(false); return {}; } result.reset(new AsyncRecord(subkind, reason)); } if (m_lex->lookAhead() == ',') { m_lex->nextToken(); if (!parseCSV(*result)) return {}; } return std::move(result); } bool MIParser::parseResult(Result *&result) { // be less strict about the format, see e.g.: // https://bugs.kde.org/show_bug.cgi?id=304730 // http://sourceware.org/bugzilla/show_bug.cgi?id=9659 std::unique_ptr res(new Result); if (m_lex->lookAhead() == Token_identifier) { res->variable = m_lex->currentTokenText(); m_lex->nextToken(); if (m_lex->lookAhead() != '=') { result = res.release(); return true; } m_lex->nextToken(); } Value *value = 0; if (!parseValue(value)) return false; res->value = value; result = res.release(); return true; } bool MIParser::parseValue(Value *&value) { value = 0; switch (m_lex->lookAhead()) { case Token_string_literal: { value = new StringLiteralValue(parseStringLiteral()); } return true; case '{': return parseTuple(value); case '[': return parseList(value); default: break; } return false; } bool MIParser::parseTuple(Value *&value) { TupleValue* val; if (!parseCSV(&val, '{', '}')) return false; value = val; return true; } bool MIParser::parseList(Value *&value) { ADVANCE('['); std::unique_ptr lst(new ListValue); // Note: can't use parseCSV here because of nested // "is this Value or Result" guessing. Too lazy to factor // that out too using function pointers. int tok = m_lex->lookAhead(); while (tok && tok != ']') { Result *result = 0; Value *val = 0; if (tok == Token_identifier) { if (!parseResult(result)) return false; } else if (!parseValue(val)) return false; Q_ASSERT(result || val); if (!result) { result = new Result; result->value = val; } lst->results.append(result); if (m_lex->lookAhead() == ',') m_lex->nextToken(); tok = m_lex->lookAhead(); } ADVANCE(']'); value = lst.release(); return true; } bool MIParser::parseCSV(TupleValue** value, char start, char end) { std::unique_ptr tuple(new TupleValue); if (!parseCSV(*tuple, start, end)) return false; *value = tuple.get(); tuple.release(); return true; } bool MIParser::parseCSV(TupleValue& value, char start, char end) { if (start) ADVANCE(start); int tok = m_lex->lookAhead(); while (tok) { if (end && tok == end) break; Result *result; if (!parseResult(result)) return false; value.results.append(result); value.results_by_name.insert(result->variable, result); if (m_lex->lookAhead() == ',') m_lex->nextToken(); tok = m_lex->lookAhead(); } if (end) ADVANCE(end); return true; } QString MIParser::parseStringLiteral() { QByteArray messageByteArray = m_lex->currentTokenText(); QString message = QString::fromUtf8(messageByteArray.constData()); int length = message.length(); QString message2; message2.reserve(length); // The [1,length-1] range removes quotes without extra // call to 'mid' int target_index = 0; for(int i = 1, e = length-1; i != e; ++i) { int translated = -1; if (message[i] == '\\') { if (i+1 < length) { // TODO: implement all the other escapes, maybe if (message[i+1] == 'n') { translated = '\n'; } else if (message[i+1] == '\\') { translated = '\\'; } else if (message[i+1] == '"') { translated = '"'; } else if (message[i+1] == 't') { translated = '\t'; } - + else if (message[i+1] == 'r') + { + translated = '\r'; + } } } if (translated != -1) { message2[target_index++] = translated; ++i; } else { message2[target_index++] = message[i]; } } m_lex->nextToken(); return message2; } diff --git a/debuggers/common/midebugger.cpp b/debuggers/common/midebugger.cpp index a4f6914807..72b04eb41e 100644 --- a/debuggers/common/midebugger.cpp +++ b/debuggers/common/midebugger.cpp @@ -1,344 +1,350 @@ /* * Low level GDB interface. * * Copyright 1999 John Birch * Copyright 2007 Vladimir Prus * 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) any later version. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "midebugger.h" #include "debuglog.h" #include "mi/micommand.h" #include #include #include #include #include #include #include // #define DEBUG_NO_TRY //to get a backtrace to where the exception was thrown using namespace KDevMI; using namespace KDevMI::MI; MIDebugger::MIDebugger(QObject* parent) : QObject(parent) , process_(nullptr) , currentCmd_(nullptr) { process_ = new KProcess(this); process_->setOutputChannelMode(KProcess::SeparateChannels); connect(process_, &KProcess::readyReadStandardOutput, this, &MIDebugger::readyReadStandardOutput); connect(process_, &KProcess::readyReadStandardError, this, &MIDebugger::readyReadStandardError); connect(process_, static_cast(&KProcess::finished), this, &MIDebugger::processFinished); connect(process_, static_cast(&KProcess::error), this, &MIDebugger::processErrored); } MIDebugger::~MIDebugger() { // prevent Qt warning: QProcess: Destroyed while process is still running. if (process_ && process_->state() == QProcess::Running) { disconnect(process_, static_cast(&KProcess::error), this, &MIDebugger::processErrored); process_->kill(); process_->waitForFinished(10); } } void MIDebugger::execute(MICommand* command) { currentCmd_ = command; QString commandText = currentCmd_->cmdToSend(); qCDebug(DEBUGGERCOMMON) << "SEND:" << commandText.trimmed(); QByteArray commandUtf8 = commandText.toUtf8(); process_->write(commandUtf8, commandUtf8.length()); command->markAsSubmitted(); QString prettyCmd = currentCmd_->cmdToSend(); prettyCmd.remove( QRegExp("set prompt \032.\n") ); prettyCmd = "(gdb) " + prettyCmd; if (currentCmd_->isUserCommand()) emit userCommandOutput(prettyCmd); else emit internalCommandOutput(prettyCmd); } bool MIDebugger::isReady() const { return currentCmd_ == 0; } void MIDebugger::interrupt() { //TODO:win32 Porting needed int pid = process_->pid(); if (pid != 0) { ::kill(pid, SIGINT); } } MICommand* MIDebugger::currentCommand() const { return currentCmd_; } void MIDebugger::kill() { process_->kill(); } void MIDebugger::readyReadStandardOutput() { process_->setReadChannel(QProcess::StandardOutput); buffer_ += process_->readAll(); for (;;) { /* In MI mode, all messages are exactly one line. See if we have any complete lines in the buffer. */ int i = buffer_.indexOf('\n'); if (i == -1) break; QByteArray reply(buffer_.left(i)); buffer_ = buffer_.mid(i+1); processLine(reply); } } void MIDebugger::readyReadStandardError() { process_->setReadChannel(QProcess::StandardError); emit debuggerInternalOutput(QString::fromUtf8(process_->readAll())); } void MIDebugger::processLine(const QByteArray& line) { qCDebug(DEBUGGERCOMMON) << "Debugger (" << process_->pid() <<") output: " << line; FileSymbol file; file.contents = line; std::unique_ptr r(mi_parser_.parse(&file)); if (!r) { // FIXME: Issue an error! qCDebug(DEBUGGERCOMMON) << "Invalid MI message:" << line; // We don't consider the current command done. // So, if a command results in unparseable reply, // we'll just wait for the "right" reply, which might // never come. However, marking the command as // done in this case is even more risky. // It's probably possible to get here if we're debugging // natively without PTY, though this is uncommon case. return; } #ifndef DEBUG_NO_TRY try { #endif switch(r->kind) { 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") { if (!currentCmd_) { qCDebug(DEBUGGERCOMMON) << "Received a result without a pending command"; } else { qCDebug(DEBUGGERCOMMON) << "Result token is" << result.token; Q_ASSERT(currentCmd_->token() == result.token); currentCmd_->markAsCompleted(); qCDebug(DEBUGGERCOMMON) << "Command successful, times " << currentCmd_->totalProcessingTime() << currentCmd_->queueTime() << currentCmd_->gdbProcessingTime(); currentCmd_->invokeHandler(result); } } else if (result.reason == "error") { qCDebug(DEBUGGERCOMMON) << "Handling error"; currentCmd_->markAsCompleted(); qCDebug(DEBUGGERCOMMON) << "Command error, times" << currentCmd_->totalProcessingTime() << currentCmd_->queueTime() << currentCmd_->gdbProcessingTime(); // Some commands want to handle errors themself. if (currentCmd_->handlesError() && currentCmd_->invokeHandler(result)) { qCDebug(DEBUGGERCOMMON) << "Invoked custom handler\n"; // Done, nothing more needed } else emit error(result); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled result code: " << result.reason; } delete currentCmd_; currentCmd_ = nullptr; emit ready(); break; } case MI::Record::Async: { MI::AsyncRecord& async = dynamic_cast(*r); switch (async.subkind) { case MI::AsyncRecord::Exec: { // Prefix '*'; asynchronous state changes of the target if (async.reason == "stopped") { emit programStopped(async); } else if (async.reason == "running") { emit programRunning(); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled exec notification: " << async.reason; } break; } case MI::AsyncRecord::Notify: { // Prefix '='; supplementary information that we should handle (new breakpoint etc.) emit notification(async); break; } case MI::AsyncRecord::Status: { // Prefix '+'; GDB documentation: // On-going status information about progress of a slow operation; may be ignored break; } default: Q_ASSERT(false); } break; } case MI::Record::Stream: { MI::StreamRecord& s = dynamic_cast(*r); if (s.subkind == MI::StreamRecord::Target) { emit applicationOutput(s.message); } else if (s.subkind == MI::StreamRecord::Console) { if (currentCmd_ && currentCmd_->isUserCommand()) emit userCommandOutput(s.message); else emit internalCommandOutput(s.message); if (currentCmd_) currentCmd_->newOutput(s.message); } else { emit debuggerInternalOutput(s.message); } emit streamRecord(s); break; } case MI::Record::Prompt: break; } #ifndef DEBUG_NO_TRY } catch(const std::exception& e) { KMessageBox::detailedSorry( qApp->activeWindow(), i18nc("Internal debugger error", "

The debugger component encountered internal error while " "processing reply from gdb. Please submit a bug report."), i18n("The exception is: %1\n" "The MI response is: %2", e.what(), QString::fromLatin1(line)), i18n("Internal debugger error")); } #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(), i18n("Could not start debugger." "

Could not run '%1'. " "Make sure that the path name is specified correctly.", 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.cpp b/debuggers/common/midebuggerplugin.cpp index e702d08c4a..048b695975 100644 --- a/debuggers/common/midebuggerplugin.cpp +++ b/debuggers/common/midebuggerplugin.cpp @@ -1,310 +1,274 @@ /* * Common code for MI debugger support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * 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 "midebuggerplugin.h" #include "midebugjobs.h" #include "dialogs/processselection.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; using namespace KDevMI; MIDebuggerPlugin::MIDebuggerPlugin(const QString &componentName, QObject *parent) : KDevelop::IPlugin(componentName, parent) { KDEV_USE_EXTENSION_INTERFACE(KDevelop::IStatus) 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(); QAction * action = new QAction(this); action->setIcon(QIcon::fromTheme("core")); action->setText(i18n("Examine Core File...")); action->setToolTip(i18n("Examine core file")); action->setWhatsThis(i18n("Examine core file" "

This loads a core file, which is typically created " "after the application has crashed, e.g. with a " "segmentation fault. The core file contains an " "image of the program memory at the time it crashed, " "allowing you to do a post-mortem analysis.

")); connect(action, &QAction::triggered, this, &MIDebuggerPlugin::slotExamineCore); ac->addAction("debug_core", action); #if KF5SysGuard_FOUND action = new QAction(this); action->setIcon(QIcon::fromTheme("connect_creating")); action->setText(i18n("Attach to Process...")); action->setToolTip(i18n("Attach to process")); action->setWhatsThis(i18n("Attach to process" "

Attaches the debugger to a running process.

")); connect(action, &QAction::triggered, this, &MIDebuggerPlugin::slotAttachProcess); ac->addAction("debug_attach", action); #endif } void MIDebuggerPlugin::setupDBus() { m_drkonqiMap = new QSignalMapper(this); connect(m_drkonqiMap, static_cast(&QSignalMapper::mapped), this, &MIDebuggerPlugin::slotDebugExternalProcess); QDBusConnectionInterface* dbusInterface = QDBusConnection::sessionBus().interface(); for (const auto &service : dbusInterface->registeredServiceNames().value()) { slotDBusServiceRegistered(service); } QDBusServiceWatcher* watcher = new QDBusServiceWatcher(this); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &MIDebuggerPlugin::slotDBusServiceRegistered); connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &MIDebuggerPlugin::slotDBusServiceUnregistered); } void MIDebuggerPlugin::unload() { - // TODO: port tool views - /* - core()->uiController()->removeToolView(disassemblefactory); - core()->uiController()->removeToolView(lldbfactory); - core()->uiController()->removeToolView(memoryviewerfactory); - */ + unloadToolviews(); } MIDebuggerPlugin::~MIDebuggerPlugin() { } void MIDebuggerPlugin::slotDBusServiceRegistered(const QString& service) { if (service.startsWith("org.kde.drkonqi")) { // New registration QDBusInterface* drkonqiInterface = new QDBusInterface(service, "/krashinfo", QString(), QDBusConnection::sessionBus(), this); m_drkonqis.insert(service, drkonqiInterface); connect(drkonqiInterface, SIGNAL(acceptDebuggingApplication()), m_drkonqiMap, SLOT(map())); m_drkonqiMap->setMapping(drkonqiInterface, drkonqiInterface); drkonqiInterface->call("registerDebuggingApplication", i18n("KDevelop")); } } void MIDebuggerPlugin::slotDBusServiceUnregistered(const QString& service) { if (service.startsWith("org.kde.drkonqi")) { // Deregistration if (m_drkonqis.contains(service)) delete m_drkonqis.take(service); } } void MIDebuggerPlugin::slotDebugExternalProcess(QObject* interface) { auto dbusInterface = static_cast(interface); QDBusReply reply = dbusInterface->call("pid"); if (reply.isValid()) { attachProcess(reply.value()); QTimer::singleShot(500, this, &MIDebuggerPlugin::slotCloseDrKonqi); m_drkonqi = m_drkonqis.key(dbusInterface); } core()->uiController()->activeMainWindow()->raise(); } void MIDebuggerPlugin::slotCloseDrKonqi() { if (!m_drkonqi.isEmpty()) { QDBusInterface drkonqiInterface(m_drkonqi, "/MainApplication", "org.kde.KApplication"); drkonqiInterface.call("quit"); m_drkonqi.clear(); } } ContextMenuExtension MIDebuggerPlugin::contextMenuExtension(Context* context) { ContextMenuExtension menuExt = IPlugin::contextMenuExtension(context); if (context->type() != KDevelop::Context::EditorContext) return menuExt; EditorContext *econtext = dynamic_cast(context); if (!econtext) return menuExt; QString contextIdent = econtext->currentWord(); if (!contextIdent.isEmpty()) { QString squeezed = KStringHandler::csqueeze(contextIdent, 30); QAction* action = new QAction(this); action->setText(i18n("Evaluate: %1", squeezed)); action->setWhatsThis(i18n("Evaluate expression" "

Shows the value of the expression under the cursor.

")); connect(action, &QAction::triggered, this, [this, contextIdent](){ emit addWatchVariable(contextIdent); }); menuExt.addAction(ContextMenuExtension::DebugGroup, action); action = new QAction(this); action->setText(i18n("Watch: %1", squeezed)); action->setWhatsThis(i18n("Watch expression" "

Adds the expression under the cursor to the Variables/Watch list.

")); connect(action, &QAction::triggered, this, [this, contextIdent](){ emit evaluateExpression(contextIdent); }); menuExt.addAction(ContextMenuExtension::DebugGroup, action); } return menuExt; } void MIDebuggerPlugin::slotExamineCore() { showStatusMessage(i18n("Choose a core file to examine..."), 1000); if (core()->debugController()->currentSession() != nullptr) { KMessageBox::ButtonCode answer = KMessageBox::warningYesNo( core()->uiController()->activeMainWindow(), i18n("A program is already being debugged. Do you want to abort the " "currently running debug session and continue?")); if (answer == KMessageBox::No) return; } MIExamineCoreJob *job = new MIExamineCoreJob(this, core()->runController()); core()->runController()->registerJob(job); // job->start() is called in registerJob } #if KF5SysGuard_FOUND void MIDebuggerPlugin::slotAttachProcess() { showStatusMessage(i18n("Choose a process to attach to..."), 1000); if (core()->debugController()->currentSession() != nullptr) { KMessageBox::ButtonCode answer = KMessageBox::warningYesNo( core()->uiController()->activeMainWindow(), i18n("A program is already being debugged. Do you want to abort the " "currently running debug session and continue?")); if (answer == KMessageBox::No) return; } ProcessSelectionDialog dlg(core()->uiController()->activeMainWindow()); if (!dlg.exec() || !dlg.pidSelected()) return; // TODO: move check into process selection dialog int pid = dlg.pidSelected(); if (QApplication::applicationPid() == pid) KMessageBox::error(core()->uiController()->activeMainWindow(), i18n("Not attaching to process %1: cannot attach the debugger to itself.", pid)); else attachProcess(pid); } #endif void MIDebuggerPlugin::attachProcess(int pid) { MIAttachProcessJob *job = new MIAttachProcessJob(this, pid, core()->runController()); core()->runController()->registerJob(job); // job->start() is called in registerJob } QString MIDebuggerPlugin::statusName() const { return i18n("Debugger"); } void MIDebuggerPlugin::showStatusMessage(const QString& msg, int timeout) { emit showMessage(this, msg, timeout); } diff --git a/debuggers/common/midebuggerplugin.h b/debuggers/common/midebuggerplugin.h index ba9bab996d..0883cc64c9 100644 --- a/debuggers/common/midebuggerplugin.h +++ b/debuggers/common/midebuggerplugin.h @@ -1,158 +1,156 @@ /* * Common code for MI debugger support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * 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 MIDEBUGGERPLUGIN_H #define MIDEBUGGERPLUGIN_H #include "config.h" #include #include #include #include #include class QDBusInterface; class QSignalMapper; class QUrl; namespace KDevelop { class Context; } namespace KDevMI { class MIDebugSession; class MIDebuggerPlugin : public KDevelop::IPlugin, public KDevelop::IStatus { Q_OBJECT Q_INTERFACES(KDevelop::IStatus) public: MIDebuggerPlugin(const QString& componentName, QObject *parent); ~MIDebuggerPlugin() override; 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: QString statusName() const override; Q_SIGNALS: void clearMessage(KDevelop::IStatus*) override; void showMessage(KDevelop::IStatus*, const QString & message, int timeout = 0) override; void hideProgress(KDevelop::IStatus*) override; void showProgress(KDevelop::IStatus*, int minimum, int maximum, int value) override; void showErrorMessage(const QString&, int) override; //END IStatus Q_SIGNALS: void reset(); void stopDebugger(); void attachTo(int pid); void coreFile(const QString& core); void runUntil(const QUrl &url, int line); void jumpTo(const QUrl &url, int line); void addWatchVariable(const QString& var); void evaluateExpression(const QString& expr); void raiseDebuggerConsoleViews(); protected Q_SLOTS: void slotDebugExternalProcess(QObject* interface); void slotExamineCore(); #if KF5SysGuard_FOUND void slotAttachProcess(); #endif void slotDBusServiceRegistered(const QString& service); void slotDBusServiceUnregistered(const QString& service); void slotCloseDrKonqi(); protected: - void setupToolviews(); void setupActions(); void setupDBus(); void attachProcess(int pid); void showStatusMessage(const QString& msg, int timeout); private: QHash m_drkonqis; QSignalMapper* m_drkonqiMap; QString m_drkonqi; }; template class DebuggerToolFactory : public KDevelop::IToolViewFactory { public: DebuggerToolFactory(Plugin * plugin, const QString &id, Qt::DockWidgetArea defaultArea) : m_plugin(plugin), m_id(id), m_defaultArea(defaultArea) {} QWidget* create(QWidget *parent = 0) override { return new T(m_plugin, parent); } QString id() const override { return m_id; } Qt::DockWidgetArea defaultPosition() override { return m_defaultArea; } void viewCreated(Sublime::View* view) override { if (view->widget()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("requestRaise()")) != -1) 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; Qt::DockWidgetArea m_defaultArea; }; } // end of namespace KDevMI #endif // MIDEBUGGERPLUGIN_H diff --git a/debuggers/common/midebugjobs.cpp b/debuggers/common/midebugjobs.cpp index 383cda845d..b97d1059e6 100644 --- a/debuggers/common/midebugjobs.cpp +++ b/debuggers/common/midebugjobs.cpp @@ -1,225 +1,223 @@ /* * Common Code for Debugger Support * * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Andreas Pakulat * 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) any later version. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "midebugjobs.h" #include "debuglog.h" #include "dialogs/selectcoredialog.h" #include "midebugsession.h" #include "midebuggerplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevMI; using namespace KDevelop; MIDebugJob::MIDebugJob(MIDebuggerPlugin* p, ILaunchConfiguration* launchcfg, IExecutePlugin* execute, QObject* parent) : KDevelop::OutputJob(parent) , m_launchcfg(launchcfg) , m_execute(execute) { 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)); }); connect(m_session, &MIDebugSession::finished, this, &MIDebugJob::done); if (launchcfg->project()) { setObjectName(i18nc("ProjectName: run configuration name", "%1: %2", launchcfg->project()->name(), launchcfg->name())); } else { setObjectName(launchcfg->name()); } } void MIDebugJob::start() { Q_ASSERT(m_execute); QString err; // check if the config is valid QString executable = m_execute->executable(m_launchcfg, err).toLocalFile(); if (!err.isEmpty()) { setError(-1); setErrorText(err); emitResult(); return; } if (!QFileInfo(executable).isExecutable()) { setError(-1); setErrorText(i18n("'%1' is not an executable", executable)); emitResult(); return; } QStringList arguments = m_execute->arguments(m_launchcfg, err); if (!err.isEmpty()) { setError(-1); setErrorText(err); emitResult(); return; } setStandardToolView(IOutputView::DebugView); setBehaviours(IOutputView::Behaviours(IOutputView::AllowUserClose) | KDevelop::IOutputView::AutoScroll); auto model = new KDevelop::OutputModel; model->setFilteringStrategy(OutputModel::NativeAppErrorFilter); setModel(model); 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(); if (!m_session->startDebugging(m_launchcfg, m_execute)) { done(); } } bool MIDebugJob::doKill() { m_session->stopDebugger(); return true; } void MIDebugJob::stderrReceived(const QStringList& l) { if (OutputModel* m = model()) { m->appendLines(l); } } void MIDebugJob::stdoutReceived(const QStringList& l) { if (OutputModel* m = model()) { m->appendLines(l); } } OutputModel* MIDebugJob::model() { return qobject_cast(OutputJob::model()); } void MIDebugJob::done() { emitResult(); } MIExamineCoreJob::MIExamineCoreJob(MIDebuggerPlugin *plugin, QObject *parent) : KJob(parent) { setCapabilities(Killable); m_session = plugin->createSession(); connect(m_session, &MIDebugSession::finished, this, &MIExamineCoreJob::done); setObjectName(i18n("Debug core file")); } void MIExamineCoreJob::start() { SelectCoreDialog dlg(ICore::self()->uiController()->activeMainWindow()); if (dlg.exec() == QDialog::Rejected) { done(); return; } if (!m_session->examineCoreFile(dlg.binary(), dlg.core())) { done(); } } bool MIExamineCoreJob::doKill() { m_session->stopDebugger(); return true; } void MIExamineCoreJob::done() { emitResult(); } MIAttachProcessJob::MIAttachProcessJob(MIDebuggerPlugin *plugin, int pid, QObject *parent) : KJob(parent) , m_pid(pid) { setCapabilities(Killable); m_session = plugin->createSession(); connect(m_session, &MIDebugSession::finished, this, &MIAttachProcessJob::done); setObjectName(i18n("Debug process %1", pid)); } void MIAttachProcessJob::start() { if (!m_session->attachToProcess(m_pid)) { done(); } } bool MIAttachProcessJob::doKill() { m_session->stopDebugger(); return true; } void MIAttachProcessJob::done() { emitResult(); } diff --git a/debuggers/common/midebugsession.cpp b/debuggers/common/midebugsession.cpp index 5dd32a2738..6d6751a0d6 100644 --- a/debuggers/common/midebugsession.cpp +++ b/debuggers/common/midebugsession.cpp @@ -1,1321 +1,1318 @@ /* * Common code for debugger support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * 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 "midebugsession.h" #include "debuglog.h" #include "midebugger.h" #include "mivariable.h" #include "mi/mi.h" #include "mi/micommand.h" #include "mi/micommandqueue.h" #include "stty.h" #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; 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) , m_debugger(nullptr) , m_debuggerState(s_dbgNotStarted | s_appNotStarted) , m_stateReloadInProgress(false) , m_stateReloadNeeded(false) , m_tty(nullptr) , m_hasCrashed(false) , m_sourceInitFile(true) + , m_plugin(plugin) { // setup signals connect(m_procLineMaker, &ProcessLineMaker::receivedStdoutLines, this, &MIDebugSession::inferiorStdoutLines); connect(m_procLineMaker, &ProcessLineMaker::receivedStderrLines, this, &MIDebugSession::inferiorStderrLines); // forward tty output to process line maker connect(this, &MIDebugSession::inferiorTtyStdout, m_procLineMaker, &ProcessLineMaker::slotReceivedStdout); connect(this, &MIDebugSession::inferiorTtyStderr, m_procLineMaker, &ProcessLineMaker::slotReceivedStderr); // FIXME: see if this still works //connect(statusBarIndicator, SIGNAL(doubleClicked()), // controller, SLOT(explainDebuggerStatus())); // FIXME: reimplement / re-enable //connect(this, SIGNAL(addWatchVariable(QString)), controller->variables(), SLOT(slotAddWatchVariable(QString))); //connect(this, SIGNAL(evaluateExpression(QString)), controller->variables(), SLOT(slotEvaluateExpression(QString))); } MIDebugSession::~MIDebugSession() { qCDebug(DEBUGGERCOMMON) << "Destroying MIDebugSession"; // Deleting the session involves shutting down gdb nicely. // When were attached to a process, we must first detach so that the process // can continue running as it was before being attached. gdb is quite slow to // detach from a process, so we must process events within here to get a "clean" // shutdown. if (!debuggerStateIsOn(s_dbgNotStarted)) { stopDebugger(); } } IDebugSession::DebuggerState MIDebugSession::state() const { return m_sessionState; } QMap & MIDebugSession::variableMapping() { return m_allVariables; } MIVariable* MIDebugSession::findVariableByVarobjName(const QString &varobjName) const { if (m_allVariables.count(varobjName) == 0) return nullptr; return m_allVariables.value(varobjName); } void MIDebugSession::markAllVariableDead() { for (auto i = m_allVariables.begin(), e = m_allVariables.end(); i != e; ++i) { i.value()->markAsDead(); } m_allVariables.clear(); } bool MIDebugSession::restartAvaliable() const { if (debuggerStateIsOn(s_attached) || debuggerStateIsOn(s_core)) { return false; } else { return true; } } bool MIDebugSession::startDebugger(ILaunchConfiguration *cfg) { qCDebug(DEBUGGERCOMMON) << "Starting new debugger instance"; if (m_debugger) { qCWarning(DEBUGGERCOMMON) << "m_debugger object still exists"; delete m_debugger; m_debugger = nullptr; } m_debugger = createDebugger(); m_debugger->setParent(this); // 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); connect(m_debugger, &MIDebugger::debuggerInternalOutput, this, &MIDebugSession::debuggerInternalOutput); // state signals connect(m_debugger, &MIDebugger::programStopped, this, &MIDebugSession::inferiorStopped); connect(m_debugger, &MIDebugger::programRunning, this, &MIDebugSession::inferiorRunning); // internal handlers connect(m_debugger, &MIDebugger::ready, this, &MIDebugSession::slotDebuggerReady); connect(m_debugger, &MIDebugger::exited, this, &MIDebugSession::slotDebuggerExited); connect(m_debugger, &MIDebugger::programStopped, this, &MIDebugSession::slotInferiorStopped); connect(m_debugger, &MIDebugger::programRunning, this, &MIDebugSession::slotInferiorRunning); connect(m_debugger, &MIDebugger::notification, this, &MIDebugSession::processNotification); // start the debugger. Do this after connecting all signals so that initial // debugger output, and important events like the debugger died are reported. QStringList extraArguments; if (!m_sourceInitFile) extraArguments << "--nx"; auto config = cfg ? cfg->config() // FIXME: this is only used when attachToProcess or examineCoreFile. // Change to use a global launch configuration when calling : KConfigGroup(KSharedConfig::openConfig(), "GDB Config"); if (!m_debugger->start(config, extraArguments)) { return false; } // FIXME: here, we should wait until the debugger is up and waiting for input. // Then, clear s_dbgNotStarted // It's better to do this right away so that the state bit is always correct. setDebuggerStateOff(s_dbgNotStarted); // Initialise debugger. At this stage debugger is sitting wondering what to do, // and to whom. initializeDebugger(); qCDebug(DEBUGGERCOMMON) << "Debugger instance started"; return true; } bool MIDebugSession::startDebugging(ILaunchConfiguration* cfg, IExecutePlugin* iexec) { qCDebug(DEBUGGERCOMMON) << "Starting new debug session"; Q_ASSERT(cfg); Q_ASSERT(iexec); // Ensure debugger is started first if (debuggerStateIsOn(s_appNotStarted)) { emit showMessage(i18n("Running program"), 1000); } if (debuggerStateIsOn(s_dbgNotStarted)) { if (!startDebugger(cfg)) return false; } if (debuggerStateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERCOMMON) << "Tried to run when debugger shutting down"; return false; } // Set up the tty for the inferior bool config_useExternalTerminal = iexec->useTerminal(cfg); QString config_ternimalName = iexec->terminal(cfg); if (!config_ternimalName.isEmpty()) { // the external terminal cmd contains additional arguments, just get the terminal name config_ternimalName = KShell::splitArgs(config_ternimalName).first(); } m_tty.reset(new STTY(config_useExternalTerminal, config_ternimalName)); if (!config_useExternalTerminal) { connect(m_tty.get(), &STTY::OutOutput, this, &MIDebugSession::inferiorTtyStdout); connect(m_tty.get(), &STTY::ErrOutput, this, &MIDebugSession::inferiorTtyStderr); } QString tty(m_tty->getSlave()); if (tty.isEmpty()) { KMessageBox::information(qApp->activeWindow(), m_tty->lastError(), i18n("warning")); m_tty.reset(nullptr); return false; } addCommand(InferiorTtySet, tty); // Only dummy err here, actual erros have been checked already in the job and we don't get here if there were any QString err; QString executable = iexec->executable(cfg, err).toLocalFile(); QStringList arguments = iexec->arguments(cfg, err); // Change the working directory to the correct one QString dir = iexec->workingDirectory(cfg).toLocalFile(); if (dir.isEmpty()) { dir = QFileInfo(executable).absolutePath(); } 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") { emit raiseFramestackViews(); } else { // ApplicationOutput is raised in DebugJob (by setting job to Verbose/Silent) } return true; } // FIXME: use same configuration process as startDebugging bool MIDebugSession::attachToProcess(int pid) { qCDebug(DEBUGGERCOMMON) << "Attach to process" << pid; emit showMessage(i18n("Attaching to process %1", pid), 1000); if (debuggerStateIsOn(s_dbgNotStarted)) { // FIXME: use global launch configuration rather than nullptr if (!startDebugger(nullptr)) { return false; } } setDebuggerStateOn(s_attached); //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); addCommand(new SentinelCommand(breakpointController(), &MIBreakpointController::initSendBreakpoints)); raiseEvent(connected_to_program); emit raiseFramestackViews(); return true; } void MIDebugSession::handleTargetAttach(const MI::ResultRecord& r) { if (r.reason == "error") { KMessageBox::error( qApp->activeWindow(), i18n("Could not attach debugger:
")+ r["msg"].literal(), i18n("Startup error")); stopDebugger(); } } bool MIDebugSession::examineCoreFile(const QUrl &debugee, const QUrl &coreFile) { emit showMessage(i18n("Examining core file %1", coreFile.toLocalFile()), 1000); if (debuggerStateIsOn(s_dbgNotStarted)) { // FIXME: use global launch configuration rather than nullptr if (!startDebugger(nullptr)) { return false; } } // FIXME: support non-local URLs addCommand(FileExecAndSymbols, debugee.toLocalFile()); addCommand(NonMI, "core " + coreFile.toLocalFile(), this, &MIDebugSession::handleCoreFile, CmdHandlesError); raiseEvent(connected_to_program); raiseEvent(program_state_changed); return true; } void MIDebugSession::handleCoreFile(const MI::ResultRecord& r) { if (r.reason != "error") { setDebuggerStateOn(s_programExited|s_core); } else { KMessageBox::information( qApp->activeWindow(), i18n("Failed to load core file" "

Debugger reported the following error:" "

%1", r["msg"].literal()), i18n("Debugger error")); // FIXME: How should we proceed at this point? Stop the debugger? } } #define ENUM_NAME(o,e,v) (o::staticMetaObject.enumerator(o::staticMetaObject.indexOfEnumerator(#e)).valueToKey((v))) void MIDebugSession::setSessionState(DebuggerState state) { qCDebug(DEBUGGERCOMMON) << "Session state changed to" << ENUM_NAME(IDebugSession, DebuggerState, state) << "(" << state << ")"; if (state != m_sessionState) { m_sessionState = state; emit stateChanged(state); } } bool MIDebugSession::debuggerStateIsOn(DBGStateFlags state) const { return m_debuggerState & state; } DBGStateFlags MIDebugSession::debuggerState() const { return m_debuggerState; } void MIDebugSession::setDebuggerStateOn(DBGStateFlags stateOn) { DBGStateFlags oldState = m_debuggerState; debuggerStateChange(m_debuggerState, m_debuggerState | stateOn); m_debuggerState |= stateOn; handleDebuggerStateChange(oldState, m_debuggerState); } void MIDebugSession::setDebuggerStateOff(DBGStateFlags stateOff) { DBGStateFlags oldState = m_debuggerState; debuggerStateChange(m_debuggerState, m_debuggerState & ~stateOff); m_debuggerState &= ~stateOff; handleDebuggerStateChange(oldState, m_debuggerState); } void MIDebugSession::setDebuggerState(DBGStateFlags newState) { DBGStateFlags oldState = m_debuggerState; debuggerStateChange(m_debuggerState, newState); m_debuggerState = newState; handleDebuggerStateChange(oldState, m_debuggerState); } void MIDebugSession::debuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState) { int delta = oldState ^ newState; if (delta) { QString out; #define STATE_CHECK(name) \ do { \ if (delta & name) { \ out += ((newState & name) ? " +" : " -"); \ out += #name; \ delta &= ~name; \ } \ } while (0) STATE_CHECK(s_dbgNotStarted); STATE_CHECK(s_appNotStarted); STATE_CHECK(s_programExited); STATE_CHECK(s_attached); STATE_CHECK(s_core); STATE_CHECK(s_shuttingDown); STATE_CHECK(s_dbgBusy); STATE_CHECK(s_appRunning); STATE_CHECK(s_dbgNotListening); STATE_CHECK(s_automaticContinue); #undef STATE_CHECK for (unsigned int i = 0; delta != 0 && i < 32; ++i) { if (delta & (1 << i)) { delta &= ~(1 << i); out += ((1 << i) & newState) ? " +" : " -"; out += QString::number(i); } } qCDebug(DEBUGGERCOMMON) << "Debugger state change:" << out; } } void MIDebugSession::handleDebuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState) { QString message; DebuggerState oldSessionState = state(); DebuggerState newSessionState = oldSessionState; DBGStateFlags changedState = oldState ^ newState; if (newState & s_dbgNotStarted) { if (changedState & s_dbgNotStarted) { message = i18n("Debugger stopped"); emit finished(); } if (oldSessionState != NotStartedState) { newSessionState = EndedState; } } else { if (newState & s_appNotStarted) { if (oldSessionState == NotStartedState || oldSessionState == StartingState) { newSessionState = StartingState; } else { newSessionState = StoppedState; } } else if (newState & s_programExited) { if (changedState & s_programExited) { message = i18n("Process exited"); } newSessionState = StoppedState; } else if (newState & s_appRunning) { if (changedState & s_appRunning) { message = i18n("Application is running"); } newSessionState = ActiveState; } else { if (changedState & s_appRunning) { message = i18n("Application is paused"); } newSessionState = PausedState; } } // And now? :-) qCDebug(DEBUGGERCOMMON) << "Debugger state changed to: " << newState << message; if (!message.isEmpty()) emit showMessage(message, 3000); emit debuggerStateChanged(oldState, newState); // must be last, since it can lead to deletion of the DebugSession if (newSessionState != oldSessionState) { setSessionState(newSessionState); } } void MIDebugSession::restartDebugger() { // We implement restart as kill + slotRun, as opposed as plain "run" // command because kill + slotRun allows any special logic in slotRun // to apply for restart. // // That includes: // - checking for out-of-date project // - special setup for remote debugging. // // Had we used plain 'run' command, restart for remote debugging simply // would not work. if (!debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) { // FIXME: s_dbgBusy or m_debugger->isReady()? if (debuggerStateIsOn(s_dbgBusy)) { interruptDebugger(); } // The -exec-abort is not implemented in gdb // addCommand(ExecAbort); addCommand(NonMI, "kill"); } run(); } void MIDebugSession::stopDebugger() { + if (debuggerStateIsOn(s_dbgNotStarted)) { + return; + } + m_commandQueue->clear(); qCDebug(DEBUGGERCOMMON) << "try stopping debugger"; if (debuggerStateIsOn(s_shuttingDown) || !m_debugger) return; setDebuggerStateOn(s_shuttingDown); qCDebug(DEBUGGERCOMMON) << "stopping debugger"; // Get debugger's attention if it's busy. We need debugger to be at the // command line so we can stop it. if (!m_debugger->isReady()) { qCDebug(DEBUGGERCOMMON) << "debugger busy on shutdown - interruping"; interruptDebugger(); } // If the app is attached then we release it here. This doesn't stop // the app running. if (debuggerStateIsOn(s_attached)) { addCommand(TargetDetach); emit debuggerUserCommandOutput("(gdb) detach\n"); } // Now try to stop debugger running. addCommand(GdbExit); emit debuggerUserCommandOutput("(gdb) quit"); // We cannot wait forever, kill gdb after 5 seconds if it's not yet quit QPointer guarded_this(this); QTimer::singleShot(5000, [guarded_this](){ if (guarded_this) { if (!guarded_this->debuggerStateIsOn(s_programExited) && guarded_this->debuggerStateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERCOMMON) << "debugger not shutdown - killing"; guarded_this->m_debugger->kill(); guarded_this->setDebuggerState(s_dbgNotStarted | s_appNotStarted); guarded_this->raiseEvent(debugger_exited); } } }); emit reset(); } void MIDebugSession::interruptDebugger() { Q_ASSERT(m_debugger); // Explicitly send the interrupt in case something went wrong with the usual // ensureGdbListening logic. m_debugger->interrupt(); addCommand(ExecInterrupt, QString(), CmdInterrupt); } void MIDebugSession::run() { if (debuggerStateIsOn(s_appNotStarted|s_dbgNotStarted|s_shuttingDown)) return; addCommand(MI::ExecContinue, QString(), CmdMaybeStartsRunning); } void MIDebugSession::runToCursor() { if (IDocument* doc = ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if (cursor.isValid()) runUntil(doc->url(), cursor.line() + 1); } } void MIDebugSession::jumpToCursor() { if (IDocument* doc = ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if (cursor.isValid()) jumpTo(doc->url(), cursor.line() + 1); } } void MIDebugSession::stepOver() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecNext, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepIntoInstruction() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecStepInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepInto() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecStep, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepOverInstruction() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecNextInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepOut() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecFinish, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::runUntil(const QUrl& url, int line) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!url.isValid()) { addCommand(ExecUntil, QString::number(line), CmdMaybeStartsRunning | CmdTemporaryRun); } else { addCommand(ExecUntil, QString("%1:%2").arg(url.toLocalFile()).arg(line), CmdMaybeStartsRunning | CmdTemporaryRun); } } void MIDebugSession::runUntil(const QString& address) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!address.isEmpty()) { addCommand(ExecUntil, QString("*%1").arg(address), CmdMaybeStartsRunning | CmdTemporaryRun); } } void MIDebugSession::jumpTo(const QUrl& url, int line) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (url.isValid()) { addCommand(NonMI, QString("tbreak %1:%2").arg(url.toLocalFile()).arg(line)); addCommand(NonMI, QString("jump %1:%2").arg(url.toLocalFile()).arg(line)); } } void MIDebugSession::jumpToMemoryAddress(const QString& address) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!address.isEmpty()) { addCommand(NonMI, QString("tbreak *%1").arg(address)); addCommand(NonMI, QString("jump *%1").arg(address)); } } 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. // We can do it right now, and don't wait for user command to finish // since commands used to reload all view will be executed after // user command anyway. if (!debuggerStateIsOn(s_appNotStarted) && !debuggerStateIsOn(s_programExited)) raiseEvent(program_state_changed); } MICommand *MIDebugSession::createCommand(CommandType type, const QString& arguments, CommandFlags flags) const { return new MICommand(type, arguments, flags); } void MIDebugSession::addCommand(MICommand* cmd) { queueCmd(cmd); } void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, MI::CommandFlags flags) { queueCmd(createCommand(type, arguments, flags)); } void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, MI::MICommandHandler *handler, MI::CommandFlags flags) { auto cmd = createCommand(type, arguments, flags); cmd->setHandler(handler); queueCmd(cmd); } void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, const MI::FunctionCommandHandler::Function& callback, MI::CommandFlags flags) { auto cmd = createCommand(type, arguments, flags); cmd->setHandler(callback); queueCmd(cmd); } // Fairly obvious that we'll add whatever command you give me to a queue // Not quite so obvious though is that if we are going to run again. then any // information requests become redundent and must be removed. // We also try and run whatever command happens to be at the head of // the queue. void MIDebugSession::queueCmd(MICommand *cmd) { if (debuggerStateIsOn(s_dbgNotStarted)) { KMessageBox::information( qApp->activeWindow(), i18n("Gdb command sent when debugger is not running
" "The command was:
%1", cmd->initialString()), i18n("Internal error")); return; } if (m_stateReloadInProgress) cmd->setStateReloading(true); m_commandQueue->enqueue(cmd); qCDebug(DEBUGGERCOMMON) << "QUEUE: " << cmd->initialString() << (m_stateReloadInProgress ? "(state reloading)" : "") << m_commandQueue->count() << "pending"; bool varCommandWithContext= (cmd->type() >= MI::VarAssign && cmd->type() <= MI::VarUpdate && cmd->type() != MI::VarDelete); bool stackCommandWithContext = (cmd->type() >= MI::StackInfoDepth && cmd->type() <= MI::StackListLocals); if (varCommandWithContext || stackCommandWithContext) { if (cmd->thread() == -1) qCDebug(DEBUGGERCOMMON) << "\t--thread will be added on execution"; if (cmd->frame() == -1) qCDebug(DEBUGGERCOMMON) << "\t--frame will be added on execution"; } setDebuggerStateOn(s_dbgBusy); raiseEvent(debugger_busy); executeCmd(); } void MIDebugSession::executeCmd() { Q_ASSERT(m_debugger); if (debuggerStateIsOn(s_dbgNotListening) && m_commandQueue->haveImmediateCommand()) { // We may have to call this even while a command is currently executing, because // debugger can get into a state where a command such as ExecRun does not send a response // while the inferior is running. ensureDebuggerListening(); } if (!m_debugger->isReady()) return; MICommand* currentCmd = m_commandQueue->nextCommand(); if (!currentCmd) return; if (currentCmd->flags() & (CmdMaybeStartsRunning | CmdInterrupt)) { setDebuggerStateOff(s_automaticContinue); } if (currentCmd->flags() & CmdMaybeStartsRunning) { // GDB can be in a state where it is listening for commands while the program is running. // However, when we send a command such as ExecContinue in this state, GDB may return to // the non-listening state without acknowledging that the ExecContinue command has even // finished, let alone sending a new notification about the program's running state. // So let's be extra cautious about ensuring that we will wake GDB up again if required. setDebuggerStateOn(s_dbgNotListening); } bool varCommandWithContext= (currentCmd->type() >= MI::VarAssign && currentCmd->type() <= MI::VarUpdate && currentCmd->type() != MI::VarDelete); bool stackCommandWithContext = (currentCmd->type() >= MI::StackInfoDepth && currentCmd->type() <= MI::StackListLocals); if (varCommandWithContext || stackCommandWithContext) { // Most var commands should be executed in the context // of the selected thread and frame. if (currentCmd->thread() == -1) currentCmd->setThread(frameStackModel()->currentThread()); if (currentCmd->frame() == -1) currentCmd->setFrame(frameStackModel()->currentFrame()); } QString commandText = currentCmd->cmdToSend(); bool bad_command = false; QString message; int length = commandText.length(); // No i18n for message since it's mainly for debugging. if (length == 0) { // The command might decide it's no longer necessary to send // it. if (SentinelCommand* sc = dynamic_cast(currentCmd)) { qCDebug(DEBUGGERCOMMON) << "SEND: sentinel command, not sending"; sc->invokeHandler(); } else { qCDebug(DEBUGGERCOMMON) << "SEND: command " << currentCmd->initialString() << "changed its mind, not sending"; } delete currentCmd; executeCmd(); return; } else { if (commandText[length-1] != '\n') { bad_command = true; message = "Debugger command does not end with newline"; } } if (bad_command) { KMessageBox::information(qApp->activeWindow(), i18n("Invalid debugger command
%1", message), i18n("Invalid debugger command")); executeCmd(); return; } m_debugger->execute(currentCmd); } void MIDebugSession::ensureDebuggerListening() { Q_ASSERT(m_debugger); // Note: we don't use interruptDebugger() here since // we don't want to queue more commands before queuing a command m_debugger->interrupt(); setDebuggerStateOn(s_interruptSent); if (debuggerStateIsOn(s_appRunning)) setDebuggerStateOn(s_automaticContinue); setDebuggerStateOff(s_dbgNotListening); } void MIDebugSession::destroyCmds() { m_commandQueue->clear(); } // FIXME: I don't fully remember what is the business with // m_stateReloadInProgress and whether we can lift it to the // generic level. void MIDebugSession::raiseEvent(event_t e) { if (e == program_exited || e == debugger_exited) { m_stateReloadInProgress = false; } if (e == program_state_changed) { m_stateReloadInProgress = true; qCDebug(DEBUGGERCOMMON) << "State reload in progress\n"; } IDebugSession::raiseEvent(e); if (e == program_state_changed) { m_stateReloadInProgress = false; } } bool KDevMI::MIDebugSession::hasCrashed() const { return m_hasCrashed; } void MIDebugSession::slotDebuggerReady() { Q_ASSERT(m_debugger); m_stateReloadInProgress = false; executeCmd(); if (m_debugger->isReady()) { /* There is nothing in the command queue and no command is currently executing. */ if (debuggerStateIsOn(s_automaticContinue)) { if (!debuggerStateIsOn(s_appRunning)) { qCDebug(DEBUGGERCOMMON) << "Posting automatic continue"; addCommand(ExecContinue, QString(), CmdMaybeStartsRunning); } setDebuggerStateOff(s_automaticContinue); return; } if (m_stateReloadNeeded && !debuggerStateIsOn(s_appRunning)) { qCDebug(DEBUGGERCOMMON) << "Finishing program stop"; // Set to false right now, so that if 'actOnProgramPauseMI_part2' // sends some commands, we won't call it again when handling replies // from that commands. m_stateReloadNeeded = false; reloadProgramState(); } qCDebug(DEBUGGERCOMMON) << "No more commands"; setDebuggerStateOff(s_dbgBusy); raiseEvent(debugger_ready); } } void MIDebugSession::slotDebuggerExited(bool abnormal, const QString &msg) { /* Technically speaking, GDB is likely not to kill the application, and we should have some backup mechanism to make sure the application is killed by KDevelop. But even if application stays around, we no longer can control it in any way, so mark it as exited. */ setDebuggerStateOn(s_appNotStarted); setDebuggerStateOn(s_dbgNotStarted); setDebuggerStateOn(s_programExited); setDebuggerStateOff(s_shuttingDown); if (!msg.isEmpty()) emit showMessage(msg, 3000); if (abnormal) { /* The error is reported to user in MIDebugger now. KMessageBox::information( KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("Debugger exited abnormally" "

This is likely a bug in GDB. " "Examine the gdb output window and then stop the debugger"), i18n("Debugger exited abnormally")); */ // FIXME: not sure if the following still applies. // Note: we don't stop the debugger here, becuse that will hide gdb // window and prevent the user from finding the exact reason of the // problem. } /* FIXME: raiseEvent is handled across multiple places where we explicitly * stop/kill the debugger, a better way is to let the debugger itself report * its exited event. */ // raiseEvent(debugger_exited); } void MIDebugSession::slotInferiorStopped(const MI::AsyncRecord& r) { /* By default, reload all state on program stop. */ m_stateReloadNeeded = true; setDebuggerStateOff(s_appRunning); setDebuggerStateOff(s_dbgNotListening); QString reason; if (r.hasField("reason")) reason = r["reason"].literal(); if (reason == "exited-normally" || reason == "exited") { if (r.hasField("exit-code")) { programNoApp(i18n("Exited with return code: %1", r["exit-code"].literal())); } else { programNoApp(i18n("Exited normally")); } m_stateReloadNeeded = false; return; } if (reason == "exited-signalled") { programNoApp(i18n("Exited on signal %1", r["signal-name"].literal())); m_stateReloadNeeded = false; return; } if (reason == "watchpoint-scope") { QString number = r["wpnum"].literal(); // FIXME: shuld remove this watchpoint // But first, we should consider if removing all // watchpoinst on program exit is the right thing to // do. addCommand(ExecContinue, QString(), CmdMaybeStartsRunning); m_stateReloadNeeded = false; return; } bool wasInterrupt = false; if (reason == "signal-received") { QString name = r["signal-name"].literal(); QString user_name = r["signal-meaning"].literal(); // SIGINT is a "break into running program". // We do this when the user set/mod/clears a breakpoint but the // application is running. // And the user does this to stop the program also. if (name == "SIGINT" && debuggerStateIsOn(s_interruptSent)) { wasInterrupt = true; } else { // Whenever we have a signal raised then tell the user, but don't // end the program as we want to allow the user to look at why the // program has a signal that's caused the prog to stop. // Continuing from SIG FPE/SEGV will cause a "Cannot ..." and // that'll end the program. programFinished(i18n("Program received signal %1 (%2)", name, user_name)); m_hasCrashed = true; } } if (!reason.contains("exited")) { // FIXME: we should immediately update the current thread and // frame in the framestackmodel, so that any user actions // are in that thread. However, the way current framestack model // is implemented, we can't change thread id until we refresh // the entire list of threads -- otherwise we might set a thread // id that is not already in the list, and it will be upset. //Indicates if program state should be reloaded immediately. bool updateState = false; if (r.hasField("frame")) { const MI::Value& frame = r["frame"]; QString file, line, addr; if (frame.hasField("fullname")) file = frame["fullname"].literal();; if (frame.hasField("line")) line = frame["line"].literal(); if (frame.hasField("addr")) addr = frame["addr"].literal(); // gdb counts lines from 1 and we don't setCurrentPosition(QUrl::fromLocalFile(file), line.toInt() - 1, addr); updateState = true; } if (updateState) { reloadProgramState(); } } setDebuggerStateOff(s_interruptSent); if (!wasInterrupt) setDebuggerStateOff(s_automaticContinue); } void MIDebugSession::slotInferiorRunning() { setDebuggerStateOn(s_appRunning); raiseEvent(program_running); if (m_commandQueue->haveImmediateCommand() || (m_debugger->currentCommand() && (m_debugger->currentCommand()->flags() & (CmdImmediately | CmdInterrupt)))) { ensureDebuggerListening(); } else { setDebuggerStateOn(s_dbgNotListening); } } void MIDebugSession::processNotification(const MI::AsyncRecord & async) { if (async.reason == "thread-group-started") { setDebuggerStateOff(s_appNotStarted | s_programExited); } else if (async.reason == "thread-group-exited") { setDebuggerStateOn(s_programExited); } else if (async.reason == "library-loaded") { // do nothing } else if (async.reason == "breakpoint-created") { breakpointController()->notifyBreakpointCreated(async); } else if (async.reason == "breakpoint-modified") { breakpointController()->notifyBreakpointModified(async); } else if (async.reason == "breakpoint-deleted") { breakpointController()->notifyBreakpointDeleted(async); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled notification: " << async.reason; } } void MIDebugSession::reloadProgramState() { raiseEvent(program_state_changed); m_stateReloadNeeded = false; } // There is no app anymore. This can be caused by program exiting // an invalid program specified or ... // gdb is still running though, but only the run command (may) make sense // all other commands are disabled. void MIDebugSession::programNoApp(const QString& msg) { qCDebug(DEBUGGERCOMMON) << msg; setDebuggerState(s_appNotStarted | s_programExited | (m_debuggerState & s_shuttingDown)); destroyCmds(); // The application has existed, but it's possible that // some of application output is still in the pipe. We use // different pipes to communicate with gdb and to get application // output, so "exited" message from gdb might have arrived before // last application output. Get this last bit. // Note: this method can be called when we open an invalid // core file. In that case, tty_ won't be set. if (m_tty){ m_tty->readRemaining(); // Tty is no longer usable, delete it. Without this, QSocketNotifier // will continiously bomd STTY with signals, so we need to either disable // QSocketNotifier, or delete STTY. The latter is simpler, since we can't // reuse it for future debug sessions anyway. m_tty.reset(nullptr); } stopDebugger(); raiseEvent(program_exited); raiseEvent(debugger_exited); emit showMessage(msg, 0); programFinished(msg); } void MIDebugSession::programFinished(const QString& msg) { QString m = QString("*** %0 ***").arg(msg.trimmed()); emit inferiorStderrLines(QStringList(m)); /* Also show message in gdb window, so that users who prefer to look at gdb window know what's up. */ emit debuggerUserCommandOutput(m); } void MIDebugSession::explainDebuggerStatus() { MICommand* currentCmd_ = m_debugger->currentCommand(); QString information = i18np("1 command in queue\n", "%1 commands in queue\n", m_commandQueue->count()) + i18ncp("Only the 0 and 1 cases need to be translated", "1 command being processed by gdb\n", "%1 commands being processed by gdb\n", (currentCmd_ ? 1 : 0)) + i18n("Debugger state: %1\n", m_debuggerState); if (currentCmd_) { QString extra = i18n("Current command class: '%1'\n" "Current command text: '%2'\n" "Current command original text: '%3'\n", typeid(*currentCmd_).name(), currentCmd_->cmdToSend(), currentCmd_->initialString()); information += extra; } KMessageBox::information(qApp->activeWindow(), information, i18n("Debugger status")); } // There is no app anymore. This can be caused by program exiting // an invalid program specified or ... // gdb is still running though, but only the run command (may) make sense // all other commands are disabled. void MIDebugSession::handleNoInferior(const QString& msg) { qCDebug(DEBUGGERCOMMON) << msg; setDebuggerState(s_appNotStarted | s_programExited | (debuggerState() & s_shuttingDown)); destroyCmds(); // The application has existed, but it's possible that // some of application output is still in the pipe. We use // different pipes to communicate with gdb and to get application // output, so "exited" message from gdb might have arrived before // last application output. Get this last bit. // Note: this method can be called when we open an invalid // core file. In that case, tty_ won't be set. if (m_tty){ m_tty->readRemaining(); // Tty is no longer usable, delete it. Without this, QSocketNotifier // will continiously bomd STTY with signals, so we need to either disable // QSocketNotifier, or delete STTY. The latter is simpler, since we can't // reuse it for future debug sessions anyway. m_tty.reset(0); } stopDebugger(); raiseEvent(program_exited); raiseEvent(debugger_exited); emit showMessage(msg, 0); handleInferiorFinished(msg); } void MIDebugSession::handleInferiorFinished(const QString& msg) { QString m = QStringLiteral("*** %0 ***").arg(msg.trimmed()); emit inferiorStderrLines(QStringList(m)); /* Also show message in gdb window, so that users who prefer to look at gdb window know what's up. */ emit debuggerUserCommandOutput(m); } // FIXME: connect to debugger's slot. void MIDebugSession::defaultErrorHandler(const MI::ResultRecord& result) { QString msg = result["msg"].literal(); if (msg.contains("No such process")) { setDebuggerState(s_appNotStarted|s_programExited); raiseEvent(program_exited); return; } KMessageBox::information( qApp->activeWindow(), i18n("Debugger error" "

Debugger reported the following error:" "

%1", result["msg"].literal()), i18n("Debugger error")); // Error most likely means that some change made in GUI // was not communicated to the gdb, so GUI is now not // in sync with gdb. Resync it. // // Another approach is to make each widget reload it content // on errors from commands that it sent, but that's too complex. // Errors are supposed to happen rarely, so full reload on error // is not a big deal. Well, maybe except for memory view, but // it's no auto-reloaded anyway. // // Also, don't reload state on errors appeared during state // reloading! if (!m_debugger->currentCommand()->stateReloading()) raiseEvent(program_state_changed); } void MIDebugSession::setSourceInitFile(bool enable) { m_sourceInitFile = enable; } diff --git a/debuggers/common/midebugsession.h b/debuggers/common/midebugsession.h index 7c1459628d..fa7de9969c 100644 --- a/debuggers/common/midebugsession.h +++ b/debuggers/common/midebugsession.h @@ -1,360 +1,365 @@ /* * Common code for debugger support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * 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 MIDEBUGSESSION_H #define MIDEBUGSESSION_H #include #include "dbgglobal.h" #include "mibreakpointcontroller.h" #include "mi/mi.h" #include "mi/micommand.h" #include #include class IExecutePlugin; namespace KDevelop { class ILaunchConfiguration; class ProcessLineMaker; } namespace KDevMI { namespace MI { class CommandQueue; } 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: /** * Emits when received standard output lines from inferior */ void inferiorStdoutLines(const QStringList &lines); /** * Emits when received standard error lines from inferior */ void inferiorStderrLines(const QStringList &lines); void inferiorStopped(const MI::AsyncRecord &r); void inferiorRunning(); /** * Emits when received standard output from debugger for user commands */ void debuggerUserCommandOutput(const QString &output); /** * Emits when received standard output from debugger for internal commands */ void debuggerInternalCommandOutput(const QString &output); /** * Emits when received internal output from debugger */ void debuggerInternalOutput(const QString &output); /** * Emits when received standard output from inferior's tty */ void inferiorTtyStdout(const QByteArray &output); /** * Emits when received standard output from inferior's tty */ void inferiorTtyStderr(const QByteArray &output); /** * Emits when the debugger instance state changes */ void debuggerStateChanged(DBGStateFlags oldState, DBGStateFlags newState); /** * Emits when there's message needed to be show to user. */ void showMessage(const QString& message, int timeout); /** * Emits when the debugger console view need to be raised. */ void raiseDebuggerConsoleViews(); /** * Emits when need to reset */ void reset(); public: bool debuggerStateIsOn(DBGStateFlags state) const; DBGStateFlags debuggerState() const; bool hasCrashed() const; // BEGIN IDebugSession overrides public: DebuggerState state() const override; bool restartAvaliable() const override; MIBreakpointController * breakpointController() const override = 0; public Q_SLOTS: void restartDebugger() override; void stopDebugger() override; void interruptDebugger() override; void run() override; void runToCursor() override; void jumpToCursor() override; void stepOver() override; void stepIntoInstruction() override; void stepInto() override; void stepOverInstruction() override; void stepOut() override; // END IDebugSession overrides public Q_SLOTS: /** * Run currently executing program to the given \a url and \a line. */ void runUntil(const QUrl& url, int line); /** * Run currently executing program to the given \a address */ void runUntil(const QString& address); /** * Move the execution point of the currently executing program to the given \a url and \a line. */ void jumpTo(const QUrl& url, int line); /** * Move the execution point of the currently executing program to the given \a address. *Note: It can be really very dangerous, so use jumpTo instead. */ void jumpToMemoryAddress(const QString& address); /** * Start the debugger, and execute the inferior program specified by \a cfg. */ bool startDebugging(KDevelop::ILaunchConfiguration *cfg, IExecutePlugin *iexec); /** * Start the debugger, and examine the core file given by \a coreFile. */ bool examineCoreFile(const QUrl &debugee, const QUrl &coreFile); /** * Start the debugger, and attach to a currently running process with the given \a pid. */ bool attachToProcess(int pid); public: virtual MI::MICommand *createCommand(MI::CommandType type, const QString& arguments, MI::CommandFlags flags = 0) const; /** Adds a command to the end of queue of commands to be executed by gdb. The command will be actually sent to gdb only when replies from all previous commands are received and full processed. The literal command sent to gdb is obtained by calling cmd->cmdToSend. The call is made immediately before sending the command, so it's possible to use results of prior commands when computing the exact command to send. */ void addUserCommand(const QString &cmd); void addCommand(MI::MICommand* cmd); /** Same as above, but internally constructs MICommand using createCommand() */ void addCommand(MI::CommandType type, const QString& arguments = QString(), MI::CommandFlags flags = 0); void addCommand(MI::CommandType type, const QString& arguments, MI::MICommandHandler* handler, MI::CommandFlags flags = 0); void addCommand(MI::CommandType type, const QString& arguments, const MI::FunctionCommandHandler::Function& callback, MI::CommandFlags flags = 0); template void addCommand(MI::CommandType type, const QString& arguments, Handler* handler_this, void (Handler::* handler_method)(const MI::ResultRecord&), MI::CommandFlags flags = 0); QMap & variableMapping(); MIVariable* findVariableByVarobjName(const QString &varobjName) const; void markAllVariableDead(); protected Q_SLOTS: virtual void slotDebuggerReady(); virtual void slotDebuggerExited(bool abnormal, const QString &msg); virtual void slotInferiorStopped(const MI::AsyncRecord &r); /** * Triggered every time program begins/continues it's execution. */ virtual void slotInferiorRunning(); /** * Handle MI async notifications. */ virtual void processNotification(const MI::AsyncRecord &n); /** Default handler for errors. Tries to guess is the error message is telling that target is gone, if so, informs the user. Otherwise, shows a dialog box and reloads view state. */ virtual void defaultErrorHandler(const MI::ResultRecord &result); /** * Update session state when debugger state changes, and show messages */ virtual void handleDebuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState); void handleNoInferior(const QString &msg); void handleInferiorFinished(const QString &msg); protected: void queueCmd(MI::MICommand *cmd); /** Try to execute next command in the queue. If GDB is not 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 */ bool startDebugger(KDevelop::ILaunchConfiguration *cfg); /** * MIDebugSession takes the ownership of the created instance. */ virtual MIDebugger *createDebugger() const = 0; /** * Initialize debugger and set default configurations. */ virtual void initializeDebugger() = 0; /** * 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 */ void setDebuggerStateOn(DBGStateFlags stateOn); void setDebuggerStateOff(DBGStateFlags stateOff); void setDebuggerState(DBGStateFlags newState); void debuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState); /** * Manipulate the session state */ void setSessionState(DebuggerState state); void raiseEvent(event_t e) override; /** Called when there are no pending commands and 'm_stateReloadNeeded' is true. Also can be used to immediately reload program state. Issues commands to completely reload all program state shown to the user. */ void reloadProgramState(); void programNoApp(const QString &msg); void programFinished(const QString &msg); // FIXME: Whether let the debugger source init files when starting, // only used in unit test currently, potentially could be made a user // configurable option void setSourceInitFile(bool enable); private Q_SLOTS: void handleTargetAttach(const MI::ResultRecord& r); void handleCoreFile(const MI::ResultRecord& r); // Pops up a dialog box with some hopefully // detailed information about which state debugger // is in, which commands were sent and so on. void explainDebuggerStatus(); protected: KDevelop::ProcessLineMaker *m_procLineMaker; std::unique_ptr m_commandQueue; // Though the misleading class name, this is the session level state. // see m_debuggerState for debugger instance state DebuggerState m_sessionState; MIDebugger *m_debugger; DBGStateFlags m_debuggerState; bool m_stateReloadInProgress; bool m_stateReloadNeeded; std::unique_ptr m_tty; bool m_hasCrashed; bool m_sourceInitFile; // Map from GDB varobj name to MIVariable. QMap m_allVariables; + + MIDebuggerPlugin *m_plugin; }; template void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, Handler* handler_this, void (Handler::* handler_method)(const MI::ResultRecord&), MI::CommandFlags flags) { auto cmd = createCommand(type, arguments, flags); cmd->setHandler(handler_this, handler_method); queueCmd(cmd); } } // end of namespace KDevMI #endif // MIDEBUGSESSION_H diff --git a/debuggers/common/miframestackmodel.cpp b/debuggers/common/miframestackmodel.cpp index 3dcaf9fc9b..a4945620d3 100644 --- a/debuggers/common/miframestackmodel.cpp +++ b/debuggers/common/miframestackmodel.cpp @@ -1,159 +1,155 @@ /* * Implementation of thread and frame model that are common to debuggers using MI. * * Copyright 2009 Vladimir Prus * * 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) any later version. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "miframestackmodel.h" #include "midebugsession.h" #include "mi/micommand.h" #include #include using namespace KDevelop; using namespace KDevMI; using namespace KDevMI::MI; QString getFunctionOrAddress(const Value &frame) { if (frame.hasField("func")) return frame["func"].literal(); else return frame["addr"].literal(); } QPair getSource(const Value &frame) { QPair ret(QString(), -1); if (frame.hasField("fullname")) ret=qMakePair(frame["fullname"].literal(), frame["line"].toInt()-1); else if (frame.hasField("file")) ret=qMakePair(frame["file"].literal(), frame["line"].toInt()-1); else if (frame.hasField("from")) ret.first=frame["from"].literal(); return ret; } MIFrameStackModel::MIFrameStackModel(MIDebugSession * session) : FrameStackModel(session) { } MIDebugSession * MIFrameStackModel::session() { return static_cast(FrameStackModel::session()); } 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); } void MIFrameStackModel::handleThreadInfo(const ResultRecord& r) { const Value& threads = r["threads"]; QList threadsList; for (int i = 0; i!= threads.size(); ++i) { const auto &threadMI = threads[i]; FrameStackModel::ThreadItem threadItem; threadItem.nr = threadMI["id"].toInt(); if (threadMI["state"].literal() == "stopped") { threadItem.name = getFunctionOrAddress(threadMI["frame"]); } else { i18n("(running)"); } threadsList << threadItem; } // Sort the list by id, some old version of GDB // reports them in backward order. We want UI to // show thread IDs in the natural order. std::sort(threadsList.begin(), threadsList.end(), [](const FrameStackModel::ThreadItem &a, const FrameStackModel::ThreadItem &b){ return a.nr < b.nr; }); setThreads(threadsList); if (r.hasField("current-thread-id")) { int currentThreadId = r["current-thread-id"].toInt(); setCurrentThread(currentThreadId); if (session()->hasCrashed()) { setCrashedThreadIndex(currentThreadId); } } } struct FrameListHandler : public MICommandHandler { FrameListHandler(MIFrameStackModel* model, int thread, int to) : model(model), m_thread(thread) , m_to(to) {} void handle(const ResultRecord &r) override { const Value& stack = r["stack"]; int first = stack[0]["level"].toInt(); QList frames; for (int i = 0; i< stack.size(); ++i) { const Value& frame = stack[i]; KDevelop::FrameStackModel::FrameItem f; f.nr = frame["level"].toInt(); f.name = getFunctionOrAddress(frame); QPair loc = getSource(frame); f.file = QUrl::fromLocalFile(loc.first); f.line = loc.second; frames << f; } bool hasMore = false; if (!frames.isEmpty()) { if (frames.last().nr == m_to+1) { frames.takeLast(); hasMore = true; } } if (first == 0) { model->setFrames(m_thread, frames); } else { model->insertFrames(m_thread, frames); } model->setHasMoreFrames(m_thread, hasMore); } private: MIFrameStackModel* model; int m_thread; int m_to; }; void MIFrameStackModel::fetchFrames(int threadNumber, int from, int to) { //to+1 so we know if there are more QString arg = QString("%1 %2").arg(from).arg(to+1); MICommand *c = session()->createCommand(StackListFrames, arg); c->setHandler(new FrameListHandler(this, threadNumber, to)); c->setThread(threadNumber); session()->addCommand(c); } diff --git a/debuggers/common/mivariable.cpp b/debuggers/common/mivariable.cpp index 8a365bdc8b..c8c0d2af1e 100644 --- a/debuggers/common/mivariable.cpp +++ b/debuggers/common/mivariable.cpp @@ -1,362 +1,362 @@ /* * MI based debugger specific Variable * * Copyright 2009 Vladimir Prus * * 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) any later version. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mivariable.h" #include "debuglog.h" #include "midebugsession.h" #include "mi/micommand.h" #include #include using namespace KDevelop; using namespace KDevMI; using namespace KDevMI::MI; bool MIVariable::sessionIsAlive() const { if (!debugSession) return false; IDebugSession::DebuggerState s = debugSession->state(); return s != IDebugSession::NotStartedState && s != IDebugSession::EndedState; } MIVariable::MIVariable(MIDebugSession *session, TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) : Variable(model, parent, expression, display) , debugSession(session) { } MIVariable::~MIVariable() { if (!varobj_.isEmpty()) { // Delete only top-level variable objects. if (topLevel()) { if (sessionIsAlive()) { debugSession->addCommand(VarDelete, QString("\"%1\"").arg(varobj_)); } } if (debugSession) debugSession->variableMapping().remove(varobj_); } } 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()) { // this should not happen // but apperently it does when attachMaybe is called a second time before // the first -var-create call returned debugSession->variableMapping().remove(varobj_); } varobj_ = v; debugSession->variableMapping()[varobj_] = this; } static int nextId = 0; class CreateVarobjHandler : public MICommandHandler { public: CreateVarobjHandler(MIVariable *variable, QObject *callback, const char *callbackMethod) : m_variable(variable), m_callback(callback), m_callbackMethod(callbackMethod) {} void handle(const ResultRecord &r) override { if (!m_variable) return; bool hasValue = false; MIVariable* variable = m_variable.data(); variable->deleteChildren(); variable->setInScope(true); if (r.reason == "error") { variable->setShowError(true); } else { variable->setVarobj(r["name"].literal()); bool hasMore = false; if (r.hasField("has_more") && r["has_more"].toInt()) // GDB swears there are more children. Trust it hasMore = true; else // There are no more children in addition to what // numchild reports. But, in KDevelop, the variable // is not yet expanded, and those numchild are not // fetched yet. So, if numchild != 0, hasMore should // be true. hasMore = r["numchild"].toInt() != 0; variable->setHasMore(hasMore); variable->setType(r["type"].literal()); variable->setValue(r["value"].literal()); hasValue = !r["value"].literal().isEmpty(); if (variable->isExpanded() && r["numchild"].toInt()) { variable->fetchMoreChildren(); } if (variable->format() != KDevelop::Variable::Natural) { //TODO doesn't work for children as they are not yet loaded variable->formatChanged(); } } if (m_callback && m_callbackMethod) { QMetaObject::invokeMethod(m_callback, m_callbackMethod, Q_ARG(bool, hasValue)); } } bool handlesError() override { return true; } private: QPointer m_variable; QObject *m_callback; const char *m_callbackMethod; }; void MIVariable::attachMaybe(QObject *callback, const char *callbackMethod) { if (!varobj_.isEmpty()) return; // Try find a current session and attach to it if (!ICore::self()->debugController()) return; //happens on shutdown debugSession = static_cast(ICore::self()->debugController()->currentSession()); if (sessionIsAlive()) { debugSession->addCommand(VarCreate, QString("var%1 @ %2").arg(nextId++).arg(enquotedExpression()), new CreateVarobjHandler(this, callback, callbackMethod)); } } void MIVariable::markAsDead() { varobj_.clear(); } class FetchMoreChildrenHandler : public MICommandHandler { public: FetchMoreChildrenHandler(MIVariable *variable, MIDebugSession *session) : m_variable(variable), m_session(session), m_activeCommands(1) {} void handle(const ResultRecord &r) override { if (!m_variable) return; --m_activeCommands; MIVariable* variable = m_variable.data(); if (r.hasField("children")) { const Value& children = r["children"]; for (int i = 0; i < children.size(); ++i) { const Value& child = children[i]; const QString& exp = child["exp"].literal(); if (exp == "public" || exp == "protected" || exp == "private") { ++m_activeCommands; m_session->addCommand(VarListChildren, QString("--all-values \"%1\"").arg(child["name"].literal()), this/*use again as handler*/); } else { KDevelop::Variable* xvar = m_session->variableController()-> createVariable(variable->model(), variable, child["exp"].literal()); MIVariable* var = static_cast(xvar); var->setTopLevel(false); var->setVarobj(child["name"].literal()); bool hasMore = child["numchild"].toInt() != 0 || ( child.hasField("dynamic") && child["dynamic"].toInt()!=0 ); var->setHasMoreInitial(hasMore); variable->appendChild(var); var->setType(child["type"].literal()); var->setValue(child["value"].literal()); } } } /* Note that we don't set hasMore to true if there are still active commands. The reason is that we don't want the user to have even theoretical ability to click on "..." item and confuse us. */ bool hasMore = false; if (r.hasField("has_more")) hasMore = r["has_more"].toInt(); variable->setHasMore(hasMore); if (m_activeCommands == 0) { variable->emitAllChildrenFetched(); delete this; } } bool handlesError() override { // FIXME: handle error? return false; } bool autoDelete() override { // we delete ourselve return false; } private: QPointer m_variable; MIDebugSession *m_session; int m_activeCommands; }; void MIVariable::fetchMoreChildren() { int c = childItems.size(); // FIXME: should not even try this if app is not started. // Probably need to disable open, or something if (sessionIsAlive()) { debugSession->addCommand(VarListChildren, QString("--all-values \"%1\" %2 %3") // fetch from .. to .. .arg(varobj_).arg(c).arg(c + fetchStep), new FetchMoreChildrenHandler(this, debugSession)); } } void MIVariable::handleUpdate(const Value& var) { if (var.hasField("type_changed") && var["type_changed"].literal() == "true") { deleteChildren(); // FIXME: verify that this check is right. setHasMore(var["new_num_children"].toInt() != 0); fetchMoreChildren(); } if (var.hasField("in_scope") && var["in_scope"].literal() == "false") { setInScope(false); } else { setInScope(true); if (var.hasField("new_num_children")) { int nc = var["new_num_children"].toInt(); Q_ASSERT(nc != -1); setHasMore(false); while (childCount() > nc) { TreeItem *c = child(childCount()-1); removeChild(childCount()-1); delete c; } } // FIXME: the below code is essentially copy-paste from // FetchMoreChildrenHandler. We need to introduce GDB-specific // subclass of KDevelop::Variable that is capable of creating // itself from MI output directly, and relay to that. if (var.hasField("new_children")) { const Value& children = var["new_children"]; for (int i = 0; i < children.size(); ++i) { const Value& child = children[i]; const QString& exp = child["exp"].literal(); if (debugSession) { auto xvar = debugSession->variableController()->createVariable(model(), this, exp); auto var = static_cast(xvar); var->setTopLevel(false); var->setVarobj(child["name"].literal()); bool hasMore = child["numchild"].toInt() != 0 || ( child.hasField("dynamic") && child["dynamic"].toInt()!=0 ); var->setHasMoreInitial(hasMore); appendChild(var); var->setType(child["type"].literal()); var->setValue(child["value"].literal()); var->setChanged(true); } } } if (var.hasField("type_changed") && var["type_changed"].literal() == "true") { setType(var["new_type"].literal()); } setValue(var["value"].literal()); setChanged(true); setHasMore(var.hasField("has_more") && var["has_more"].toInt()); } } const QString& MIVariable::varobj() const { return varobj_; } QString MIVariable::enquotedExpression() const { QString expr = expression(); expr.replace('"', "\\\""); expr = expr.prepend('"').append('"'); return expr; } class SetFormatHandler : public MICommandHandler { public: SetFormatHandler(MIVariable *var) : m_variable(var) {} void handle(const ResultRecord &r) override { if(r.hasField("value")) m_variable.data()->setValue(r["value"].literal()); } private: QPointer m_variable; }; void MIVariable::formatChanged() { if(childCount()) { foreach(TreeItem* item, childItems) { Q_ASSERT(dynamic_cast(item)); if( MIVariable* var=dynamic_cast(item)) var->setFormat(format()); } } else { 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/mivariable.h b/debuggers/common/mivariable.h index d774199614..25054166ea 100644 --- a/debuggers/common/mivariable.h +++ b/debuggers/common/mivariable.h @@ -1,80 +1,80 @@ /* * MI based debugger specific Variable * * Copyright 2009 Vladimir Prus * * 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) any later version. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MIVARIABLE_H #define MIVARIABLE_H #include "mi/mi.h" #include #include #include class CreateVarobjHandler; class FetchMoreChildrenHandler; namespace KDevMI { class MIDebugSession; class MIVariable : public KDevelop::Variable { public: MIVariable(MIDebugSession *session, KDevelop::TreeModel* model, KDevelop::TreeItem* parent, const QString& expression, const QString& display = ""); ~MIVariable(); /* FIXME: should eventually remove, so that existance of varobjs is fully encapsulalated inside GdbVariable. */ const QString& varobj() const; void handleUpdate(const MI::Value& var); /* Called when debugger dies. Clears the association between varobj names and Variable instances. */ void markAsDead(); 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; QString enquotedExpression() const; bool sessionIsAlive() const; void setVarobj(const QString& v); QString varobj_; QPointer debugSession; // How many children should be fetched in one // increment. static const int fetchStep = 5; }; } // end of KDevMI #endif diff --git a/debuggers/common/mivariablecontroller.cpp b/debuggers/common/mivariablecontroller.cpp index cc147350a9..5ac8b75209 100644 --- a/debuggers/common/mivariablecontroller.cpp +++ b/debuggers/common/mivariablecontroller.cpp @@ -1,255 +1,257 @@ /* * GDB Debugger Support * * Copyright 2007 Hamish Rodda * Copyright 2008 Vladimir Prus * Copyright 2009 Niko Sams * * 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) any later version. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mivariablecontroller.h" #include "debuglog.h" #include "midebugsession.h" #include "mivariable.h" #include "mi/mi.h" #include "mi/micommand.h" #include "stringhelpers.h" #include #include #include #include #include #include using namespace KDevelop; using namespace KDevMI; using namespace KDevMI::MI; using KTextEditor::Cursor; using KTextEditor::Document; using KTextEditor::Range; MIVariableController::MIVariableController(MIDebugSession *parent) : IVariableController(parent) { Q_ASSERT(parent); connect(parent, &MIDebugSession::inferiorStopped, this, &MIVariableController::programStopped); connect(parent, &MIDebugSession::stateChanged, this, &MIVariableController::stateChanged); } MIDebugSession *MIVariableController::debugSession() const { return static_cast(const_cast(QObject::parent())); } void MIVariableController::programStopped(const AsyncRecord& r) { if (debugSession()->debuggerStateIsOn(s_shuttingDown)) return; if (r.hasField("reason") && r["reason"].literal() == "function-finished" && r.hasField("gdb-result-var")) { variableCollection()->watches()->addFinishResult(r["gdb-result-var"].literal()); } else { variableCollection()->watches()->removeFinishResult(); } } void MIVariableController::update() { - qCDebug(DEBUGGERCOMMON) << autoUpdate(); + qCDebug(DEBUGGERCOMMON) << "autoUpdate =" << autoUpdate(); if (autoUpdate() & UpdateWatches) { variableCollection()->watches()->reinstall(); } if (autoUpdate() & UpdateLocals) { updateLocals(); } if ((autoUpdate() & UpdateLocals) || ((autoUpdate() & UpdateWatches) && variableCollection()->watches()->childCount() > 0)) { debugSession()->addCommand(VarUpdate, "--all-values *", this, &MIVariableController::handleVarUpdate); } } void MIVariableController::handleVarUpdate(const ResultRecord& r) { const Value& changed = r["changelist"]; for (int i = 0; i < changed.size(); ++i) { const Value& var = changed[i]; MIVariable* v = debugSession()->findVariableByVarobjName(var["name"].literal()); // v can be NULL here if we've already removed locals after step, // but the corresponding -var-delete command is still in the queue. if (v) { v->handleUpdate(var); } } } class StackListArgumentsHandler : public MICommandHandler { public: StackListArgumentsHandler(QStringList localsName) : m_localsName(localsName) {} void handle(const ResultRecord &r) override { 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(); + } } } private: QStringList m_localsName; }; class StackListLocalsHandler : public MICommandHandler { public: StackListLocalsHandler(MIDebugSession *session) : m_session(session) {} 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: MIDebugSession *m_session; }; void MIVariableController::updateLocals() { debugSession()->addCommand(StackListLocals, "--simple-values", new StackListLocalsHandler(debugSession())); } Range MIVariableController::expressionRangeUnderCursor(Document* doc, const Cursor& cursor) { QString line = doc->line(cursor.line()); int index = cursor.column(); QChar c = line[index]; if (!c.isLetterOrNumber() && c != '_') return {}; int start = Utils::expressionAt(line, index+1); int end = index; for (; end < line.size(); ++end) { QChar c = line[end]; if (!(c.isLetterOrNumber() || c == '_')) break; } if (!(start < end)) return {}; return { Cursor{cursor.line(), start}, Cursor{cursor.line(), end} }; } void MIVariableController::addWatch(KDevelop::Variable* variable) { // FIXME: should add async 'get full expression' method // to MIVariable, not poke at varobj. In that case, // we will be able to make addWatch a generic method, not // gdb-specific one. if (MIVariable *gv = dynamic_cast(variable)) { debugSession()->addCommand(VarInfoPathExpression, gv->varobj(), this, &MIVariableController::addWatch); } } void MIVariableController::addWatchpoint(KDevelop::Variable* variable) { // FIXME: should add async 'get full expression' method // to MIVariable, not poke at varobj. In that case, // we will be able to make addWatchpoint a generic method, not // gdb-specific one. if (MIVariable *gv = dynamic_cast(variable)) { debugSession()->addCommand(VarInfoPathExpression, gv->varobj(), this, &MIVariableController::addWatchpoint); } } 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()); } } void MIVariableController::addWatchpoint(const ResultRecord& r) { if (r.reason == "done" && !r["path_expr"].literal().isEmpty()) { KDevelop::ICore::self()->debugController()->breakpointModel()->addWatchpoint(r["path_expr"].literal()); } } Variable* MIVariableController::createVariable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) { return new MIVariable(debugSession(), model, parent, expression, display); } void MIVariableController::handleEvent(IDebugSession::event_t event) { IVariableController::handleEvent(event); } void MIVariableController::stateChanged(IDebugSession::DebuggerState state) { if (state == IDebugSession::EndedState) { debugSession()->markAllVariableDead(); } } diff --git a/debuggers/common/mivariablecontroller.h b/debuggers/common/mivariablecontroller.h index 85d357a171..2fb2c621d6 100644 --- a/debuggers/common/mivariablecontroller.h +++ b/debuggers/common/mivariablecontroller.h @@ -1,76 +1,78 @@ /* * Variable controller implementation common to MI based debugger * * Copyright 2007 Hamish Rodda * Copyright 2008 Vladimir Prus * Copyright 2009 Niko Sams * * 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) any later version. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MIVARIABLECONTROLLER_H #define MIVARIABLECONTROLLER_H #include "dbgglobal.h" #include namespace KDevMI { namespace MI { struct AsyncRecord; struct ResultRecord; struct Value; } class MIDebugSession; class MIVariableController : public KDevelop::IVariableController { Q_OBJECT public: MIVariableController( MIDebugSession* parent); KDevelop::Variable* createVariable(KDevelop::TreeModel* model, KDevelop::TreeItem* parent, const QString& expression, const QString& display = "") override; KTextEditor::Range expressionRangeUnderCursor(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor) override; void addWatch(KDevelop::Variable* variable) override; 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); void addWatchpoint(const MI::ResultRecord& r); void handleEvent(KDevelop::IDebugSession::event_t event) override; }; } // end of namespace KDevMI #endif // MIVARIABLECONTROLLER_H diff --git a/debuggers/common/widgets/debuggerconsoleview.cpp b/debuggers/common/widgets/debuggerconsoleview.cpp new file mode 100644 index 0000000000..7d1f20f9a1 --- /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.h b/debuggers/common/widgets/debuggerconsoleview.h new file mode 100644 index 0000000000..f8ef6de369 --- /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.ui b/debuggers/common/widgets/debuggerconsoleview.ui new file mode 100644 index 0000000000..3af354cdc0 --- /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.cpp b/debuggers/common/widgets/disassemblewidget.cpp index 2948ce5e5e..cd81140d2b 100644 --- a/debuggers/common/widgets/disassemblewidget.cpp +++ b/debuggers/common/widgets/disassemblewidget.cpp @@ -1,538 +1,540 @@ /* * GDB Debugger Support * * Copyright 2000 John Birch * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2013 Vlas Puhov * * 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) any later version. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "disassemblewidget.h" #include "midebuggerplugin.h" #include "debuglog.h" #include "midebugsession.h" #include "mi/micommand.h" #include "registers/registersmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevMI; using namespace KDevMI::MI; SelectAddressDialog::SelectAddressDialog(QWidget* parent) : QDialog(parent) { m_ui.setupUi(this); setWindowTitle(i18n("Address Selector")); connect(m_ui.comboBox, &KHistoryComboBox::editTextChanged, this, &SelectAddressDialog::validateInput); connect(m_ui.comboBox, static_cast(&KHistoryComboBox::returnPressed), this, &SelectAddressDialog::itemSelected); } QString SelectAddressDialog::address() const { return hasValidAddress() ? m_ui.comboBox->currentText() : QString(); } bool SelectAddressDialog::hasValidAddress() const { bool ok; m_ui.comboBox->currentText().toLongLong(&ok, 16); return ok; } void SelectAddressDialog::updateOkState() { m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(hasValidAddress()); } void SelectAddressDialog::validateInput() { updateOkState(); } void SelectAddressDialog::itemSelected() { QString text = m_ui.comboBox->currentText(); if( hasValidAddress() && m_ui.comboBox->findText(text) < 0 ) m_ui.comboBox->addItem(text); } DisassembleWindow::DisassembleWindow(QWidget *parent, DisassembleWidget* widget) : QTreeWidget(parent) { /*context menu commands */{ m_selectAddrAction = new QAction(i18n("Change &address"), this); m_selectAddrAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(m_selectAddrAction, &QAction::triggered, widget, &DisassembleWidget::slotChangeAddress); m_jumpToLocation = new QAction(QIcon::fromTheme("debug-execute-to-cursor"), i18n("&Jump to Cursor"), this); m_jumpToLocation->setWhatsThis(i18n("Sets the execution pointer to the current cursor position.")); connect(m_jumpToLocation,&QAction::triggered, widget, &DisassembleWidget::jumpToCursor); m_runUntilCursor = new QAction(QIcon::fromTheme("debug-run-cursor"), i18n("&Run to Cursor"), this); m_runUntilCursor->setWhatsThis(i18n("Continues execution until the cursor position is reached.")); connect(m_runUntilCursor,&QAction::triggered, widget, &DisassembleWidget::runToCursor); m_disassemblyFlavorAtt = new QAction(i18n("&AT&&T"), this); m_disassemblyFlavorAtt->setToolTip(i18n("GDB will use the AT&T disassembly flavor (e.g. mov 0xc(%ebp),%eax).")); m_disassemblyFlavorAtt->setData(DisassemblyFlavorATT); m_disassemblyFlavorAtt->setCheckable(true); m_disassemblyFlavorIntel = new QAction(i18n("&Intel"), this); m_disassemblyFlavorIntel->setToolTip(i18n("GDB will use the Intel disassembly flavor (e.g. mov eax, DWORD PTR [ebp+0xc]).")); m_disassemblyFlavorIntel->setData(DisassemblyFlavorIntel); m_disassemblyFlavorIntel->setCheckable(true); m_disassemblyFlavorActionGroup = new QActionGroup(this); m_disassemblyFlavorActionGroup->setExclusive(true); m_disassemblyFlavorActionGroup->addAction(m_disassemblyFlavorAtt); m_disassemblyFlavorActionGroup->addAction(m_disassemblyFlavorIntel); connect(m_disassemblyFlavorActionGroup, &QActionGroup::triggered, widget, &DisassembleWidget::setDisassemblyFlavor); } } void DisassembleWindow::setDisassemblyFlavor(DisassemblyFlavor flavor) { switch(flavor) { default: case DisassemblyFlavorUnknown: m_disassemblyFlavorAtt->setChecked(false); m_disassemblyFlavorIntel->setChecked(false); break; case DisassemblyFlavorATT: m_disassemblyFlavorAtt->setChecked(true); m_disassemblyFlavorIntel->setChecked(false); break; case DisassemblyFlavorIntel: m_disassemblyFlavorAtt->setChecked(false); m_disassemblyFlavorIntel->setChecked(true); break; } } void DisassembleWindow::contextMenuEvent(QContextMenuEvent *e) { QMenu popup(this); popup.addAction(m_selectAddrAction); popup.addAction(m_jumpToLocation); popup.addAction(m_runUntilCursor); QMenu* disassemblyFlavorMenu = popup.addMenu(i18n("Disassembly flavor")); disassemblyFlavorMenu->addAction(m_disassemblyFlavorAtt); disassemblyFlavorMenu->addAction(m_disassemblyFlavorIntel); popup.exec(e->globalPos()); } /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ DisassembleWidget::DisassembleWidget(MIDebuggerPlugin* plugin, QWidget *parent) : QWidget(parent), active_(false), lower_(0), upper_(0), address_(0), m_splitter(new KDevelop::AutoOrientedSplitter(this)) { QVBoxLayout* topLayout = new QVBoxLayout(this); QHBoxLayout* controlsLayout = new QHBoxLayout; topLayout->addLayout(controlsLayout); { // initialize disasm/registers views topLayout->addWidget(m_splitter); //topLayout->setMargin(0); m_disassembleWindow = new DisassembleWindow(m_splitter, this); m_disassembleWindow->setWhatsThis(i18n("Machine code display

" "A machine code view into your running " "executable with the current instruction " "highlighted. You can step instruction by " "instruction using the debuggers toolbar " "buttons of \"step over\" instruction and " "\"step into\" instruction.")); m_disassembleWindow->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_disassembleWindow->setSelectionMode(QTreeWidget::SingleSelection); m_disassembleWindow->setColumnCount(ColumnCount); m_disassembleWindow->setUniformRowHeights(true); m_disassembleWindow->setRootIsDecorated(false); m_disassembleWindow->setHeaderLabels(QStringList() << "" << i18n("Address") << i18n("Function") << i18n("Instruction")); m_splitter->setStretchFactor(0, 1); m_splitter->setContentsMargins(0, 0, 0, 0); m_registersManager = new RegistersManager(m_splitter); m_config = KSharedConfig::openConfig()->group("Disassemble/Registers View"); QByteArray state = m_config.readEntry("splitterState", QByteArray()); if (!state.isEmpty()) { m_splitter->restoreState(state); } } setLayout(topLayout); setWindowIcon( QIcon::fromTheme("system-run", windowIcon()) ); setWindowTitle(i18n("Disassemble/Registers View")); KDevelop::IDebugController* pDC=KDevelop::ICore::self()->debugController(); Q_ASSERT(pDC); connect(pDC, &KDevelop::IDebugController::currentSessionChanged, this, &DisassembleWidget::currentSessionChanged); connect(plugin, &MIDebuggerPlugin::reset, this, &DisassembleWidget::slotDeactivate); m_dlg = new SelectAddressDialog(this); // show the data if debug session is active KDevelop::IDebugSession* pS = pDC->currentSession(); currentSessionChanged(pS); if(pS && !pS->currentAddr().isEmpty()) slotShowStepInSource(pS->currentUrl(), pS->currentLine(), pS->currentAddr()); } void DisassembleWidget::jumpToCursor() { MIDebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if (s && s->isRunning()) { QString address = m_disassembleWindow->selectedItems().at(0)->text(Address); s->jumpToMemoryAddress(address); } } void DisassembleWidget::runToCursor(){ MIDebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if (s && s->isRunning()) { QString address = m_disassembleWindow->selectedItems().at(0)->text(Address); s->runUntil(address); } } void DisassembleWidget::currentSessionChanged(KDevelop::IDebugSession* s) { MIDebugSession *session = qobject_cast(s); enableControls( session != NULL ); // disable if session closed m_registersManager->setSession(session); if (session) { connect(session, &MIDebugSession::showStepInSource, this, &DisassembleWidget::slotShowStepInSource); connect(session,&MIDebugSession::showStepInDisassemble,this, &DisassembleWidget::update); } } /***************************************************************************/ DisassembleWidget::~DisassembleWidget() { m_config.writeEntry("splitterState", m_splitter->saveState()); } /***************************************************************************/ bool DisassembleWidget::displayCurrent() { if(address_ < lower_ || address_ > upper_) return false; bool bFound=false; for (int line=0; line < m_disassembleWindow->topLevelItemCount(); line++) { QTreeWidgetItem* item = m_disassembleWindow->topLevelItem(line); unsigned long address = item->text(Address).toULong(&ok,16); if (address == address_) { // put cursor at start of line and highlight the line m_disassembleWindow->setCurrentItem(item); static const QIcon icon = QIcon::fromTheme(QStringLiteral("go-next")); item->setIcon(Icon, icon); bFound = true; // need to process all items to clear icons } else if(!item->icon(Icon).isNull()) item->setIcon(Icon, QIcon()); } return bFound; } /***************************************************************************/ void DisassembleWidget::slotActivate(bool activate) { qCDebug(DEBUGGERCOMMON) << "Disassemble widget active: " << activate; if (active_ != activate) { active_ = activate; if (active_) { updateDisassemblyFlavor(); m_registersManager->updateRegisters(); if (!displayCurrent()) disassembleMemoryRegion(); } } } /***************************************************************************/ void DisassembleWidget::slotShowStepInSource(const QUrl&, int, const QString& currentAddress) { update(currentAddress); } void DisassembleWidget::updateExecutionAddressHandler(const ResultRecord& r) { const Value& content = r["asm_insns"]; const Value& pc = content[0]; if( pc.hasField("address") ){ QString addr = pc["address"].literal(); address_ = addr.toULong(&ok,16); disassembleMemoryRegion(addr); } } /***************************************************************************/ void DisassembleWidget::disassembleMemoryRegion(const QString& from, const QString& to) { MIDebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) return; //only get $pc if (from.isEmpty()){ s->addCommand(DataDisassemble, "-s \"$pc\" -e \"$pc+1\" -- 0", this, &DisassembleWidget::updateExecutionAddressHandler); }else{ QString cmd = (to.isEmpty())? QString("-s %1 -e \"%1 + 256\" -- 0").arg(from ): QString("-s %1 -e %2+1 -- 0").arg(from).arg(to); // if both addr set s->addCommand(DataDisassemble, cmd, this, &DisassembleWidget::disassembleMemoryHandler); } } /***************************************************************************/ void DisassembleWidget::disassembleMemoryHandler(const ResultRecord& r) { const Value& content = r["asm_insns"]; QString currentFunction; m_disassembleWindow->clear(); for(int i = 0; i < content.size(); ++i) { const Value& line = content[i]; QString addr, fct, offs, inst; if( line.hasField("address") ) addr = line["address"].literal(); if( line.hasField("func-name") ) fct = line["func-name"].literal(); if( line.hasField("offset") ) offs = line["offset"].literal(); if( line.hasField("inst") ) inst = line["inst"].literal(); //We use offset at the same column where function is. if(currentFunction == fct){ if(!fct.isEmpty()){ fct = QString("+") + offs; } }else { currentFunction = fct; } m_disassembleWindow->addTopLevelItem(new QTreeWidgetItem(m_disassembleWindow, QStringList() << QString() << addr << fct << inst)); if (i == 0) { lower_ = addr.toULong(&ok,16); } else if (i == content.size()-1) { upper_ = addr.toULong(&ok,16); } } displayCurrent(); m_disassembleWindow->resizeColumnToContents(Icon); // make Icon always visible m_disassembleWindow->resizeColumnToContents(Address); // make entire address always visible } void DisassembleWidget::showEvent(QShowEvent*) { slotActivate(true); //it doesn't work for large names of functions // for (int i = 0; i < m_disassembleWindow->model()->columnCount(); ++i) // m_disassembleWindow->resizeColumnToContents(i); } void DisassembleWidget::hideEvent(QHideEvent*) { slotActivate(false); } void DisassembleWidget::slotDeactivate() { slotActivate(false); } void DisassembleWidget::enableControls(bool enabled) { m_disassembleWindow->setEnabled(enabled); } void DisassembleWidget::slotChangeAddress() { if(!m_dlg) return; m_dlg->updateOkState(); if (!m_disassembleWindow->selectedItems().isEmpty()) { m_dlg->setAddress(m_disassembleWindow->selectedItems().first()->text(Address)); } if (m_dlg->exec() == QDialog::Rejected) return; unsigned long addr = m_dlg->address().toULong(&ok,16); if (addr < lower_ || addr > upper_ || !displayCurrent()) disassembleMemoryRegion(m_dlg->address()); } void SelectAddressDialog::setAddress ( const QString& address ) { m_ui.comboBox->setCurrentItem ( address, true ); } void DisassembleWidget::update(const QString &address) { if (!active_) { return; } address_ = address.toULong(&ok, 16); if (!displayCurrent()) { disassembleMemoryRegion(); } m_registersManager->updateRegisters(); } void DisassembleWidget::setDisassemblyFlavor(QAction* action) { MIDebugSession* s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) { return; } DisassemblyFlavor disassemblyFlavor = static_cast(action->data().toInt()); QString cmd; switch(disassemblyFlavor) { default: // unknown flavor, do not build a GDB command break; case DisassemblyFlavorATT: cmd = QStringLiteral("disassembly-flavor att"); break; case DisassemblyFlavorIntel: cmd = QStringLiteral("disassembly-flavor intel"); break; } qCDebug(DEBUGGERCOMMON) << "Disassemble widget set " << cmd; if (!cmd.isEmpty()) { s->addCommand(GdbSet, cmd, this, &DisassembleWidget::setDisassemblyFlavorHandler); } } void DisassembleWidget::setDisassemblyFlavorHandler(const ResultRecord& r) { if (r.reason == "done" && active_) { disassembleMemoryRegion(); } } void DisassembleWidget::updateDisassemblyFlavor() { MIDebugSession* s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) { return; } s->addCommand(GdbShow, QStringLiteral("disassembly-flavor"), this, &DisassembleWidget::showDisassemblyFlavorHandler); } void DisassembleWidget::showDisassemblyFlavorHandler(const ResultRecord& r) { const Value& value = r["value"]; qCDebug(DEBUGGERCOMMON) << "Disassemble widget disassembly flavor" << value.literal(); DisassemblyFlavor disassemblyFlavor = DisassemblyFlavorUnknown; if (value.literal() == "att") { disassemblyFlavor = DisassemblyFlavorATT; } else if (value.literal() == "intel") { disassemblyFlavor = DisassemblyFlavorIntel; + } else if (value.literal() == "default") { + disassemblyFlavor = DisassemblyFlavorATT; } m_disassembleWindow->setDisassemblyFlavor(disassemblyFlavor); } diff --git a/debuggers/common/widgets/disassemblewidget.h b/debuggers/common/widgets/disassemblewidget.h index eb03e84173..ee8b1816c2 100644 --- a/debuggers/common/widgets/disassemblewidget.h +++ b/debuggers/common/widgets/disassemblewidget.h @@ -1,174 +1,172 @@ /* * GDB Debugger Support * * Copyright 1999 John Birch * Copyright 2007 Hamish Rodda * Copyright 2013 Vlas Puhov * * 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) any later version. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _DISASSEMBLEWIDGET_H_ #define _DISASSEMBLEWIDGET_H_ #include "mi/mi.h" #include #include #include #include "ui_selectaddressdialog.h" /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ class QSplitter; namespace KDevelop { class IDebugSession; } namespace KDevMI { class RegistersManager; class SelectAddressDialog : public QDialog { Q_OBJECT public: SelectAddressDialog(QWidget *parent = 0); QString address() const; void setAddress(const QString& address); bool hasValidAddress() const; void updateOkState(); private Q_SLOTS: void validateInput(); void itemSelected(); private: Ui::SelectAddressDialog m_ui; }; class DisassembleWidget; enum DisassemblyFlavor { DisassemblyFlavorUnknown = -1, DisassemblyFlavorATT = 0, DisassemblyFlavorIntel, }; class DisassembleWindow : public QTreeWidget { public: DisassembleWindow(QWidget *parent, DisassembleWidget* widget); void setDisassemblyFlavor(DisassemblyFlavor flavor); protected: void contextMenuEvent(QContextMenuEvent *e) override; private: QAction* m_selectAddrAction; QAction* m_jumpToLocation; QAction* m_runUntilCursor; QAction* m_disassemblyFlavorAtt; QAction* m_disassemblyFlavorIntel; QActionGroup* m_disassemblyFlavorActionGroup; }; -class Breakpoint; -class DebugSession; class MIDebuggerPlugin; class DisassembleWidget : public QWidget { Q_OBJECT public: enum Columns { Icon, Address, Function, Instruction, ColumnCount }; DisassembleWidget( MIDebuggerPlugin* plugin, QWidget *parent=0 ); ~DisassembleWidget() override; Q_SIGNALS: void requestRaise(); public Q_SLOTS: void slotActivate(bool activate); void slotDeactivate(); void slotShowStepInSource(const QUrl &fileName, int lineNum, const QString &address); void slotChangeAddress(); ///Disassembles code at @p address and updates registers void update(const QString &address); void jumpToCursor(); void runToCursor(); void setDisassemblyFlavor(QAction* action); private Q_SLOTS: void currentSessionChanged(KDevelop::IDebugSession* session); protected: void showEvent(QShowEvent*) override; void hideEvent(QHideEvent*) override; void enableControls(bool enabled); private: bool displayCurrent(); void updateDisassemblyFlavor(); /// Disassembles memory region from..to /// if from is empty current execution position is used /// if to is empty, 256 bytes range is taken void disassembleMemoryRegion(const QString& from=QString(), const QString& to=QString() ); /// callbacks for GDBCommands void disassembleMemoryHandler(const MI::ResultRecord& r); void updateExecutionAddressHandler(const MI::ResultRecord& r); void setDisassemblyFlavorHandler(const MI::ResultRecord& r); void showDisassemblyFlavorHandler(const MI::ResultRecord& r); //for str to uint conversion. bool ok; bool active_; unsigned long lower_; unsigned long upper_; unsigned long address_; RegistersManager* m_registersManager ; DisassembleWindow * m_disassembleWindow; SelectAddressDialog* m_dlg; KConfigGroup m_config; QSplitter *m_splitter; }; } // end of namespace KDevMI #endif diff --git a/debuggers/gdb/CMakeLists.txt b/debuggers/gdb/CMakeLists.txt index acd799212f..7cb150a14a 100644 --- a/debuggers/gdb/CMakeLists.txt +++ b/debuggers/gdb/CMakeLists.txt @@ -1,90 +1,72 @@ 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) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) #add_subdirectory(tests) ########### next target ############### set(kdevgdb_SRCS debuggerplugin.cpp gdb.cpp gdboutputwidget.cpp # debuggertracingdialog.cpp debugsession.cpp gdbbreakpointcontroller.cpp gdbconfigpage.cpp variablecontroller.cpp gdbframestackmodel.cpp gdbvariable.cpp ) if(OKTETA_FOUND) add_definitions(-DWITH_OKTETA=1) list(APPEND kdevgdb_SRCS memviewdlg.cpp) endif() set(kdevgdb_UI debuggertracingdialog.ui gdbconfigpage.ui ) kde_enable_exceptions() ki18n_wrap_ui(kdevgdb_SRCS ${kdevgdb_UI}) qt5_add_resources(kdevgdb_SRCS kdevgdb.qrc) kdevplatform_add_plugin(kdevgdb JSON kdevgdb.json SOURCES ${kdevgdb_SRCS}) target_link_libraries(kdevgdb KDev::Sublime KDev::Interfaces KDev::Language KDev::Debugger KDev::OutputView KDev::Project KDev::Util KF5::TextEditor kdevdebuggercommon ) set(test_gdb_SRCS unittests/test_gdb.cpp ${kdevgdb_SRCS}) ecm_add_test(${test_gdb_SRCS} TEST_NAME test_gdb LINK_LIBRARIES Qt5::Test KDev::Shell KDev::Interfaces KDev::Project KDev::Debugger KDev::Tests KDev::Util KF5::KIOWidgets KF5::TextEditor KF5::Parts kdevdebuggercommon ) if (HAVE_PATH_WITH_SPACES_TEST) set_target_properties(test_gdb PROPERTIES COMPILE_FLAGS "-DHAVE_PATH_WITH_SPACES_TEST") endif() diff --git a/debuggers/gdb/debuggerplugin.cpp b/debuggers/gdb/debuggerplugin.cpp index 813f53760a..c77bba0eda 100644 --- a/debuggers/gdb/debuggerplugin.cpp +++ b/debuggers/gdb/debuggerplugin.cpp @@ -1,110 +1,125 @@ // /* // * GDB Debugger Support // * // * Copyright 1999-2001 John Birch // * Copyright 2001 by Bernd Gehrmann // * Copyright 2006 Vladimir Prus // * Copyright 2007 Hamish Rodda // * // * 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) any later version. // * // * 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, write to the // * Free Software Foundation, Inc., // * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // */ #include "debuggerplugin.h" #include "widgets/disassemblewidget.h" #include "memviewdlg.h" #include "gdboutputwidget.h" #include "gdbconfigpage.h" #include "debugsession.h" #include #include #include #include #include #include #include using namespace KDevMI::GDB; K_PLUGIN_FACTORY_WITH_JSON(CppDebuggerFactory, "kdevgdb.json", registerPlugin(); ) 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); gdbfactory = new DebuggerToolFactory( this, "org.kdevelop.debugger.ConsoleView",Qt::BottomDockWidgetArea); core()->uiController()->addToolView( i18n("Disassemble/Registers"), disassemblefactory); core()->uiController()->addToolView( i18n("GDB"), gdbfactory); #ifndef WITH_OKTETA memoryviewerfactory = nullptr; #else memoryviewerfactory = new DebuggerToolFactory( this, "org.kdevelop.debugger.MemoryView", Qt::BottomDockWidgetArea); core()->uiController()->addToolView( 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); connect(session, &DebugSession::raiseDebuggerConsoleViews, this, &CppDebuggerPlugin::raiseDebuggerConsoleViews); return session; } #include "debuggerplugin.moc" diff --git a/debuggers/gdb/debuggerplugin.h b/debuggers/gdb/debuggerplugin.h index d2ece37b0d..8c762fb062 100644 --- a/debuggers/gdb/debuggerplugin.h +++ b/debuggers/gdb/debuggerplugin.h @@ -1,88 +1,86 @@ /* * GDB 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 _DEBUGGERPART_H_ #define _DEBUGGERPART_H_ #include "config.h" #include #include #include #include #include #include #include #include #include "midebuggerplugin.h" #include "debugsession.h" class QLabel; class QMenu; class QDBusInterface; class QSignalMapper; class ProcessWidget; class KToolBar; class QAction; namespace KDevelop { class Context; class ProcessLineMaker; } namespace KDevMI { class DisassembleWidget; namespace GDB { class GDBOutputWidget; class MemoryViewerWidget; class CppDebuggerPlugin : public MIDebuggerPlugin { Q_OBJECT public: friend class DebugSession; 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; DebuggerToolFactory* gdbfactory; DebuggerToolFactory* memoryviewerfactory; }; } // end of namespace GDB } // end of namespace KDevMI #endif diff --git a/debuggers/gdb/debugsession.cpp b/debuggers/gdb/debugsession.cpp index 3e59846d50..b98157253f 100644 --- a/debuggers/gdb/debugsession.cpp +++ b/debuggers/gdb/debugsession.cpp @@ -1,281 +1,301 @@ /* * GDB Debugger Support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * 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) any later version. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "debugsession.h" #include "debuglog.h" +#include "debuggerplugin.h" #include "gdb.h" #include "gdbbreakpointcontroller.h" #include "gdbframestackmodel.h" #include "mi/micommand.h" #include "stty.h" #include "variablecontroller.h" #include #include +#include #include #include #include +#include #include #include #include #include #include #include #include #include using namespace KDevMI::GDB; 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) { m_autoDisableASLR = enable; } BreakpointController *DebugSession::breakpointController() const { return m_breakpointController; } VariableController *DebugSession::variableController() const { return m_variableController; } GdbFrameStackModel *DebugSession::frameStackModel() const { return m_frameStackModel; } GdbDebugger *DebugSession::createDebugger() const { return new GdbDebugger; } void DebugSession::initializeDebugger() { //addCommand(new GDBCommand(GDBMI::EnableTimings, "yes")); addCommand(new CliCommand(MI::GdbShow, "version", this, &DebugSession::handleVersion)); // This makes gdb pump a variable out on one line. addCommand(MI::GdbSet, "width 0"); addCommand(MI::GdbSet, "height 0"); addCommand(MI::SignalHandle, "SIG32 pass nostop noprint"); addCommand(MI::SignalHandle, "SIG41 pass nostop noprint"); addCommand(MI::SignalHandle, "SIG42 pass nostop noprint"); addCommand(MI::SignalHandle, "SIG43 pass nostop noprint"); addCommand(MI::EnablePrettyPrinting); addCommand(MI::GdbSet, "charset UTF-8"); addCommand(MI::GdbSet, "print sevenbit-strings off"); QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevgdb/printers/gdbinit"); if (!fileName.isEmpty()) { QFileInfo fileInfo(fileName); QString quotedPrintersPath = fileInfo.dir().path() .replace('\\', "\\\\") .replace('"', "\\\""); addCommand(MI::NonMI, QString("python sys.path.insert(0, \"%0\")").arg(quotedPrintersPath)); addCommand(MI::NonMI, "source " + fileName); } // GDB can't disable ASLR on CI server. addCommand(MI::GdbSet, QStringLiteral("disable-randomization %1").arg(m_autoDisableASLR ? "on" : "off")); 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(); 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); if (displayStaticMembers) { addCommand(MI::GdbSet, "print static-members on"); } else { addCommand(MI::GdbSet, "print static-members off"); } if (asmDemangle) { addCommand(MI::GdbSet, "print asm-demangle on"); } else { 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()) { addCommand(MI::NonMI, "source " + KShell::quoteArg(configGdbScript.toLocalFile())); } // FIXME: have a check box option that controls remote debugging if (runShellScript.isValid()) { // Special for remote debug, the remote inferior is started by this shell script QByteArray tty(m_tty->getSlave().toLatin1()); QByteArray options = QByteArray(">") + tty + QByteArray(" 2>&1 <") + tty; QProcess *proc = new QProcess; QStringList arguments; arguments << "-c" << KShell::quoteArg(runShellScript.toLocalFile()) + ' ' + KShell::quoteArg(executable) + QString::fromLatin1(options); qCDebug(DEBUGGERGDB) << "starting sh" << arguments; proc->start("sh", arguments); //PORTING TODO QProcess::DontCare); } if (runGdbScript.isValid()) { // Special for remote debug, gdb script at run is requested, to connect to remote inferior // Race notice: wait for the remote gdbserver/executable // - but that might be an issue for this script to handle... // Note: script could contain "run" or "continue" // Future: the shell script should be able to pass info (like pid) // to the gdb script... addCommand(new SentinelCommand([this, runGdbScript]() { breakpointController()->initSendBreakpoints(); breakpointController()->setDeleteDuplicateBreakpoints(true); qCDebug(DEBUGGERGDB) << "Running gdb script " << KShell::quoteArg(runGdbScript.toLocalFile()); addCommand(MI::NonMI, "source " + KShell::quoteArg(runGdbScript.toLocalFile()), [this](const MI::ResultRecord&) { breakpointController()->setDeleteDuplicateBreakpoints(false); }, CmdMaybeStartsRunning); raiseEvent(connected_to_program); }, CmdMaybeStartsRunning)); } else { // normal local debugging addCommand(MI::FileExecAndSymbols, KShell::quoteArg(executable), this, &DebugSession::handleFileExecAndSymbols, CmdHandlesError); raiseEvent(connected_to_program); addCommand(new SentinelCommand([this]() { breakpointController()->initSendBreakpoints(); addCommand(MI::ExecRun, QString(), CmdMaybeStartsRunning); }, CmdMaybeStartsRunning)); } return true; } void DebugSession::handleVersion(const QStringList& s) { qCDebug(DEBUGGERGDB) << s.first(); // minimal version is 7.0,0 QRegExp rx("([7-9]+)\\.([0-9]+)(\\.([0-9]+))?"); int idx = rx.indexIn(s.first()); if (idx == -1) { if (!qobject_cast(qApp)) { //for unittest qFatal("You need a graphical application."); } KMessageBox::error( qApp->activeWindow(), i18n("You need gdb 7.0.0 or higher.
" "You are using: %1", s.first()), i18n("gdb error")); stopDebugger(); } } void DebugSession::handleFileExecAndSymbols(const ResultRecord& r) { if (r.reason == "error") { KMessageBox::error( qApp->activeWindow(), i18n("Could not start debugger:
")+ r["msg"].literal(), i18n("Startup error")); stopDebugger(); } } diff --git a/debuggers/gdb/debugsession.h b/debuggers/gdb/debugsession.h index 6fa683c1f0..6e968205c1 100644 --- a/debuggers/gdb/debugsession.h +++ b/debuggers/gdb/debugsession.h @@ -1,104 +1,106 @@ /* * GDB Debugger Support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * 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) any later version. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef GDB_DEBUGSESSION_H #define GDB_DEBUGSESSION_H #include "midebugsession.h" #include "dbgglobal.h" #include "gdb.h" #include "gdbbreakpointcontroller.h" #include "gdbframestackmodel.h" #include "variablecontroller.h" #include "mi/mi.h" #include #include #include class IExecutePlugin; class KToolBar; namespace KDevelop { class ProcessLineMaker; class ILaunchConfiguration; } namespace KDevMI { class STTY; namespace MI { class MICommand; class CommandQueue; } namespace GDB { +class CppDebuggerPlugin; class DebugSession : public MIDebugSession { Q_OBJECT public: - DebugSession(); + explicit DebugSession(CppDebuggerPlugin *plugin = nullptr); ~DebugSession() override; BreakpointController * breakpointController() const override; VariableController * variableController() const override; GdbFrameStackModel * frameStackModel() const override; /// FIXME: only used in unit test currently, potentially could /// be made a user configurable option. /// Whether turn off auto-disable ASLR when starting inferiors void setAutoDisableASLR(bool enable); 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); void handleFileExecAndSymbols(const MI::ResultRecord& r); private: friend class GdbTest; BreakpointController *m_breakpointController; VariableController *m_variableController; GdbFrameStackModel *m_frameStackModel; bool m_autoDisableASLR; }; } // end of namespace GDB } // end of namespace KDevMI #endif diff --git a/debuggers/gdb/gdb.cpp b/debuggers/gdb/gdb.cpp index 79f5bd6b40..4088b4cf82 100644 --- a/debuggers/gdb/gdb.cpp +++ b/debuggers/gdb/gdb.cpp @@ -1,99 +1,99 @@ /* * Low level GDB interface. * * Copyright 1999 John Birch * Copyright 2007 Vladimir Prus * 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) any later version. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gdb.h" #include "dbgglobal.h" #include "debuglog.h" #include #include #include #include #include #include #include #include using namespace KDevMI::GDB; using namespace KDevMI::MI; GdbDebugger::GdbDebugger(QObject* parent) : MIDebugger(parent) { } GdbDebugger::~GdbDebugger() { } 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 { // FIXME: verify its' a local path. debuggerBinary_ = gdbUrl.url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); } 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(); QFileInfo info(shell_without_args); /*if( info.isRelative() ) { shell_without_args = build_dir + "/" + shell_without_args; info.setFile( shell_without_args ); }*/ if(!info.exists()) { KMessageBox::information( qApp->activeWindow(), i18n("Could not locate the debugging shell '%1'.", shell_without_args ), i18n("Debugging Shell Not Found") ); return false; } arguments.insert(0, debuggerBinary_); arguments.insert(0, shell.toLocalFile()); process_->setShellCommand(KShell::joinArgs(arguments)); } else { process_->setProgram(debuggerBinary_, arguments); } process_->start(); qCDebug(DEBUGGERGDB) << "Starting GDB with command" << shell.toLocalFile() + ' ' + debuggerBinary_ + ' ' + arguments.join(' '); qCDebug(DEBUGGERGDB) << "GDB process pid:" << process_->pid(); emit userCommandOutput(shell.toLocalFile() + ' ' + debuggerBinary_ + ' ' + arguments.join(' ') + '\n'); return true; } diff --git a/debuggers/gdb/gdbconfigpage.cpp b/debuggers/gdb/gdbconfigpage.cpp index 7aae1e2617..baf9644a22 100644 --- a/debuggers/gdb/gdbconfigpage.cpp +++ b/debuggers/gdb/gdbconfigpage.cpp @@ -1,191 +1,193 @@ /* * GDB Debugger Support * * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Andreas Pakulat * * 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) any later version. * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gdbconfigpage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include "dbgglobal.h" #include "debugsession.h" #include "debuggerplugin.h" #include "midebugjobs.h" #include "ui_gdbconfigpage.h" #include #include using namespace KDevelop; +namespace Config = KDevMI::GDB::Config; GdbConfigPage::GdbConfigPage( QWidget* parent ) : LaunchConfigurationPage(parent), ui( new Ui::GdbConfigPage ) { ui->setupUi( this ); ui->kcfg_gdbPath->setMode(KFile::File|KFile::ExistingOnly|KFile::LocalOnly); connect(ui->kcfg_asmDemangle, &QCheckBox::toggled, this, &GdbConfigPage::changed); connect(ui->kcfg_configGdbScript, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); //connect(ui->kcfg_dbgTerminal, SIGNAL(toggled(bool)), this, SIGNAL(changed())); connect(ui->kcfg_debuggingShell, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); connect(ui->kcfg_displayStaticMembers, &QCheckBox::toggled, this, &GdbConfigPage::changed); connect(ui->kcfg_gdbPath, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); connect(ui->kcfg_runGdbScript, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); connect(ui->kcfg_runShellScript, &KUrlRequester::textChanged, this, &GdbConfigPage::changed); connect(ui->kcfg_startWith, static_cast(&QComboBox::currentIndexChanged), this, &GdbConfigPage::changed); //Setup data info for combobox ui->kcfg_startWith->setItemData(0, "ApplicationOutput" ); ui->kcfg_startWith->setItemData(1, "GdbConsole" ); ui->kcfg_startWith->setItemData(2, "FrameStack" ); } GdbConfigPage::~GdbConfigPage() { delete ui; } QIcon GdbConfigPage::icon() const { return QIcon(); } 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 { return i18n( "GDB Configuration" ); } GdbLauncher::GdbLauncher( KDevMI::GDB::CppDebuggerPlugin* p, IExecutePlugin* execute ) : m_plugin( p ) , m_execute( execute ) { factoryList << new GdbConfigPageFactory(); } QList< KDevelop::LaunchConfigurationPageFactory* > GdbLauncher::configPages() const { return factoryList; } QString GdbLauncher::id() { return "gdb"; } QString GdbLauncher::name() const { return i18n("GDB"); } KJob* GdbLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { return 0; } if( launchMode == "debug" ) { Q_ASSERT(m_execute); if (KDevelop::ICore::self()->debugController()->currentSession() != nullptr) { KMessageBox::ButtonCode answer = KMessageBox::warningYesNo( nullptr, i18n("A program is already being debugged. Do you want to abort the " "currently running debug session and continue with the launch?")); if (answer == KMessageBox::No) return nullptr; } QList l; KJob* depjob = m_execute->dependencyJob(cfg); if( depjob ) { l << depjob; } l << new KDevMI::MIDebugJob( m_plugin, cfg, m_execute ); return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), l ); } qWarning() << "Unknown launch mode" << launchMode << "for config:" << cfg->name(); return 0; } QStringList GdbLauncher::supportedModes() const { return QStringList() << "debug"; } QString GdbLauncher::description() const { return i18n("Executes a Native application in GDB"); } KDevelop::LaunchConfigurationPage* GdbConfigPageFactory::createWidget( QWidget* parent ) { return new GdbConfigPage( parent ); } diff --git a/debuggers/gdb/unittests/CMakeLists.txt b/debuggers/gdb/unittests/CMakeLists.txt index a8e9e6ce36..3ff060ccd0 100644 --- a/debuggers/gdb/unittests/CMakeLists.txt +++ b/debuggers/gdb/unittests/CMakeLists.txt @@ -1,20 +1,21 @@ add_debuggable_executable(debugee SRCS debugee.cpp) add_debuggable_executable(debugeeslow SRCS debugeeslow.cpp) add_debuggable_executable(debugeecrash SRCS debugeecrash.cpp) add_debuggable_executable(debugeerecursion SRCS debugeerecursion.cpp) 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) add_debuggable_executable(debugeeqt SRCS debugeeqt.cpp) target_link_libraries(debugeeqt Qt5::Core) add_debuggable_executable(debugeeexception SRCS debugeeexception.cpp) kde_target_enable_exceptions(debugeeexception PRIVATE) if (HAVE_PATH_WITH_SPACES_TEST) add_subdirectory("path with space") endif() diff --git a/debuggers/gdb/unittests/debugeeechoenv.cpp b/debuggers/gdb/unittests/debugeeechoenv.cpp new file mode 100644 index 0000000000..3283e0e5de --- /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.cpp b/debuggers/gdb/unittests/test_gdb.cpp index b0f1f43c66..aff8d458d4 100644 --- a/debuggers/gdb/unittests/test_gdb.cpp +++ b/debuggers/gdb/unittests/test_gdb.cpp @@ -1,2043 +1,2093 @@ /* 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" #include "debugsession.h" #include "gdbframestackmodel.h" #include "mi/micommand.h" #include "mi/milexer.h" #include "mi/miparser.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 using KDevelop::AutoTestShell; namespace KDevMI { namespace GDB { QUrl findExecutable(const QString& name) { QFileInfo info(qApp->applicationDirPath() + "/unittests/" + name); Q_ASSERT(info.exists()); Q_ASSERT(info.isExecutable()); return QUrl::fromLocalFile(info.canonicalFilePath()); } QString findSourceFile(const QString& name) { QFileInfo info(QFileInfo(__FILE__).dir().absoluteFilePath(name)); Q_ASSERT(info.exists()); return info.canonicalFilePath(); } 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; } } return false; } #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); } void GdbTest::cleanupTestCase() { KDevelop::TestCore::shutdown(); } void GdbTest::init() { //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()); 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(); } +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: TestLaunchConfiguration(const QUrl& executable = findExecutable("debugee"), const QUrl& workingDirectory = QUrl()) { qDebug() << "FIND" << executable; c = new KConfig(); c->deleteGroup("launch"); cfg = c->group("launch"); cfg.writeEntry("isExecutable", true); cfg.writeEntry("Executable", executable); cfg.writeEntry("Working Directory", workingDirectory); } ~TestLaunchConfiguration() override { delete c; } const KConfigGroup config() const override { return cfg; } KConfigGroup config() override { return cfg; }; 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 { public: TestFrameStackModel(DebugSession* session) : GdbFrameStackModel(session), fetchFramesCalled(0), fetchThreadsCalled(0) {} int fetchFramesCalled; int fetchThreadsCalled; void fetchFrames(int threadNumber, int from, int to) override { fetchFramesCalled++; GdbFrameStackModel::fetchFrames(threadNumber, from, to); } void fetchThreads() override { fetchThreadsCalled++; GdbFrameStackModel::fetchThreads(); } }; class TestDebugSession : public DebugSession { Q_OBJECT public: 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; } private: 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; } 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; } private: QTime stopWatch; QPointer session; const char * condition; const char * file; int line; }; #define WAIT_FOR_STATE(session, state) \ do { if (!waitForState((session), (state), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR_STATE_AND_IDLE(session, state) \ do { if (!waitForState((session), (state), __FILE__, __LINE__, true)) return; } while (0) #define WAIT_FOR(session, condition) \ do { \ TestWaiter w((session), #condition, __FILE__, __LINE__); \ while (w.waitUnless((condition))) /* nothing */ ; \ } while(0) #define COMPARE_DATA(index, expected) \ do { if(!compareData((index), (expected), __FILE__, __LINE__)) return; } while (0) 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; } static const QString debugeeFileName = findSourceFile("debugee.cpp"); KDevelop::BreakpointModel* breakpoints() { return KDevelop::ICore::self()->debugController()->breakpointModel(); } void GdbTest::testStdOut() { TestDebugSession *session = new TestDebugSession; QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); TestLaunchConfiguration cfg; session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); { QCOMPARE(outputSpy.count(), 1); QList arguments = outputSpy.takeFirst(); QCOMPARE(arguments.count(), 1); QCOMPARE(arguments.first().toStringList(), QStringList() << "Hello, world!" << "Hello"); } } +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; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); } void GdbTest::testDisableBreakpoint() { //Description: We must stop only on the third breakpoint int firstBreakLine=28; int secondBreakLine=23; int thirdBreakLine=24; int fourthBreakLine=31; TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b; b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(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); 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); 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->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); QTest::qWait(300); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testChangeLocationBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 27); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); QTest::qWait(100); b->setLine(28); QTest::qWait(100); session->run(); QTest::qWait(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(b->line(), 29); QTest::qWait(100); QCOMPARE(b->line(), 29); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testDeleteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; QCOMPARE(breakpoints()->rowCount(), 0); //add breakpoint before startDebugging breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QCOMPARE(breakpoints()->rowCount(), 1); breakpoints()->removeRow(0); QCOMPARE(breakpoints()->rowCount(), 0); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); 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() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("test_gdb.cpp")), 10); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::PendingState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testUpdateBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; // breakpoint 1: line 29 KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(breakpoints()->rowCount(), 1); 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 28 session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop after step QCOMPARE(breakpoints()->rowCount(), 2); b = breakpoints()->breakpoint(1); QCOMPARE(b->url(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(b->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); // stop at line 29 session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testIgnoreHitsBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); b1->setIgnoreHits(1); KDevelop::Breakpoint * b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); session->startDebugging(&cfg, m_iface); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b2->hitCount() == 1); b2->setIgnoreHits(1); session->run(); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b1->hitCount() == 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 39); b->setCondition("x[0] == 'H'"); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 23); b->setCondition("i==2"); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR(session, session->state() == DebugSession::PausedState && session->line() == 24); b->setCondition("i == 0"); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 39); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnWriteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addWatchpoint("i"); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnWriteWithConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 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); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnReadBreakpoint() { /* test disabled because of gdb bug: http://sourceware.org/bugzilla/show_bug.cgi?id=10136 TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b = breakpoints()->addReadWatchpoint("foo::i"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); */ } void GdbTest::testBreakOnReadBreakpoint2() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addReadWatchpoint("i"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnAccessBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addAccessWatchpoint("i"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointWhileRunning() { 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); QTest::qWait(500); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 25); b->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointWhileRunningMultiple() { 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 *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); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 25); b1->setDeleted(); b2->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointFunctionName() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testManualBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); breakpoints()->removeRows(0, 1); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 0); session->addCommand(MI::NonMI, "break debugee.cpp:23"); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 1); KDevelop::Breakpoint* 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); 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); QCOMPARE(breakpoints()->rowCount(), 0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testShowStepInSource() { TestDebugSession *session = new TestDebugSession; QSignalSpy showStepInSourceSpy(session, &TestDebugSession::showStepInSource); TestLaunchConfiguration cfg; 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); { QCOMPARE(showStepInSourceSpy.count(), 3); QList arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 29); 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); } } void GdbTest::testStack() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(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), 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"); session->stepOut(); WAIT_FOR_STATE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":30"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackFetchMore() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("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"); QCOMPARE(stackModel->rowCount(tIdx), 21); COMPARE_DATA(tIdx.child(0, 0), "0"); 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, 2), fileName+":24"); COMPARE_DATA(tIdx.child(2, 0), "2"); 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); QCOMPARE(stackModel->fetchFramesCalled, 2); QCOMPARE(stackModel->rowCount(tIdx), 41); COMPARE_DATA(tIdx.child(20, 0), "20"); COMPARE_DATA(tIdx.child(21, 0), "21"); COMPARE_DATA(tIdx.child(22, 0), "22"); COMPARE_DATA(tIdx.child(39, 0), "39"); COMPARE_DATA(tIdx.child(40, 0), "40"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 3); QCOMPARE(stackModel->rowCount(tIdx), 121); COMPARE_DATA(tIdx.child(40, 0), "40"); COMPARE_DATA(tIdx.child(41, 0), "41"); COMPARE_DATA(tIdx.child(42, 0), "42"); COMPARE_DATA(tIdx.child(119, 0), "119"); COMPARE_DATA(tIdx.child(120, 0), "120"); stackModel->fetchMoreFrames(); QTest::qWait(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(300, 0), "300"); COMPARE_DATA(tIdx.child(300, 1), "main"); COMPARE_DATA(tIdx.child(300, 2), fileName+":30"); stackModel->fetchMoreFrames(); //nothing to fetch, we are at the end QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackDeactivateAndActive() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(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); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":30"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackSwitchThread() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeethreads")); QString fileName = findSourceFile("debugeethreads.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->rowCount(), 4); QModelIndex tIdx = stackModel->index(0,0); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), fileName+":39"); tIdx = stackModel->index(1,0); QVERIFY(stackModel->data(tIdx).toString().startsWith("#2 at ")); stackModel->setCurrentThread(2); QTest::qWait(200); int rows = stackModel->rowCount(tIdx); QVERIFY(rows > 3); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile("debugeeslow.cpp"); KProcess debugeeProcess; debugeeProcess << "nice" << findExecutable("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); } session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testManualAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile("debugeeslow.cpp"); KProcess debugeeProcess; debugeeProcess << "nice" << findExecutable("debugeeslow").toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); 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())); WAIT_FOR_STATE(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() { QFile f("core"); if (f.exists()) f.remove(); KProcess debugeeProcess; debugeeProcess.setOutputChannelMode(KProcess::MergedChannels); debugeeProcess << "bash" << "-c" << "ulimit -c unlimited; " + findExecutable("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")); TestFrameStackModel *stackModel = session->frameStackModel(); WAIT_FOR_STATE(session, DebugSession::StoppedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } KDevelop::VariableCollection *variableCollection() { return KDevelop::ICore::self()->debugController()->variableCollection(); } void GdbTest::testVariablesLocals() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); 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, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesLocalsStruct() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QModelIndex i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); int structIndex = 0; for(int j=0; j<3; ++j) { if (variableCollection()->index(j, 0, i).data().toString() == "ts") { structIndex = j; } } COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(structIndex, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); QTest::qWait(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"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(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() { TestDebugSession *session = new TestDebugSession; KDevelop::ICore::self()->debugController()->variableCollection()->variableWidgetShown(); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("ts"); QTest::qWait(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); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(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() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; const QString testString("test"); const QString quotedTestString("\"" + testString + "\""); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(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); 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) + "]"); QModelIndex testStr = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, testStr), "..."); variableCollection()->expanded(testStr); QTest::qWait(100); int len = testString.length(); for (int ind = 0; ind < len; ind++) { COMPARE_DATA(variableCollection()->index(ind, 0, testStr), QString::number(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'"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatchesTwoSessions() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("ts"); QTest::qWait(300); QModelIndex ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); variableCollection()->expanded(ts); QTest::qWait(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)); QVERIFY(v); QVERIFY(!v->inScope()); QCOMPARE(v->childCount(), 3); 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); QCOMPARE(variableCollection()->watches()->childCount(), 1); ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); v = dynamic_cast(variableCollection()->watches()->child(0)); QVERIFY(v); QVERIFY(v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(v->inScope()); QCOMPARE(v->data(1, Qt::DisplayRole).toString(), 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)); QVERIFY(!v->inScope()); QVERIFY(!dynamic_cast(v->child(0))->inScope()); } void GdbTest::testVariablesStopDebugger() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stopDebugger(); QTest::qWait(300); } void GdbTest::testVariablesStartSecondSession() { QPointer session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QPointer session2 = new TestDebugSession; session2->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session2->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session2, DebugSession::PausedState); session2->run(); WAIT_FOR_STATE(session2, DebugSession::EndedState); } void GdbTest::testVariablesSwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); 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), "1"); stackModel->setCurrentFrame(1); QTest::qWait(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() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); 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), "1"); stackModel->setCurrentFrame(1); QTest::qWait(300); stackModel->setCurrentFrame(0); QTest::qWait(1); stackModel->setCurrentFrame(1); QTest::qWait(1); stackModel->setCurrentFrame(0); QTest::qWait(1); stackModel->setCurrentFrame(1); QTest::qWait(500); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); QStringList locs; for (int j = 0; j < variableCollection()->rowCount(i); ++j) { locs << variableCollection()->index(j, 0, i).data().toString(); } QVERIFY(locs.contains("argc")); QVERIFY(locs.contains("argv")); QVERIFY(locs.contains("x")); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testSegfaultDebugee() { 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); 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); QCOMPARE(stackModel->currentFrame(), 1); session->addUserCommand("print x"); QTest::qWait(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() { TestDebugSession *session = new TestDebugSession; 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(); 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)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); 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())); + grp.writeEntry(Config::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())); + grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testRemoteDebug() { const QString gdbserverExecutable = QStandardPaths::findExecutable("gdbserver"); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } TestDebugSession *session = new TestDebugSession; 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())); + grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); + grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); 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); } 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())); + grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile(shellScript.fileName()+"-copy")); + grp.writeEntry(Config::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() { 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"); 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.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::RemoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); + grp.writeEntry(Config::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); //************************** 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() { 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); } -//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); } 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); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //start second debug session (same cfg) session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(300); i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); 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() { 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(); 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); 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); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } } // see: https://bugs.kde.org/show_bug.cgi?id=339231 void GdbTest::testPathWithSpace() { #ifdef HAVE_PATH_WITH_SPACES_TEST TestDebugSession* session = new TestDebugSession; auto debugee = findExecutable("path with space/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); 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" diff --git a/debuggers/gdb/unittests/test_gdb.h b/debuggers/gdb/unittests/test_gdb.h index 56aeae167d..39894d15b1 100644 --- a/debuggers/gdb/unittests/test_gdb.h +++ b/debuggers/gdb/unittests/test_gdb.h @@ -1,111 +1,112 @@ /* 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. */ #ifndef GDBTEST_H #define GDBTEST_H #include #include namespace KDevelop { class TestCore; } namespace KDevMI { namespace GDB { class GdbTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void init(); void testStdOut(); + void testEnvironmentSet(); void testBreakpoint(); void testDisableBreakpoint(); void testChangeLocationBreakpoint(); void testDeleteBreakpoint(); void testPendingBreakpoint(); void testUpdateBreakpoint(); void testIgnoreHitsBreakpoint(); void testConditionBreakpoint(); void testBreakOnWriteBreakpoint(); void testBreakOnWriteWithConditionBreakpoint(); void testBreakOnReadBreakpoint(); void testBreakOnReadBreakpoint2(); void testBreakOnAccessBreakpoint(); void testInsertBreakpointWhileRunning(); void testInsertBreakpointWhileRunningMultiple(); void testInsertBreakpointFunctionName(); void testManualBreakpoint(); void testShowStepInSource(); void testStack(); void testStackFetchMore(); void testStackDeactivateAndActive(); void testStackSwitchThread(); void testAttach(); void testManualAttach(); void testCoreFile(); void testVariablesLocals(); void testVariablesLocalsStruct(); void testVariablesWatches(); void testVariablesWatchesQuotes(); void testVariablesWatchesTwoSessions(); void testVariablesStopDebugger(); void testVariablesStartSecondSession(); void testVariablesSwitchFrame(); void testVariablesQuicklySwitchFrame(); 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 testBug301287(); void testMultipleBreakpoint(); void testRegularExpressionBreakpoint(); void testChangeBreakpointWhileRunning(); void testDebugInExternalTerminal(); void testPathWithSpace(); private: bool waitForState(DebugSession *session, KDevelop::IDebugSession::DebuggerState state, const char *file, int line, bool waitForIdle = false); IExecutePlugin* m_iface; }; } // end of namespace GDB } // end of namespace KDevMI #endif // GDBTEST_H diff --git a/debuggers/lldb/CMakeLists.txt b/debuggers/lldb/CMakeLists.txt new file mode 100644 index 0000000000..ea58fb40ae --- /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 index 0000000000..b2b84556b6 --- /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 index 0000000000..5c6fd0f2fd --- /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.cpp b/debuggers/lldb/controllers/breakpointcontroller.cpp new file mode 100644 index 0000000000..ddea36635a --- /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/breakpointcontroller.h b/debuggers/lldb/controllers/breakpointcontroller.h new file mode 100644 index 0000000000..611f2d200c --- /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/framestackmodel.cpp b/debuggers/lldb/controllers/framestackmodel.cpp new file mode 100644 index 0000000000..4ad2476247 --- /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/framestackmodel.h b/debuggers/lldb/controllers/framestackmodel.h new file mode 100644 index 0000000000..46526dd70b --- /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/variable.cpp b/debuggers/lldb/controllers/variable.cpp new file mode 100644 index 0000000000..f0f10cef45 --- /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/variable.h b/debuggers/lldb/controllers/variable.h new file mode 100644 index 0000000000..7521da1b44 --- /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/variablecontroller.cpp b/debuggers/lldb/controllers/variablecontroller.cpp new file mode 100644 index 0000000000..6f93d12e8b --- /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/controllers/variablecontroller.h b/debuggers/lldb/controllers/variablecontroller.h new file mode 100644 index 0000000000..7677bf6469 --- /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/debuggerplugin.cpp b/debuggers/lldb/debuggerplugin.cpp new file mode 100644 index 0000000000..cea9ab9ce2 --- /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/debuggerplugin.h b/debuggers/lldb/debuggerplugin.h new file mode 100644 index 0000000000..bd316da648 --- /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/debugsession.cpp b/debuggers/lldb/debugsession.cpp new file mode 100644 index 0000000000..c0926aead0 --- /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/debugsession.h b/debuggers/lldb/debugsession.h new file mode 100644 index 0000000000..d8663eabe7 --- /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/kdevlldb.json b/debuggers/lldb/kdevlldb.json new file mode 100644 index 0000000000..693ddf2622 --- /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 index 0000000000..d759d431ba --- /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 index 0000000000..d89b435e77 --- /dev/null +++ b/debuggers/lldb/kdevlldbui.rc @@ -0,0 +1,22 @@ + + + +

+ Run + + + + + + + + + + + Debugger Toolbar + + + + + + diff --git a/debuggers/lldb/lldbcommand.cpp b/debuggers/lldb/lldbcommand.cpp new file mode 100644 index 0000000000..5da8d56fc5 --- /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/lldbcommand.h b/debuggers/lldb/lldbcommand.h new file mode 100644 index 0000000000..eea1a4d65a --- /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/lldbdebugger.cpp b/debuggers/lldb/lldbdebugger.cpp new file mode 100644 index 0000000000..a24b08ea90 --- /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/lldbdebugger.h b/debuggers/lldb/lldbdebugger.h new file mode 100644 index 0000000000..95e46af80f --- /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/lldblauncher.cpp b/debuggers/lldb/lldblauncher.cpp new file mode 100644 index 0000000000..d237aa2d89 --- /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/lldblauncher.h b/debuggers/lldb/lldblauncher.h new file mode 100644 index 0000000000..ddc806a5de --- /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/unittests/debugees/CMakeLists.txt b/debuggers/lldb/unittests/debugees/CMakeLists.txt new file mode 100644 index 0000000000..e7f524e819 --- /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 index 0000000000..37b41a5e8d --- /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 index 0000000000..ea47c2c022 --- /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 index 0000000000..876d3a27f2 --- /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 index 0000000000..3283e0e5de --- /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 index 0000000000..3d8d8e3d11 --- /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 index 0000000000..189e95d80d --- /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 index 0000000000..af63b7c073 --- /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 index 0000000000..1ed0c92c3e --- /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 index 0000000000..c62e6659b3 --- /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 index 0000000000..98f8ab1d2b --- /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 index 0000000000..a575a2c6c9 --- /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 index 0000000000..4aa7ccf91a --- /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 index 0000000000..840e243979 --- /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.cpp b/debuggers/lldb/unittests/test_lldb.cpp similarity index 51% copy from debuggers/gdb/unittests/test_gdb.cpp copy to debuggers/lldb/unittests/test_lldb.cpp index b0f1f43c66..e2de10bdae 100644 --- a/debuggers/gdb/unittests/test_gdb.cpp +++ b/debuggers/lldb/unittests/test_lldb.cpp @@ -1,2043 +1,1849 @@ /* + * 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(); c->deleteGroup("launch"); cfg = c->group("launch"); cfg.writeEntry("isExecutable", true); cfg.writeEntry("Executable", executable); cfg.writeEntry("Working Directory", workingDirectory); } ~TestLaunchConfiguration() override { delete c; } const KConfigGroup config() const override { return cfg; } KConfigGroup config() override { return cfg; }; 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 { Q_OBJECT public: 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; } private: 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; int secondBreakLine=23; int thirdBreakLine=24; int fourthBreakLine=31; TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; 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); b2->setIgnoreHits(1); session->run(); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b1->hitCount() == 1); session->run(); 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"); COMPARE_DATA(tIdx.child(21, 0), "21"); COMPARE_DATA(tIdx.child(22, 0), "22"); COMPARE_DATA(tIdx.child(39, 0), "39"); 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"); COMPARE_DATA(tIdx.child(41, 0), "41"); COMPARE_DATA(tIdx.child(42, 0), "42"); COMPARE_DATA(tIdx.child(119, 0), "119"); 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(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->rowCount(), 4); QModelIndex tIdx = stackModel->index(0,0); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), fileName+":39"); 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(); WAIT_FOR_STATE(session, DebugSession::StoppedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); session->stopDebugger(); 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); int structIndex = 0; for(int j=0; j<3; ++j) { if (variableCollection()->index(j, 0, i).data().toString() == "ts") { structIndex = j; } } COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); 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"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); 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"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); 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); QStringList locs; for (int j = 0; j < variableCollection()->rowCount(i); ++j) { locs << variableCollection()->index(j, 0, i).data().toString(); } QVERIFY(locs.contains("argc")); QVERIFY(locs.contains("argv")); QVERIFY(locs.contains("x")); breakpoints()->removeRow(0); session->run(); 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); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //start second debug session (same cfg) 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); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); 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/gdb/unittests/test_gdb.h b/debuggers/lldb/unittests/test_lldb.h similarity index 52% copy from debuggers/gdb/unittests/test_gdb.h copy to debuggers/lldb/unittests/test_lldb.h index 56aeae167d..9befe0c371 100644 --- a/debuggers/gdb/unittests/test_gdb.h +++ b/debuggers/lldb/unittests/test_lldb.h @@ -1,111 +1,135 @@ /* - 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(); void testPendingBreakpoint(); void testUpdateBreakpoint(); void testIgnoreHitsBreakpoint(); void testConditionBreakpoint(); void testBreakOnWriteBreakpoint(); void testBreakOnWriteWithConditionBreakpoint(); void testBreakOnReadBreakpoint(); void testBreakOnReadBreakpoint2(); void testBreakOnAccessBreakpoint(); void testInsertBreakpointWhileRunning(); 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(); void testVariablesWatchesQuotes(); void testVariablesWatchesTwoSessions(); void testVariablesStopDebugger(); 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/lldb/unittests/testhelper.cpp b/debuggers/lldb/unittests/testhelper.cpp new file mode 100644 index 0000000000..8fe3463153 --- /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/unittests/testhelper.h b/debuggers/lldb/unittests/testhelper.h new file mode 100644 index 0000000000..1202758abd --- /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/widgets/lldbconfigpage.cpp b/debuggers/lldb/widgets/lldbconfigpage.cpp new file mode 100644 index 0000000000..6e628c1d6b --- /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.h b/debuggers/lldb/widgets/lldbconfigpage.h new file mode 100644 index 0000000000..61cb72ba1d --- /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.ui b/debuggers/lldb/widgets/lldbconfigpage.ui new file mode 100644 index 0000000000..1d55a6ab5f --- /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
+
+
+ + +