diff --git a/debuggers/common/CMakeLists.txt b/debuggers/common/CMakeLists.txt index d1584cbafe..cc4bf3c49e 100644 --- a/debuggers/common/CMakeLists.txt +++ b/debuggers/common/CMakeLists.txt @@ -1,14 +1,15 @@ set(debuggercommon_SRCS mi/mi.cpp mi/milexer.cpp mi/miparser.cpp + mi/micommand.cpp ) #ki18n_wrap_ui(debuggercommon_SRCS something.ui) add_library(kdevdebuggercommon STATIC ${debuggercommon_SRCS}) target_link_libraries(kdevdebuggercommon PRIVATE Qt5::Core ) kde_target_enable_exceptions(kdevdebuggercommon PUBLIC) diff --git a/debuggers/gdb/gdbcommand.cpp b/debuggers/common/mi/micommand.cpp similarity index 90% rename from debuggers/gdb/gdbcommand.cpp rename to debuggers/common/mi/micommand.cpp index 231da1b3b0..f673c0625e 100644 --- a/debuggers/gdb/gdbcommand.cpp +++ b/debuggers/common/mi/micommand.cpp @@ -1,615 +1,621 @@ /*************************************************************************** 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 "gdbcommand.h" +#include "micommand.h" using namespace MI; -namespace GDBDebugger -{ - 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); } -GDBCommand::GDBCommand(CommandType type, const QString& command, CommandFlags flags) -: type_(type), flags_(flags & ~CmdHandlesError), command_(command), commandHandler_(0), - stateReloading_(false), m_thread(-1), m_frame(-1) +MICommand::MICommand(CommandType type, const QString& command, CommandFlags flags) + : type_(type) + , flags_(flags & ~CmdHandlesError) + , command_(command) + , commandHandler_(0) + , stateReloading_(false) + , m_thread(-1) + , m_frame(-1) { } -GDBCommand::GDBCommand(CommandType type, const QString& arguments, GDBCommandHandler* handler, +MICommand::MICommand(CommandType type, const QString& arguments, MICommandHandler* handler, CommandFlags flags) -: type_(type), flags_(flags), command_(arguments), commandHandler_(handler), - stateReloading_(false), m_thread(-1), m_frame(-1) + : type_(type) + , flags_(flags) + , command_(arguments) + , commandHandler_(handler) + , stateReloading_(false) + , m_thread(-1) + , m_frame(-1) { } -GDBCommand::GDBCommand(CommandType type, const QString& arguments, +MICommand::MICommand(CommandType type, const QString& arguments, const FunctionCommandHandler::Function& callback, CommandFlags flags) : type_(type) , flags_(flags & ~CmdHandlesError) , command_(arguments) , commandHandler_(new FunctionCommandHandler(callback, flags)) , stateReloading_(false) , m_thread(-1) , m_frame(-1) { } -GDBCommand::~GDBCommand() +MICommand::~MICommand() { if (commandHandler_ && commandHandler_->autoDelete()) { delete commandHandler_; } commandHandler_ = nullptr; } -QString GDBCommand::cmdToSend() +QString MICommand::cmdToSend() { return initialString() + '\n'; } -QString GDBCommand::initialString() const +QString MICommand::initialString() const { QString result = QString::number(token()); if (type() == NonMI) { result += command_; } else { - result += gdbCommand(); + 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 GDBCommand::isUserCommand() const +bool MICommand::isUserCommand() const { return false; } -void GDBCommand::setHandler(GDBCommandHandler* handler) +void MICommand::setHandler(MICommandHandler* handler) { if (commandHandler_ && commandHandler_->autoDelete()) delete commandHandler_; commandHandler_ = handler; } -bool -GDBCommand::invokeHandler(const ResultRecord& r) +bool MICommand::invokeHandler(const ResultRecord& r) { if (commandHandler_) { - bool autoDelete = commandHandler_->autoDelete(); //ask before calling handler as it might deleted itself in handler + //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 GDBCommand::newOutput(const QString& line) +void MICommand::newOutput(const QString& line) { lines.push_back(line); } -const QStringList& GDBCommand::allStreamOutput() const +const QStringList& MICommand::allStreamOutput() const { return lines; } -bool GDBCommand::handlesError() const +bool MICommand::handlesError() const { return commandHandler_ ? commandHandler_->handlesError() : false; } UserCommand::UserCommand(CommandType type, const QString& s) -: GDBCommand(type, s, CmdMaybeStartsRunning) + : MICommand(type, s, CmdMaybeStartsRunning) { } bool UserCommand::isUserCommand() const { return true; } -QString GDBCommand::gdbCommand() const +QString MICommand::miCommand() const { QString command; switch (type()) { case NonMI: command = ""; break; case BreakAfter: command = "break-after";//"ignore" break; case BreakCatch: 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 GDBCommand::type() const +CommandType MICommand::type() const { return type_; } -int GDBCommand::thread() const +int MICommand::thread() const { return m_thread; } -void GDBCommand::setThread(int thread) +void MICommand::setThread(int thread) { m_thread = thread; } -int GDBCommand::frame() const +int MICommand::frame() const { return m_frame; } -void GDBCommand::setFrame(int frame) +void MICommand::setFrame(int frame) { m_frame = frame; } -QString GDBCommand::command() const +QString MICommand::command() const { return command_; } -void GDBCommand::setStateReloading(bool f) +void MICommand::setStateReloading(bool f) { stateReloading_ = f; } -bool GDBCommand::stateReloading() const +bool MICommand::stateReloading() const { return stateReloading_; } - -} diff --git a/debuggers/gdb/gdbcommand.h b/debuggers/common/mi/micommand.h similarity index 77% rename from debuggers/gdb/gdbcommand.h rename to debuggers/common/mi/micommand.h index 36c09d4c74..1471d61bfd 100644 --- a/debuggers/gdb/gdbcommand.h +++ b/debuggers/common/mi/micommand.h @@ -1,352 +1,352 @@ /*************************************************************************** begin : Sun Aug 8 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org + copyright : (C) 2016 by Aetf + email : aetf@unlimitedcodeworks.xyz ***************************************************************************/ /*************************************************************************** * * * 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 _GDBCOMMAND_H_ -#define _GDBCOMMAND_H_ +#ifndef _MICOMMAND_H_ +#define _MICOMMAND_H_ -#include +#include "mi/mi.h" #include #include #include -#include "mi/mi.h" +#include -namespace GDBDebugger +namespace MI { - class VarItem; class ValueCallback; enum CommandFlag { /// The command handler also wishes to receive an error responses, overriding the default error handler CmdHandlesError = 1 << 0, /// The command is expected to cause the inferior to run. Controllers that display the /// program's state should refrain from sending commands while a command with this flag /// is currently pending; however, note that a command with this flag needn't be guaranteed /// to lead to a running state. CmdMaybeStartsRunning = 1 << 1, /// The command is a temporary-run type command, meaning that it typically causes the program /// to run, but only for a short time before it stops again (e.g. Step and StepInto-type /// commands). When the program is running due to this type of command, a CmdImmediately /// command will wait before forcing an interrupt of the debugger, and the program is _not_ /// automatically restarted if an interrupt was forced. /// /// TODO: this special handling has not actually been implemented yet CmdTemporaryRun = 1 << 2, /// This command should be executed immediately, even if the program is currently running /// (e.g. breakpoint setting and modification); however, if the program is interrupted, /// it should be resumed after this command has run. CmdImmediately = 1 << 3, /// This is a command that should interrupt a running program, without resuming. CmdInterrupt = 1 << 4, }; Q_DECLARE_FLAGS(CommandFlags, CommandFlag) //base class for handlers -class GDBCommandHandler +class MICommandHandler { public: - virtual ~GDBCommandHandler() {} - virtual void handle(const MI::ResultRecord&) = 0; + virtual ~MICommandHandler() {} + virtual void handle(const ResultRecord&) = 0; virtual bool handlesError() { return false; } /** * If the handler object should be deleted after the handle() call. */ virtual bool autoDelete() { return true; } }; -class FunctionCommandHandler : public GDBCommandHandler { +class FunctionCommandHandler : public MICommandHandler { public: - typedef std::function Function; + typedef std::function Function; FunctionCommandHandler(const Function& callback, CommandFlags flags = 0); - virtual void handle(const MI::ResultRecord&) override; + virtual void handle(const ResultRecord&) override; virtual bool handlesError() override; private: CommandFlags _flags; Function _callback; }; /** * @author John Birch */ -class GDBCommand +class MICommand { public: - GDBCommand(MI::CommandType type, const QString& arguments = QString(), CommandFlags flags = 0); + MICommand(CommandType type, const QString& arguments = QString(), CommandFlags flags = 0); template - GDBCommand(MI::CommandType type, const QString& arguments, - Handler* handler_this, - void (Handler::* handler_method)(const MI::ResultRecord&), - CommandFlags flags = 0); + MICommand(CommandType type, const QString& arguments, + Handler* handler_this, + void (Handler::* handler_method)(const ResultRecord&), + CommandFlags flags = 0); - GDBCommand(MI::CommandType type, const QString& arguments, GDBCommandHandler* handler, - CommandFlags flags = 0); + MICommand(CommandType type, const QString& arguments, MICommandHandler* handler, + CommandFlags flags = 0); - GDBCommand( - MI::CommandType type, const QString& arguments, - const FunctionCommandHandler::Function& callback, - CommandFlags flags = 0); + MICommand(CommandType type, const QString& arguments, + const FunctionCommandHandler::Function& callback, + CommandFlags flags = 0); - virtual ~GDBCommand(); + virtual ~MICommand(); - MI::CommandType type() const; - QString gdbCommand() const; + CommandType type() const; + QString miCommand() const; CommandFlags flags() const {return flags_;} /** * Returns the MI token with which the command is sent, allowing the parser to match up * the result message with the command. */ uint32_t token() const {return token_;} /** - * Set the MI token. This is done by \ref GDBCommandQueue. + * Set the MI token. This is done by \ref MICommandQueue. */ void setToken(uint32_t token) {token_ = token;} /** * Returns the thread that needs to be currently selected when this command is executed, * or -1 if there is no requirement. */ int thread() const; /** * Set the thread required to be currently selected when the command is executed. */ void setThread(int thread); /** * Returns the frame that needs to be currently selected when this command is executed, * or -1 if there is no requirement. */ int frame() const; /** * Set the frame required to be currently selected when the command is executed. */ void setFrame(int frame); /** * Sets the handler for results. * * The command object assumes ownership of @p handler. */ - void setHandler(GDBCommandHandler* handler); - - /* The command that should be sent to gdb. + void setHandler(MICommandHandler* handler); + + /* The command that should be sent to debugger. This method is virtual so the command can compute this dynamically, possibly using results of the previous commands. If the empty string is returned, nothing is sent. */ virtual QString cmdToSend(); /* Returns the initial string that was specified in ctor invocation. The actual command will be determined by cmdToSend above and the return value of this method is only used in various diagnostic messages emitted before actually sending the command. */ QString initialString() const; /* Returns true if this is command entered by the user and so should be always shown in the gdb output window. */ virtual bool isUserCommand() const; // If there's a handler for this command, invokes it and returns true. // Otherwise, returns false. - bool invokeHandler(const MI::ResultRecord& r); + bool invokeHandler(const ResultRecord& r); // Returns 'true' if 'invokeHandler' should be invoked even // on MI errors. bool handlesError() const; - // Called by gdbcontroller for each new output string - // gdb emits for this command. In MI mode, this includes + // Called by debuggercontroller for each new output string + // debugger emits for this command. In MI mode, this includes // all "stream" messages, but does not include MI responses. void newOutput(const QString&); const QStringList& allStreamOutput() const; QString command() const; void setStateReloading(bool f); bool stateReloading() const; private: - MI::CommandType type_; + CommandType type_; CommandFlags flags_; uint32_t token_ = 0; QString command_; - GDBCommandHandler *commandHandler_; + MICommandHandler *commandHandler_; QStringList lines; bool stateReloading_; private: int m_thread; int m_frame; }; -class UserCommand : public GDBCommand +class UserCommand : public MICommand { public: - UserCommand(MI::CommandType type, const QString& s); + UserCommand(CommandType type, const QString& s); bool isUserCommand() const override; }; /** This is a class for raw CLI commands. Instead of invoking user provided hook with MI response, it invokes the a hook with lists of strings. */ -class CliCommand : public GDBCommand +class CliCommand : public MICommand { public: template - CliCommand(MI::CommandType type, const QString& command, + CliCommand(CommandType type, const QString& command, Handler* handler_this, void (Handler::* handler_method)(const QStringList&), CommandFlags flags = 0); }; /** Command that does nothing and can be just used to invoke a user provided handler when all preceeding commands are executed. */ -class SentinelCommand : public GDBCommand +class SentinelCommand : public MICommand { public: typedef std::function Function; template SentinelCommand(Handler* handler_this, void (Handler::* handler_method)(), CommandFlags flags = 0) - : GDBCommand(MI::NonMI, QString(), flags) + : MICommand(NonMI, QString(), flags) { QPointer guarded_this(handler_this); handler = [guarded_this, handler_method]() { if (guarded_this) { (guarded_this.data()->*handler_method)(); } }; } SentinelCommand(const Function& handler, CommandFlags flags = 0) - : GDBCommand(MI::NonMI, QString(), flags) + : MICommand(NonMI, QString(), flags) , handler(handler) { } - using GDBCommand::invokeHandler; + using MICommand::invokeHandler; void invokeHandler() { handler(); } QString cmdToSend() override { return ""; } private: Function handler; }; -class ExpressionValueCommand : public QObject, public GDBCommand +class ExpressionValueCommand : public QObject, public MICommand { public: typedef void (QObject::*handler_method_t)(const QString&); template ExpressionValueCommand( const QString& expression, Handler* handler_this, void (Handler::* handler_method)(const QString&)) - : GDBCommand(MI::DataEvaluateExpression, expression, this, + : MICommand(DataEvaluateExpression, expression, this, &ExpressionValueCommand::handleResponse), handler_this(handler_this), handler_method(static_cast(handler_method)) {} - void handleResponse(const MI::ResultRecord& r) + void handleResponse(const ResultRecord& r) { (handler_this.data()->*handler_method)(r["value"].literal()); } private: QPointer handler_this; handler_method_t handler_method; }; template -GDBCommand::GDBCommand( - MI::CommandType type, +MICommand::MICommand( + CommandType type, const QString& command, Handler* handler_this, - void (Handler::* handler_method)(const MI::ResultRecord&), + void (Handler::* handler_method)(const ResultRecord&), CommandFlags flags) : type_(type), flags_(flags & ~CmdHandlesError), command_(command), commandHandler_(nullptr), stateReloading_(false), m_thread(-1), m_frame(-1) { QPointer guarded_this(handler_this); - setHandler(new FunctionCommandHandler([guarded_this, handler_method](const MI::ResultRecord& r) { + setHandler(new FunctionCommandHandler([guarded_this, handler_method](const ResultRecord& r) { if (guarded_this) { (guarded_this.data()->*handler_method)(r); } }, flags)); } template CliCommand::CliCommand( - MI::CommandType type, + CommandType type, const QString& command, Handler* handler_this, void (Handler::* handler_method)(const QStringList&), CommandFlags flags) -: GDBCommand(type, command) +: MICommand(type, command) { QPointer guarded_this(handler_this); - setHandler(new FunctionCommandHandler([this, guarded_this, handler_method](const MI::ResultRecord&) { + setHandler(new FunctionCommandHandler([this, guarded_this, handler_method](const ResultRecord&) { if (guarded_this) { (guarded_this.data()->*handler_method)(this->allStreamOutput()); } }, flags)); } -} +} // end of namespace MI -Q_DECLARE_OPERATORS_FOR_FLAGS(GDBDebugger::CommandFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(MI::CommandFlags) #endif diff --git a/debuggers/gdb/CMakeLists.txt b/debuggers/gdb/CMakeLists.txt index 76d077c2db..4abbac4c14 100644 --- a/debuggers/gdb/CMakeLists.txt +++ b/debuggers/gdb/CMakeLists.txt @@ -1,128 +1,127 @@ 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() find_package(KF5SysGuard) if(KF5SysGuard_FOUND) add_definitions( -DKDEV_ENABLE_GDB_ATTACH_DIALOG ) endif() 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 gdbcommandqueue.cpp - gdbcommand.cpp stty.cpp disassemblewidget.cpp gdboutputwidget.cpp # debuggertracingdialog.cpp breakpointcontroller.cpp stringhelpers.cpp debugsession.cpp gdbconfigpage.cpp debugjob.cpp selectcoredialog.cpp variablecontroller.cpp gdbframestackmodel.cpp gdbvariable.cpp registers/registersview.cpp registers/registercontroller.cpp registers/registersmanager.cpp registers/registercontroller_x86.cpp registers/registercontroller_arm.cpp registers/modelsmanager.cpp registers/converters.cpp ) if(OKTETA_FOUND) add_definitions(-DWITH_OKTETA=1) list(APPEND kdevgdb_SRCS memviewdlg.cpp) endif() if(KF5SysGuard_FOUND) set(kdevgdb_SRCS ${kdevgdb_SRCS} processselection.cpp ) endif() set(kdevgdb_UI debuggertracingdialog.ui gdbconfigpage.ui selectcoredialog.ui selectaddressdialog.ui registers/registersview.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 ) if(KF5SysGuard_FOUND) target_link_libraries(kdevgdb KF5::ProcessUi) endif() 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(KF5SysGuard_FOUND) target_link_libraries(test_gdb KF5::ProcessUi) endif() 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/breakpointcontroller.cpp b/debuggers/gdb/breakpointcontroller.cpp index 11d3f96692..fe0c518f8e 100644 --- a/debuggers/gdb/breakpointcontroller.cpp +++ b/debuggers/gdb/breakpointcontroller.cpp @@ -1,786 +1,786 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh Copyright (C) 2006, 2008 Vladimir Prus Copyright (C) 2007 Hamish Rodda Copyright (C) 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 as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "breakpointcontroller.h" -#include +#include "debug.h" +#include "debugsession.h" +#include "mi/micommand.h" +#include +#include #include #include -#include -#include -#include "gdbcommand.h" -#include "debugsession.h" -#include "debug.h" +#include using namespace MI; namespace GDBDebugger { QString quoteExpression(QString expr) { expr.replace('"', "\\\""); expr = expr.prepend('"').append('"'); return expr; } QString unquoteExpression(QString expr) { if (expr.left(1) == QString('"') && expr.right(1) == QString('"')) { expr = expr.mid(1, expr.length()-2); expr.replace("\\\"", "\""); } return expr; } struct BreakpointData { int gdbId; BreakpointModel::ColumnFlags dirty; BreakpointModel::ColumnFlags sent; BreakpointModel::ColumnFlags errors; bool pending; BreakpointData() : gdbId(-1) , pending(false) {} }; -struct BreakpointController::Handler : public GDBCommandHandler +struct BreakpointController::Handler : public MICommandHandler { Handler(BreakpointController* controller, const BreakpointDataPtr& b, BreakpointModel::ColumnFlags columns) : controller(controller) , breakpoint(b) , columns(columns) { breakpoint->sent |= columns; breakpoint->dirty &= ~columns; } void handle(const ResultRecord& r) override { breakpoint->sent &= ~columns; if (r.reason == "error") { breakpoint->errors |= columns; int row = controller->breakpointRow(breakpoint); if (row >= 0) { controller->updateErrorText(row, r["msg"].literal()); qWarning() << r["msg"].literal(); } } else { if (breakpoint->errors & columns) { breakpoint->errors &= ~columns; if (breakpoint->errors) { // Since at least one error column cleared, it's possible that any remaining // error bits were collateral damage; try resending the corresponding columns // to see whether errors remain. breakpoint->dirty |= (breakpoint->errors & ~breakpoint->sent); } } } } bool handlesError() override { return true; } BreakpointController* controller; BreakpointDataPtr breakpoint; BreakpointModel::ColumnFlags columns; }; struct BreakpointController::UpdateHandler : public BreakpointController::Handler { UpdateHandler(BreakpointController* c, const BreakpointDataPtr& b, BreakpointModel::ColumnFlags columns) : Handler(c, b, columns) {} void handle(const ResultRecord &r) override { Handler::handle(r); int row = controller->breakpointRow(breakpoint); if (row >= 0) { // Note: send further updates even if we got an error; who knows: perhaps // these additional updates will "unstuck" the error condition. if (breakpoint->sent == 0 && breakpoint->dirty != 0) { controller->sendUpdates(row); } controller->recalculateState(row); } } }; struct BreakpointController::InsertedHandler : public BreakpointController::Handler { InsertedHandler(BreakpointController* c, const BreakpointDataPtr& b, BreakpointModel::ColumnFlags columns) : Handler(c, b, columns) {} void handle(const ResultRecord &r) override { Handler::handle(r); int row = controller->breakpointRow(breakpoint); if (r.reason != "error") { QString bkptKind; for (auto kind : {"bkpt", "wpt", "hw-rwpt", "hw-awpt"}) { if (r.hasField(kind)) { bkptKind = kind; break; } } if (bkptKind.isEmpty()) { qWarning() << "Gdb sent unknown breakpoint kind"; return; } const Value& miBkpt = r[bkptKind]; breakpoint->gdbId = miBkpt["number"].toInt(); if (row >= 0) { controller->updateFromGdb(row, miBkpt); if (breakpoint->dirty != 0) controller->sendUpdates(row); } else { // breakpoint was deleted while insertion was in flight controller->debugSession()->addCommand( - new GDBCommand(BreakDelete, QString::number(breakpoint->gdbId), + new MICommand(BreakDelete, QString::number(breakpoint->gdbId), CmdImmediately)); } } if (row >= 0) { controller->recalculateState(row); } } }; struct BreakpointController::DeleteHandler : BreakpointController::Handler { DeleteHandler(BreakpointController* c, const BreakpointDataPtr& b) : Handler(c, b, 0) {} void handle(const ResultRecord&) override { controller->m_pendingDeleted.removeAll(breakpoint); } }; struct BreakpointController::IgnoreChanges { IgnoreChanges(BreakpointController& controller) : controller(controller) { ++controller.m_ignoreChanges; } ~IgnoreChanges() { --controller.m_ignoreChanges; } BreakpointController& controller; }; BreakpointController::BreakpointController(DebugSession* parent) : IBreakpointController(parent) { Q_ASSERT(parent); connect(parent, &DebugSession::programStopped, this, &BreakpointController::programStopped); int numBreakpoints = breakpointModel()->breakpoints().size(); for (int row = 0; row < numBreakpoints; ++row) breakpointAdded(row); } DebugSession *BreakpointController::debugSession() const { Q_ASSERT(QObject::parent()); return static_cast(const_cast(QObject::parent())); } int BreakpointController::breakpointRow(const BreakpointDataPtr& breakpoint) { return m_breakpoints.indexOf(breakpoint); } void BreakpointController::setDeleteDuplicateBreakpoints(bool enable) { m_deleteDuplicateBreakpoints = enable; } void BreakpointController::initSendBreakpoints() { for (int row = 0; row < m_breakpoints.size(); ++row) { BreakpointDataPtr breakpoint = m_breakpoints[row]; if (breakpoint->gdbId < 0 && breakpoint->sent == 0) { createGdbBreakpoint(row); } } } void BreakpointController::breakpointAdded(int row) { if (m_ignoreChanges > 0) return; auto breakpoint = BreakpointDataPtr::create(); m_breakpoints.insert(row, breakpoint); const Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); if (!modelBreakpoint->enabled()) breakpoint->dirty |= BreakpointModel::EnableColumnFlag; if (!modelBreakpoint->condition().isEmpty()) breakpoint->dirty |= BreakpointModel::ConditionColumnFlag; if (modelBreakpoint->ignoreHits() != 0) breakpoint->dirty |= BreakpointModel::IgnoreHitsColumnFlag; if (!modelBreakpoint->address().isEmpty()) breakpoint->dirty |= BreakpointModel::LocationColumnFlag; createGdbBreakpoint(row); } void BreakpointController::breakpointModelChanged(int row, BreakpointModel::ColumnFlags columns) { if (m_ignoreChanges > 0) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); breakpoint->dirty |= columns & (BreakpointModel::EnableColumnFlag | BreakpointModel::LocationColumnFlag | BreakpointModel::ConditionColumnFlag | BreakpointModel::IgnoreHitsColumnFlag); if (breakpoint->sent != 0) { // Throttle the amount of commands we send to GDB; the response handler of currently // in-flight commands will take care of sending the update. // This also prevents us from sending updates while a break-create command is in-flight. return; } if (breakpoint->gdbId < 0) { createGdbBreakpoint(row); } else { sendUpdates(row); } } void BreakpointController::breakpointAboutToBeDeleted(int row) { if (m_ignoreChanges > 0) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); m_breakpoints.removeAt(row); if (breakpoint->gdbId < 0) { // Two possibilities: // (1) Breakpoint has never been sent to GDB, so we're done // (2) Breakpoint has been sent to GDB, but we haven't received // the response yet; the response handler will delete the // breakpoint. return; } if (debugSession()->stateIsOn(s_dbgNotStarted)) return; debugSession()->addCommand( - new GDBCommand( + new MICommand( BreakDelete, QString::number(breakpoint->gdbId), new DeleteHandler(this, breakpoint), CmdImmediately)); m_pendingDeleted << breakpoint; } void BreakpointController::debuggerStateChanged(IDebugSession::DebuggerState state) { IgnoreChanges ignoreChanges(*this); if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) { for (int row = 0; row < m_breakpoints.size(); ++row) { updateState(row, Breakpoint::NotStartedState); } } else if (state == IDebugSession::StartingState) { for (int row = 0; row < m_breakpoints.size(); ++row) { updateState(row, Breakpoint::DirtyState); } } } void BreakpointController::createGdbBreakpoint(int row) { if (debugSession()->stateIsOn(s_dbgNotStarted)) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); Q_ASSERT(breakpoint->gdbId < 0 && breakpoint->sent == 0); if (modelBreakpoint->location().isEmpty()) return; if (modelBreakpoint->kind() == Breakpoint::CodeBreakpoint) { QString location; if (modelBreakpoint->line() != -1) { location = QString("%0:%1") .arg(modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash)) .arg(modelBreakpoint->line() + 1); } else { location = modelBreakpoint->location(); } if (location == "catch throw") { location = "exception throw"; } - // Note: We rely on '-f' to be automatically added by the GDBCommand logic + // Note: We rely on '-f' to be automatically added by the MICommand logic QString arguments; if (!modelBreakpoint->enabled()) arguments += "-d "; if (!modelBreakpoint->condition().isEmpty()) arguments += QString("-c %0 ").arg(quoteExpression(modelBreakpoint->condition())); if (modelBreakpoint->ignoreHits() != 0) arguments += QString("-i %0 ").arg(modelBreakpoint->ignoreHits()); arguments += quoteExpression(location); BreakpointModel::ColumnFlags sent = BreakpointModel::EnableColumnFlag | BreakpointModel::ConditionColumnFlag | BreakpointModel::IgnoreHitsColumnFlag | BreakpointModel::LocationColumnFlag; debugSession()->addCommand( - new GDBCommand(BreakInsert, arguments, + new MICommand(BreakInsert, arguments, new InsertedHandler(this, breakpoint, sent), CmdImmediately)); } else { QString opt; if (modelBreakpoint->kind() == Breakpoint::ReadBreakpoint) opt = "-r "; else if (modelBreakpoint->kind() == Breakpoint::AccessBreakpoint) opt = "-a "; debugSession()->addCommand( - new GDBCommand( + new MICommand( BreakWatch, opt + quoteExpression(modelBreakpoint->location()), new InsertedHandler(this, breakpoint, BreakpointModel::LocationColumnFlag), CmdImmediately)); } recalculateState(row); } void BreakpointController::sendUpdates(int row) { if (debugSession()->stateIsOn(s_dbgNotStarted)) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); Q_ASSERT(breakpoint->gdbId >= 0 && breakpoint->sent == 0); if (breakpoint->dirty & BreakpointModel::LocationColumnFlag) { // Gdb considers locations as fixed, so delete and re-create the breakpoint debugSession()->addCommand( - new GDBCommand(BreakDelete, QString::number(breakpoint->gdbId), CmdImmediately)); + new MICommand(BreakDelete, QString::number(breakpoint->gdbId), CmdImmediately)); breakpoint->gdbId = -1; createGdbBreakpoint(row); return; } if (breakpoint->dirty & BreakpointModel::EnableColumnFlag) { debugSession()->addCommand( - new GDBCommand(modelBreakpoint->enabled() ? BreakEnable : BreakDisable, + new MICommand(modelBreakpoint->enabled() ? BreakEnable : BreakDisable, QString::number(breakpoint->gdbId), new UpdateHandler(this, breakpoint, BreakpointModel::EnableColumnFlag), CmdImmediately)); } if (breakpoint->dirty & BreakpointModel::IgnoreHitsColumnFlag) { debugSession()->addCommand( - new GDBCommand(BreakAfter, + new MICommand(BreakAfter, QString("%0 %1").arg(breakpoint->gdbId).arg(modelBreakpoint->ignoreHits()), new UpdateHandler(this, breakpoint, BreakpointModel::IgnoreHitsColumnFlag), CmdImmediately)); } if (breakpoint->dirty & BreakpointModel::ConditionColumnFlag) { debugSession()->addCommand( - new GDBCommand(BreakCondition, + new MICommand(BreakCondition, QString("%0 %1").arg(breakpoint->gdbId).arg(modelBreakpoint->condition()), new UpdateHandler(this, breakpoint, BreakpointModel::ConditionColumnFlag), CmdImmediately)); } recalculateState(row); } void BreakpointController::recalculateState(int row) { BreakpointDataPtr breakpoint = m_breakpoints.at(row); if (breakpoint->errors == 0) updateErrorText(row, QString()); Breakpoint::BreakpointState newState = Breakpoint::NotStartedState; if (debugSession()->state() != IDebugSession::EndedState && debugSession()->state() != IDebugSession::NotStartedState) { if (!debugSession()->stateIsOn(s_dbgNotStarted)) { if (breakpoint->dirty == 0 && breakpoint->sent == 0) { if (breakpoint->pending) { newState = Breakpoint::PendingState; } else { newState = Breakpoint::CleanState; } } else { newState = Breakpoint::DirtyState; } } } updateState(row, newState); } int BreakpointController::rowFromGdbId(int gdbId) const { for (int row = 0; row < m_breakpoints.size(); ++row) { if (gdbId == m_breakpoints[row]->gdbId) return row; } return -1; } void BreakpointController::notifyBreakpointCreated(const AsyncRecord& r) { const Value& miBkpt = r["bkpt"]; // Breakpoints with multiple locations are represented by a parent breakpoint (e.g. 1) // and multiple child breakpoints (e.g. 1.1, 1.2, 1.3, ...). // We ignore the child breakpoints here in the current implementation; this can lead to dubious // results in the UI when breakpoints are marked in document views (e.g. when a breakpoint // applies to multiple overloads of a C++ function simultaneously) and in disassembly // (e.g. when a breakpoint is set in an inlined functions). if (miBkpt["number"].literal().contains('.')) return; createFromGdb(miBkpt); } void BreakpointController::notifyBreakpointModified(const AsyncRecord& r) { const Value& miBkpt = r["bkpt"]; const int gdbId = miBkpt["number"].toInt(); const int row = rowFromGdbId(gdbId); if (row < 0) { for (const auto& breakpoint : m_pendingDeleted) { if (breakpoint->gdbId == gdbId) { // Received a modification of a breakpoint whose deletion is currently // in-flight; simply ignore it. return; } } qWarning() << "Received a modification of an unknown breakpoint"; createFromGdb(miBkpt); } else { updateFromGdb(row, miBkpt); } } void BreakpointController::notifyBreakpointDeleted(const AsyncRecord& r) { const int gdbId = r["id"].toInt(); const int row = rowFromGdbId(gdbId); if (row < 0) { // The user may also have deleted the breakpoint via the UI simultaneously return; } IgnoreChanges ignoreChanges(*this); breakpointModel()->removeRow(row); m_breakpoints.removeAt(row); } void BreakpointController::createFromGdb(const Value& miBkpt) { const QString type = miBkpt["type"].literal(); Breakpoint::BreakpointKind gdbKind; if (type == "breakpoint") { gdbKind = Breakpoint::CodeBreakpoint; } else if (type == "watchpoint" || type == "hw watchpoint") { gdbKind = Breakpoint::WriteBreakpoint; } else if (type == "read watchpoint") { gdbKind = Breakpoint::ReadBreakpoint; } else if (type == "acc watchpoint") { gdbKind = Breakpoint::AccessBreakpoint; } else { qWarning() << "Unknown gdb breakpoint type " << type; return; } // During gdb startup, we want to avoid creating duplicate breakpoints when the same breakpoint // appears both in our model and in a .gdbinit file BreakpointModel* model = breakpointModel(); const int numRows = model->rowCount(); for (int row = 0; row < numRows; ++row) { BreakpointDataPtr breakpoint = m_breakpoints.at(row); const bool breakpointSent = breakpoint->gdbId >= 0 || breakpoint->sent != 0; if (breakpointSent && !m_deleteDuplicateBreakpoints) continue; Breakpoint* modelBreakpoint = model->breakpoint(row); if (modelBreakpoint->kind() != gdbKind) continue; if (gdbKind == Breakpoint::CodeBreakpoint) { bool sameLocation = false; if (miBkpt.hasField("fullname") && miBkpt.hasField("line")) { const QString location = unquoteExpression(miBkpt["fullname"].literal()); const int line = miBkpt["line"].toInt() - 1; if (location == modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash) && line == modelBreakpoint->line()) { sameLocation = true; } } if (!sameLocation && miBkpt.hasField("original-location")) { const QString location = miBkpt["original-location"].literal(); if (location == modelBreakpoint->location()) { sameLocation = true; } else { QRegExp rx("^(.+):(\\d+)$"); if (rx.indexIn(location) != -1 && unquoteExpression(rx.cap(1)) == modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash) && rx.cap(2).toInt() - 1 == modelBreakpoint->line()) { sameLocation = true; } } } if (!sameLocation && miBkpt.hasField("what") && miBkpt["what"].literal() == "exception throw") { if (modelBreakpoint->expression() == "catch throw" || modelBreakpoint->expression() == "exception throw") { sameLocation = true; } } if (!sameLocation) continue; } else { if (unquoteExpression(miBkpt["original-location"].literal()) != modelBreakpoint->expression()) { continue; } } QString condition; if (miBkpt.hasField("cond")) { condition = miBkpt["cond"].literal(); } if (condition != modelBreakpoint->condition()) continue; // Breakpoint is equivalent if (!breakpointSent) { breakpoint->gdbId = miBkpt["number"].toInt(); // Reasonable people can probably have different opinions about what the "correct" behavior // should be for the "enabled" and "ignore hits" column. // Here, we let the status in KDevelop's UI take precedence, which we suspect to be // marginally more useful. Dirty data will be sent during the initial sending of the // breakpoint list. const bool gdbEnabled = miBkpt["enabled"].literal() == "y"; if (gdbEnabled != modelBreakpoint->enabled()) breakpoint->dirty |= BreakpointModel::EnableColumnFlag; int gdbIgnoreHits = 0; if (miBkpt.hasField("ignore")) gdbIgnoreHits = miBkpt["ignore"].toInt(); if (gdbIgnoreHits != modelBreakpoint->ignoreHits()) breakpoint->dirty |= BreakpointModel::IgnoreHitsColumnFlag; updateFromGdb(row, miBkpt, BreakpointModel::EnableColumnFlag | BreakpointModel::IgnoreHitsColumnFlag); return; } // Breakpoint from the model has already been sent, but we want to delete duplicates // It is not entirely clear _which_ breakpoint ought to be deleted, and reasonable people // may have different opinions. // We suspect that it is marginally more useful to delete the existing model breakpoint; // after all, this only happens when a user command creates a breakpoint, and perhaps the // user intends to modify the breakpoint they created manually. In any case, // this situation should only happen rarely (in particular, when a user sets a breakpoint // inside the remote run script). model->removeRows(row, 1); break; // fall through to pick up the manually created breakpoint in the model } // No equivalent breakpoint found, or we have one but want to be consistent with GDB's // behavior of allowing multiple equivalent breakpoint. IgnoreChanges ignoreChanges(*this); const int row = m_breakpoints.size(); Q_ASSERT(row == model->rowCount()); switch (gdbKind) { case Breakpoint::WriteBreakpoint: model->addWatchpoint(); break; case Breakpoint::ReadBreakpoint: model->addReadWatchpoint(); break; case Breakpoint::AccessBreakpoint: model->addAccessWatchpoint(); break; case Breakpoint::CodeBreakpoint: model->addCodeBreakpoint(); break; default: Q_ASSERT(false); return; } // Since we are in ignore-changes mode, we have to add the BreakpointData manually. auto breakpoint = BreakpointDataPtr::create(); m_breakpoints << breakpoint; breakpoint->gdbId = miBkpt["number"].toInt(); updateFromGdb(row, miBkpt); } // This method is required for the legacy interface which will be removed void BreakpointController::sendMaybe(KDevelop::Breakpoint*) { Q_ASSERT(false); } void BreakpointController::updateFromGdb(int row, const Value& miBkpt, BreakpointModel::ColumnFlags lockedColumns) { IgnoreChanges ignoreChanges(*this); BreakpointDataPtr breakpoint = m_breakpoints[row]; Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); // Commands that are currently in flight will overwrite the modification we have received, // so do not update the corresponding data lockedColumns |= breakpoint->sent | breakpoint->dirty; // TODO: // Gdb has a notion of "original-location", which is the "expression" or "location" used // to set the breakpoint, and notions of the actual location of the breakpoint (function name, // address, source file and line). The breakpoint model currently does not map well to this // (though it arguably should), and does not support multi-location breakpoints at all. // We try to do the best we can until the breakpoint model gets cleaned up. if (miBkpt.hasField("fullname") && miBkpt.hasField("line")) { modelBreakpoint->setLocation( QUrl::fromLocalFile(unquoteExpression(miBkpt["fullname"].literal())), miBkpt["line"].toInt() - 1); } else if (miBkpt.hasField("original-location")) { QRegExp rx("^(.+):(\\d+)$"); QString location = miBkpt["original-location"].literal(); if (rx.indexIn(location) != -1) { modelBreakpoint->setLocation(QUrl::fromLocalFile(unquoteExpression(rx.cap(1))), rx.cap(2).toInt()-1); } else { modelBreakpoint->setData(Breakpoint::LocationColumn, unquoteExpression(location)); } } else if (miBkpt.hasField("what")) { modelBreakpoint->setExpression(miBkpt["what"].literal()); } else { qWarning() << "Breakpoint doesn't contain required location/expression data"; } if (!(lockedColumns & BreakpointModel::EnableColumnFlag)) { bool enabled = true; if (miBkpt.hasField("enabled")) { if (miBkpt["enabled"].literal() == "n") enabled = false; } modelBreakpoint->setData(Breakpoint::EnableColumn, enabled ? Qt::Checked : Qt::Unchecked); breakpoint->dirty &= ~BreakpointModel::EnableColumnFlag; } if (!(lockedColumns & BreakpointModel::ConditionColumnFlag)) { QString condition; if (miBkpt.hasField("cond")) { condition = miBkpt["cond"].literal(); } modelBreakpoint->setCondition(condition); breakpoint->dirty &= ~BreakpointModel::ConditionColumnFlag; } if (!(lockedColumns & BreakpointModel::IgnoreHitsColumnFlag)) { int ignoreHits = 0; if (miBkpt.hasField("ignore")) { ignoreHits = miBkpt["ignore"].toInt(); } modelBreakpoint->setIgnoreHits(ignoreHits); breakpoint->dirty &= ~BreakpointModel::IgnoreHitsColumnFlag; } breakpoint->pending = false; if (miBkpt.hasField("addr") && miBkpt["addr"].literal() == "") { breakpoint->pending = true; } int hitCount = 0; if (miBkpt.hasField("times")) { hitCount = miBkpt["times"].toInt(); } updateHitCount(row, hitCount); recalculateState(row); } void BreakpointController::programStopped(const AsyncRecord& r) { if (!r.hasField("reason")) return; const QString reason = r["reason"].literal(); int gdbId = -1; if (reason == "breakpoint-hit") { gdbId = r["bkptno"].toInt(); } else if (reason == "watchpoint-trigger") { gdbId = r["wpt"]["number"].toInt(); } else if (reason == "read-watchpoint-trigger") { gdbId = r["hw-rwpt"]["number"].toInt(); } else if (reason == "access-watchpoint-trigger") { gdbId = r["hw-awpt"]["number"].toInt(); } if (gdbId < 0) return; int row = rowFromGdbId(gdbId); if (row < 0) return; QString msg; if (r.hasField("value")) { if (r["value"].hasField("old")) { msg += i18n("
Old value: %1", r["value"]["old"].literal()); } if (r["value"].hasField("new")) { msg += i18n("
New value: %1", r["value"]["new"].literal()); } } notifyHit(row, msg); } } diff --git a/debuggers/gdb/debugsession.cpp b/debuggers/gdb/debugsession.cpp index 62504d1b02..7a56157f0b 100644 --- a/debuggers/gdb/debugsession.cpp +++ b/debuggers/gdb/debugsession.cpp @@ -1,1520 +1,1516 @@ /* * 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 #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 "breakpointcontroller.h" #include "variablecontroller.h" #include "gdb.h" #include "gdbcommandqueue.h" #include "stty.h" #include "gdbframestackmodel.h" #include "debug.h" using namespace KDevelop; - -namespace GDBDebugger { +using namespace MI; +using namespace GDBDebugger; DebugSession::DebugSession() : m_breakpointController(nullptr) , m_variableController(nullptr) , m_frameStackModel(nullptr) , m_sessionState(NotStartedState) , m_config(KSharedConfig::openConfig(), "GDB Debugger") , m_testing(false) , commandQueue_(new CommandQueue) , m_tty(0) , state_(s_dbgNotStarted | s_appNotStarted) , state_reload_needed(false) , stateReloadInProgress_(false) , m_hasCrashed(false) { configure(); m_breakpointController = new BreakpointController(this); m_variableController = new VariableController(this); m_frameStackModel = new GdbFrameStackModel(this); m_procLineMaker = new KDevelop::ProcessLineMaker(this); connect(m_procLineMaker, &ProcessLineMaker::receivedStdoutLines, this, &DebugSession::applicationStandardOutputLines); connect(m_procLineMaker, &ProcessLineMaker::receivedStderrLines, this, &DebugSession::applicationStandardErrorLines); setupController(); } // 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. DebugSession::~DebugSession() { qCDebug(DEBUGGERGDB); if (!stateIsOn(s_dbgNotStarted)) { stopDebugger(); // This currently isn't working, so comment out until it can be resolved - at the moment it just causes a delay on stopping kdevelop //m_process->waitForFinished(); } delete commandQueue_; } void DebugSession::setTesting(bool testing) { m_testing = testing; } KDevelop::IDebugSession::DebuggerState DebugSession::state() const { return m_sessionState; } BreakpointController* DebugSession::breakpointController() const { return m_breakpointController; } IVariableController* DebugSession::variableController() const { return m_variableController; } IFrameStackModel* DebugSession::frameStackModel() const { return m_frameStackModel; } #define ENUM_NAME(o,e,v) (o::staticMetaObject.enumerator(o::staticMetaObject.indexOfEnumerator(#e)).valueToKey((v))) void DebugSession::setSessionState(DebuggerState state) { qCDebug(DEBUGGERGDB) << "STATE CHANGED" << this << state << ENUM_NAME(IDebugSession, DebuggerState, state); if (state != m_sessionState) { m_sessionState = state; emit stateChanged(state); } } void DebugSession::setupController() { // controller -> procLineMaker connect(this, &DebugSession::ttyStdout, m_procLineMaker, &ProcessLineMaker::slotReceivedStdout); connect(this, &DebugSession::ttyStderr, m_procLineMaker, &ProcessLineMaker::slotReceivedStderr); // connect(statusBarIndicator, SIGNAL(doubleClicked()), // controller, SLOT(explainDebuggerStatus())); // TODO: reimplement / re-enable //connect(this, SIGNAL(addWatchVariable(QString)), controller->variables(), SLOT(slotAddWatchVariable(QString))); //connect(this, SIGNAL(evaluateExpression(QString)), controller->variables(), SLOT(slotEvaluateExpression(QString))); } void DebugSession::_gdbStateChanged(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(DEBUGGERGDB) << "state: " << newState << message; if (!message.isEmpty()) emit showMessage(message, 3000); emit gdbStateChanged(oldState, newState); // must be last, since it can lead to deletion of the DebugSession if (newSessionState != oldSessionState) { setSessionState(newSessionState); } } void DebugSession::examineCoreFile(const QUrl& debugee, const QUrl& coreFile) { if (stateIsOn(s_dbgNotStarted)) startDebugger(0); // TODO support non-local URLs - queueCmd(new GDBCommand(MI::FileExecAndSymbols, debugee.toLocalFile())); - queueCmd(new GDBCommand(MI::NonMI, "core " + coreFile.toLocalFile(), this, &DebugSession::handleCoreFile, CmdHandlesError)); + queueCmd(new MICommand(MI::FileExecAndSymbols, debugee.toLocalFile())); + queueCmd(new MICommand(MI::NonMI, "core " + coreFile.toLocalFile(), this, &DebugSession::handleCoreFile, CmdHandlesError)); raiseEvent(connected_to_program); raiseEvent(program_state_changed); } void DebugSession::handleCoreFile(const MI::ResultRecord& r) { if (r.reason != "error") { setStateOn(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")); // How should we proceed at this point? Stop the debugger? } } void DebugSession::attachToProcess(int pid) { qCDebug(DEBUGGERGDB) << pid; if (stateIsOn(s_dbgNotStarted)) startDebugger(0); setStateOn(s_attached); //set current state to running, after attaching we will get *stopped response setStateOn(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. - queueCmd(new GDBCommand(MI::FileExecAndSymbols)); + queueCmd(new MICommand(MI::FileExecAndSymbols)); - queueCmd(new GDBCommand(MI::TargetAttach, QString::number(pid), this, &DebugSession::handleTargetAttach, CmdHandlesError)); + queueCmd(new MICommand(MI::TargetAttach, QString::number(pid), this, &DebugSession::handleTargetAttach, CmdHandlesError)); queueCmd(new SentinelCommand(breakpointController(), &BreakpointController::initSendBreakpoints)); raiseEvent(connected_to_program); emit raiseFramestackViews(); } void DebugSession::run() { if (stateIsOn(s_appNotStarted|s_dbgNotStarted|s_shuttingDown)) return; - queueCmd(new GDBCommand(MI::ExecContinue, QString(), CmdMaybeStartsRunning)); + queueCmd(new MICommand(MI::ExecContinue, QString(), CmdMaybeStartsRunning)); } void DebugSession::stepOut() { if (stateIsOn(s_appNotStarted|s_shuttingDown)) return; - queueCmd(new GDBCommand(MI::ExecFinish, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); + queueCmd(new MICommand(MI::ExecFinish, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); } void DebugSession::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. slotKill(); run(); } void DebugSession::stopDebugger() { commandQueue_->clear(); qCDebug(DEBUGGERGDB) << "DebugSession::slotStopDebugger() called"; if (stateIsOn(s_shuttingDown) || !m_gdb) return; setStateOn(s_shuttingDown); qCDebug(DEBUGGERGDB) << "DebugSession::slotStopDebugger() executing"; // Get gdb's attention if it's busy. We need gdb to be at the // command line so we can stop it. if (!m_gdb.data()->isReady()) { qCDebug(DEBUGGERGDB) << "gdb busy on shutdown - interruping"; m_gdb.data()->interrupt(); } // If the app is attached then we release it here. This doesn't stop // the app running. if (stateIsOn(s_attached)) { - queueCmd(new GDBCommand(MI::TargetDetach)); + queueCmd(new MICommand(MI::TargetDetach)); emit gdbUserCommandStdout("(gdb) detach\n"); } // Now try to stop gdb running. - queueCmd(new GDBCommand(MI::GdbExit)); + queueCmd(new MICommand(MI::GdbExit)); emit gdbUserCommandStdout("(gdb) quit"); // We cannot wait forever, kill gdb after 5 seconds if it's not yet quit QTimer::singleShot(5000, this, SLOT(slotKillGdb())); emit reset(); } // Pausing an app removes any pending run commands so that the app doesn't // start again. If we want to be silent then we remove any pending info // commands as well. void DebugSession::interruptDebugger() { Q_ASSERT(m_gdb); // Explicitly send the interrupt in case something went wrong with the usual // ensureGdbListening logic. m_gdb->interrupt(); - queueCmd(new GDBCommand(MI::ExecInterrupt, QString(), CmdInterrupt)); + queueCmd(new MICommand(MI::ExecInterrupt, QString(), CmdInterrupt)); } void DebugSession::runToCursor() { if (KDevelop::IDocument* doc = KDevelop::ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if (cursor.isValid()) runUntil(doc->url(), cursor.line() + 1); } } void DebugSession::jumpToCursor() { if (KDevelop::IDocument* doc = KDevelop::ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if (cursor.isValid()) jumpTo(doc->url(), cursor.line() + 1); } } void DebugSession::stepOver() { if (stateIsOn(s_appNotStarted|s_shuttingDown)) return; - queueCmd(new GDBCommand(MI::ExecNext, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); + queueCmd(new MICommand(MI::ExecNext, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); } void DebugSession::stepOverInstruction() { if (stateIsOn(s_appNotStarted|s_shuttingDown)) return; - queueCmd(new GDBCommand(MI::ExecNextInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); + queueCmd(new MICommand(MI::ExecNextInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); } void DebugSession::stepInto() { if (stateIsOn(s_appNotStarted|s_shuttingDown)) return; - queueCmd(new GDBCommand(MI::ExecStep, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); + queueCmd(new MICommand(MI::ExecStep, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); } void DebugSession::stepIntoInstruction() { if (stateIsOn(s_appNotStarted|s_shuttingDown)) return; - queueCmd(new GDBCommand(MI::ExecStepInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); + queueCmd(new MICommand(MI::ExecStepInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); } void DebugSession::slotDebuggerAbnormalExit() { KMessageBox::information( KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("GDB exited abnormally" "

This is likely a bug in GDB. " "Examine the gdb output window and then stop the debugger"), i18n("GDB exited abnormally")); // 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. } bool DebugSession::restartAvaliable() const { if (stateIsOn(s_attached) || stateIsOn(s_core)) { return false; } else { return true; } } void DebugSession::configure() { // KConfigGroup config(KSharedConfig::openConfig(), "GDB Debugger"); // // // A a configure.gdb script will prevent these from uncontrolled growth... // config_configGdbScript_ = config.readEntry("Remote GDB Configure Script", ""); // config_runShellScript_ = config.readEntry("Remote GDB Shell Script", ""); // config_runGdbScript_ = config.readEntry("Remote GDB Run Script", ""); // // // PORTING TODO: where is this in the ui? // config_forceBPSet_ = config.readEntry("Allow Forced Breakpoint Set", true); // // config_dbgTerminal_ = config.readEntry("Separate Terminal For Application IO", false); // // bool old_displayStatic = config_displayStaticMembers_; // config_displayStaticMembers_ = config.readEntry("Display Static Members",false); // // bool old_asmDemangle = config_asmDemangle_; // config_asmDemangle_ = config.readEntry("Display Demangle Names",true); // // bool old_breakOnLoadingLibrary_ = config_breakOnLoadingLibrary_; // config_breakOnLoadingLibrary_ = config.readEntry("Try Setting Breakpoints On Loading Libraries",true); // // // FIXME: should move this into debugger part or variable widget. // int old_outputRadix = config_outputRadix_; // #if 0 // config_outputRadix_ = DomUtil::readIntEntry("Output Radix", 10); // varTree_->setRadix(config_outputRadix_); // #endif // // // if (( old_displayStatic != config_displayStaticMembers_ || // old_asmDemangle != config_asmDemangle_ || // old_breakOnLoadingLibrary_ != config_breakOnLoadingLibrary_ || // old_outputRadix != config_outputRadix_) && // m_gdb) // { // bool restart = false; // if (stateIsOn(s_dbgBusy)) // { // slotPauseApp(); // restart = true; // } // // if (old_displayStatic != config_displayStaticMembers_) // { // if (config_displayStaticMembers_) -// queueCmd(new GDBCommand(MI::GdbSet, "print static-members on")); +// queueCmd(new MICommand(MI::GdbSet, "print static-members on")); // else -// queueCmd(new GDBCommand(MI::GdbSet, "print static-members off")); +// queueCmd(new MICommand(MI::GdbSet, "print static-members off")); // } // if (old_asmDemangle != config_asmDemangle_) // { // if (config_asmDemangle_) -// queueCmd(new GDBCommand(MI::GdbSet, "print asm-demangle on")); +// queueCmd(new MICommand(MI::GdbSet, "print asm-demangle on")); // else -// queueCmd(new GDBCommand(MI::GdbSet, "print asm-demangle off")); +// queueCmd(new MICommand(MI::GdbSet, "print asm-demangle off")); // } // // // Disabled for MI port. // if (old_outputRadix != config_outputRadix_) // { -// queueCmd(new GDBCommand(MI::GdbSet, QString().sprintf("output-radix %d", +// queueCmd(new MICommand(MI::GdbSet, QString().sprintf("output-radix %d", // config_outputRadix_))); // // // FIXME: should do this in variable widget anyway. // // After changing output radix, need to refresh variables view. // raiseEvent(program_state_changed); // // } // // if (config_configGdbScript_.isValid()) -// queueCmd(new GDBCommand(MI::NonMI, "source " + config_configGdbScript_.toLocalFile())); +// queueCmd(new MICommand(MI::NonMI, "source " + config_configGdbScript_.toLocalFile())); // // // if (restart) -// queueCmd(new GDBCommand(MI::ExecContinue)); +// queueCmd(new MICommand(MI::ExecContinue)); // } } // ************************************************************************** -void DebugSession::addCommand(GDBCommand* cmd) +void DebugSession::addCommand(MICommand* cmd) { queueCmd(cmd); } void DebugSession::addCommand(MI::CommandType type, const QString& str) { - queueCmd(new GDBCommand(type, str)); + queueCmd(new MICommand(type, str)); } // 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 DebugSession::queueCmd(GDBCommand *cmd) +void DebugSession::queueCmd(MICommand *cmd) { if (stateIsOn(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 (stateReloadInProgress_) cmd->setStateReloading(true); commandQueue_->enqueue(cmd); qCDebug(DEBUGGERGDB) << "QUEUE: " << cmd->initialString() << (stateReloadInProgress_ ? "(state reloading)" : ""); 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(DEBUGGERGDB) << "\t--thread will be added on execution"; if (cmd->frame() == -1) qCDebug(DEBUGGERGDB) << "\t--frame will be added on execution"; } setStateOn(s_dbgBusy); raiseEvent(debugger_busy); executeCmd(); } void DebugSession::executeCmd() { Q_ASSERT(m_gdb); if (stateIsOn(s_dbgNotListening) && commandQueue_->haveImmediateCommand()) { // We may have to call this even while a command is currently executing, because // Gdb can get into a state where a command such as ExecRun does not send a response // while the inferior is running. ensureGdbListening(); } if (!m_gdb.data()->isReady()) return; - GDBCommand* currentCmd = commandQueue_->nextCommand(); + MICommand* currentCmd = commandQueue_->nextCommand(); if (!currentCmd) return; if (currentCmd->flags() & (CmdMaybeStartsRunning | CmdInterrupt)) { setStateOff(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. setStateOn(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(DEBUGGERGDB) << "SEND: sentinel command, not sending"; sc->invokeHandler(); } else { qCDebug(DEBUGGERGDB) << "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_gdb.data()->execute(currentCmd); } // ************************************************************************** void DebugSession::destroyCmds() { commandQueue_->clear(); } void DebugSession::slotProgramStopped(const MI::AsyncRecord& r) { /* By default, reload all state on program stop. */ state_reload_needed = true; setStateOff(s_appRunning); setStateOff(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")); } state_reload_needed = false; return; } if (reason == "exited-signalled") { programNoApp(i18n("Exited on signal %1", r["signal-name"].literal())); state_reload_needed = 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. - queueCmd(new GDBCommand(MI::ExecContinue, QString(), CmdMaybeStartsRunning)); + queueCmd(new MICommand(MI::ExecContinue, QString(), CmdMaybeStartsRunning)); state_reload_needed = 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" && stateIsOn(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(); } } setStateOff(s_interruptSent); if (!wasInterrupt) setStateOff(s_automaticContinue); } bool DebugSession::hasCrashed() const { return m_hasCrashed; } void DebugSession::processNotification(const MI::AsyncRecord & async) { if (async.reason == "thread-group-started") { setStateOff(s_appNotStarted | s_programExited); } else if (async.reason == "thread-group-exited") { setStateOn(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(DEBUGGERGDB) << "Unhandled notification: " << async.reason; } } void DebugSession::reloadProgramState() { raiseEvent(program_state_changed); state_reload_needed = 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 DebugSession::programNoApp(const QString& msg) { qCDebug(DEBUGGERGDB) << msg; setState(s_appNotStarted|s_programExited|(state_&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); programFinished(msg); } void DebugSession::programFinished(const QString& msg) { QString m = QString("*** %0 ***").arg(msg.trimmed()); emit applicationStandardErrorLines(QStringList(m)); /* Also show message in gdb window, so that users who prefer to look at gdb window know what's up. */ emit gdbUserCommandStdout(m); } bool DebugSession::startDebugger(KDevelop::ILaunchConfiguration* cfg) { qCDebug(DEBUGGERGDB) << "Starting debugger controller"; if(m_gdb) { qWarning() << "m_gdb object still existed"; delete m_gdb.data(); m_gdb.clear(); } GDB* gdb = new GDB(this); m_gdb = gdb; // FIXME: here, we should wait until GDB 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. connect(gdb, &GDB::applicationOutput, this, [this](const QString& output) { emit applicationStandardOutputLines(output.split(QRegularExpression("[\r\n]"),QString::SkipEmptyParts)); }); connect(gdb, &GDB::userCommandOutput, this, &DebugSession::gdbUserCommandStdout); connect(gdb, &GDB::internalCommandOutput, this, &DebugSession::gdbInternalCommandStdout); connect(gdb, &GDB::ready, this, &DebugSession::gdbReady); connect(gdb, &GDB::gdbExited, this, &DebugSession::gdbExited); connect(gdb, &GDB::programStopped, this, &DebugSession::slotProgramStopped); connect(gdb, &GDB::programStopped, this, &DebugSession::programStopped); connect(gdb, &GDB::programRunning, this, &DebugSession::programRunning); connect(gdb, &GDB::notification, this, &DebugSession::processNotification); // Start gdb. Do this after connecting all signals so that initial // GDB output, and important events like "GDB died" are reported. { QStringList extraArguments; if (m_testing) extraArguments << "--nx"; // do not load any .gdbinit files if (cfg) { KConfigGroup config = cfg->config(); m_gdb.data()->start(config, extraArguments); } else { // FIXME: this is hack, I am not sure there's any way presently // to edit this via GUI. KConfigGroup config(KSharedConfig::openConfig(), "GDB Debugger"); m_gdb.data()->start(config, extraArguments); } } setStateOff(s_dbgNotStarted); // Initialise gdb. At this stage gdb is sitting wondering what to do, // and to whom. Organise a few things, then set up the tty for the application, // and the application itself - //queueCmd(new GDBCommand(MI::EnableTimings, "yes")); + //queueCmd(new MICommand(MI::EnableTimings, "yes")); queueCmd(new CliCommand(MI::GdbShow, "version", this, &DebugSession::handleVersion)); // This makes gdb pump a variable out on one line. - queueCmd(new GDBCommand(MI::GdbSet, "width 0")); - queueCmd(new GDBCommand(MI::GdbSet, "height 0")); + queueCmd(new MICommand(MI::GdbSet, "width 0")); + queueCmd(new MICommand(MI::GdbSet, "height 0")); - queueCmd(new GDBCommand(MI::SignalHandle, "SIG32 pass nostop noprint")); - queueCmd(new GDBCommand(MI::SignalHandle, "SIG41 pass nostop noprint")); - queueCmd(new GDBCommand(MI::SignalHandle, "SIG42 pass nostop noprint")); - queueCmd(new GDBCommand(MI::SignalHandle, "SIG43 pass nostop noprint")); + queueCmd(new MICommand(MI::SignalHandle, "SIG32 pass nostop noprint")); + queueCmd(new MICommand(MI::SignalHandle, "SIG41 pass nostop noprint")); + queueCmd(new MICommand(MI::SignalHandle, "SIG42 pass nostop noprint")); + queueCmd(new MICommand(MI::SignalHandle, "SIG43 pass nostop noprint")); - queueCmd(new GDBCommand(MI::EnablePrettyPrinting)); + queueCmd(new MICommand(MI::EnablePrettyPrinting)); - queueCmd(new GDBCommand(MI::GdbSet, "charset UTF-8")); - queueCmd(new GDBCommand(MI::GdbSet, "print sevenbit-strings off")); + queueCmd(new MICommand(MI::GdbSet, "charset UTF-8")); + queueCmd(new MICommand(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('"', "\\\""); - queueCmd(new GDBCommand(MI::NonMI, + queueCmd(new MICommand(MI::NonMI, QString("python sys.path.insert(0, \"%0\")").arg(quotedPrintersPath))); - queueCmd(new GDBCommand(MI::NonMI, "source " + fileName)); + queueCmd(new MICommand(MI::NonMI, "source " + fileName)); } return true; } bool DebugSession::startProgram(KDevelop::ILaunchConfiguration* cfg, IExecutePlugin* iface) { if (stateIsOn( s_appNotStarted ) ) { emit showMessage(i18n("Running program"), 1000); } if (stateIsOn(s_dbgNotStarted)) { if (!startDebugger(cfg)) return false; } if (stateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERGDB) << "Tried to run when debugger shutting down"; return false; } KConfigGroup grp = cfg->config(); KDevelop::EnvironmentGroupList l(KSharedConfig::openConfig()); QString envgrp = iface->environmentGroup( cfg ); if( envgrp.isEmpty() ) { qWarning() << i18n("No environment group specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment group.", cfg->name() ); envgrp = l.defaultGroup(); } if (grp.readEntry("Break on Start", false)) { BreakpointModel* m = KDevelop::ICore::self()->debugController()->breakpointModel(); bool found = false; foreach (KDevelop::Breakpoint *b, m->breakpoints()) { if (b->location() == "main") { found = true; break; } } if (!found) { KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint("main"); } } // Configuration values bool config_displayStaticMembers_ = grp.readEntry( GDBDebugger::staticMembersEntry, false ); bool config_asmDemangle_ = grp.readEntry( GDBDebugger::demangleNamesEntry, true ); QUrl config_configGdbScript_ = grp.readEntry( GDBDebugger::remoteGdbConfigEntry, QUrl() ); QUrl config_runShellScript_ = grp.readEntry( GDBDebugger::remoteGdbShellEntry, QUrl() ); QUrl config_runGdbScript_ = grp.readEntry( GDBDebugger::remoteGdbRunEntry, QUrl() ); Q_ASSERT(iface); bool config_useExternalTerminal = iface->useTerminal( cfg ); QString config_externalTerminal = iface->terminal( cfg ); if (!config_externalTerminal.isEmpty()) { // the external terminal cmd contains additional arguments, just get the terminal name config_externalTerminal = KShell::splitArgs(config_externalTerminal).first(); } m_tty.reset(new STTY(config_useExternalTerminal, config_externalTerminal)); if (!config_useExternalTerminal) { connect( m_tty.data(), &STTY::OutOutput, this, &DebugSession::ttyStdout ); connect( m_tty.data(), &STTY::ErrOutput, this, &DebugSession::ttyStderr ); } QString tty(m_tty->getSlave()); if (tty.isEmpty()) { KMessageBox::information(qApp->activeWindow(), m_tty->lastError(), i18n("Warning")); m_tty.reset(0); return false; } - queueCmd(new GDBCommand(MI::InferiorTtySet, tty)); + queueCmd(new MICommand(MI::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 = iface->executable(cfg, err).toLocalFile(); QStringList arguments = iface->arguments(cfg, err); // Change the "Working directory" to the correct one QString dir = iface->workingDirectory(cfg).toLocalFile(); if (dir.isEmpty()) { dir = QFileInfo(executable).absolutePath(); } - queueCmd(new GDBCommand(MI::EnvironmentCd, '"' + dir + '"')); + queueCmd(new MICommand(MI::EnvironmentCd, '"' + dir + '"')); // Set the run arguments if (!arguments.isEmpty()) queueCmd( - new GDBCommand(MI::ExecArguments, KShell::joinArgs( arguments ))); + new MICommand(MI::ExecArguments, KShell::joinArgs( arguments ))); foreach (const QString& envvar, l.createEnvironment(envgrp, QStringList())) - queueCmd(new GDBCommand(MI::GdbSet, "environment " + envvar)); + queueCmd(new MICommand(MI::GdbSet, "environment " + envvar)); // Needed so that breakpoint widget has a chance to insert breakpoints. // FIXME: a bit hacky, as we're really not ready for new commands. setStateOn(s_dbgBusy); raiseEvent(debugger_ready); if (config_displayStaticMembers_) - queueCmd(new GDBCommand(MI::GdbSet, "print static-members on")); + queueCmd(new MICommand(MI::GdbSet, "print static-members on")); else - queueCmd(new GDBCommand(MI::GdbSet, "print static-members off")); + queueCmd(new MICommand(MI::GdbSet, "print static-members off")); if (config_asmDemangle_) - queueCmd(new GDBCommand(MI::GdbSet, "print asm-demangle on")); + queueCmd(new MICommand(MI::GdbSet, "print asm-demangle on")); else - queueCmd(new GDBCommand(MI::GdbSet, "print asm-demangle off")); + queueCmd(new MICommand(MI::GdbSet, "print asm-demangle off")); if (config_configGdbScript_.isValid()) - queueCmd(new GDBCommand(MI::NonMI, "source " + KShell::quoteArg(config_configGdbScript_.toLocalFile()))); + queueCmd(new MICommand(MI::NonMI, "source " + KShell::quoteArg(config_configGdbScript_.toLocalFile()))); if (!config_runShellScript_.isEmpty()) { // Special for remote debug... QByteArray tty(m_tty->getSlave().toLatin1()); QByteArray options = QByteArray(">") + tty + QByteArray(" 2>&1 <") + tty; QProcess *proc = new QProcess; QStringList arguments; arguments << "-c" << KShell::quoteArg(config_runShellScript_.toLocalFile()) + ' ' + KShell::quoteArg(executable) + QString::fromLatin1( options ); qCDebug(DEBUGGERGDB) << "starting sh" << arguments; proc->start("sh", arguments); //PORTING TODO QProcess::DontCare); } if (!config_runGdbScript_.isEmpty()) {// gdb script at run is requested // Race notice: wait for the remote gdbserver/executable // - but that might be an issue for this script to handle... // Future: the shell script should be able to pass info (like pid) // to the gdb script... queueCmd(new SentinelCommand([this, config_runGdbScript_]() { breakpointController()->initSendBreakpoints(); breakpointController()->setDeleteDuplicateBreakpoints(true); qCDebug(DEBUGGERGDB) << "Running gdb script " << KShell::quoteArg(config_runGdbScript_.toLocalFile()); - queueCmd(new GDBCommand(MI::NonMI, "source " + KShell::quoteArg(config_runGdbScript_.toLocalFile()), + queueCmd(new MICommand(MI::NonMI, "source " + KShell::quoteArg(config_runGdbScript_.toLocalFile()), [this](const MI::ResultRecord&) {breakpointController()->setDeleteDuplicateBreakpoints(false);}, CmdMaybeStartsRunning)); raiseEvent(connected_to_program); // Note: script could contain "run" or "continue" }, CmdMaybeStartsRunning)); } else { - queueCmd(new GDBCommand(MI::FileExecAndSymbols, KShell::quoteArg(executable), this, &DebugSession::handleFileExecAndSymbols, CmdHandlesError)); + queueCmd(new MICommand(MI::FileExecAndSymbols, KShell::quoteArg(executable), this, &DebugSession::handleFileExecAndSymbols, CmdHandlesError)); raiseEvent(connected_to_program); queueCmd(new SentinelCommand([this]() { breakpointController()->initSendBreakpoints(); - queueCmd(new GDBCommand(MI::ExecRun, QString(), CmdMaybeStartsRunning)); + queueCmd(new MICommand(MI::ExecRun, QString(), CmdMaybeStartsRunning)); }, CmdMaybeStartsRunning)); } { QString startWith = grp.readEntry(GDBDebugger::startWithEntry, QString("ApplicationOutput")); if (startWith == "GdbConsole") { emit raiseGdbConsoleViews(); } else if (startWith == "FrameStack") { emit raiseFramestackViews(); } else { //ApplicationOutput is raised in DebugJob (by setting job to Verbose/Silent) } } return true; } // ************************************************************************** // SLOTS // ***** // For most of these slots data can only be sent to gdb when it // isn't busy and it is running. // ************************************************************************** void DebugSession::slotKillGdb() { if (!stateIsOn(s_programExited) && stateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERGDB) << "gdb not shutdown - killing"; m_gdb.data()->kill(); setState(s_dbgNotStarted | s_appNotStarted); raiseEvent(debugger_exited); } } // ************************************************************************** void DebugSession::slotKill() { if (stateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (stateIsOn(s_dbgBusy)) { interruptDebugger(); } // The -exec-abort is not implemented in gdb - // queueCmd(new GDBCommand(MI::ExecAbort)); - queueCmd(new GDBCommand(MI::NonMI, "kill")); + // queueCmd(new MICommand(MI::ExecAbort)); + queueCmd(new MICommand(MI::NonMI, "kill")); } // ************************************************************************** void DebugSession::runUntil(const QUrl& url, int line) { if (stateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!url.isValid()) - queueCmd(new GDBCommand(MI::ExecUntil, QString::number(line), + queueCmd(new MICommand(MI::ExecUntil, QString::number(line), CmdMaybeStartsRunning | CmdTemporaryRun)); else - queueCmd(new GDBCommand(MI::ExecUntil, + queueCmd(new MICommand(MI::ExecUntil, QString("%1:%2").arg(url.toLocalFile()).arg(line), CmdMaybeStartsRunning | CmdTemporaryRun)); } void DebugSession::runUntil(const QString& address) { if (stateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!address.isEmpty()) { - queueCmd(new GDBCommand(MI::ExecUntil, QString("*%1").arg(address), + queueCmd(new MICommand(MI::ExecUntil, QString("*%1").arg(address), CmdMaybeStartsRunning | CmdTemporaryRun)); } } // ************************************************************************** void DebugSession::jumpToMemoryAddress(const QString& address) { if (stateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!address.isEmpty()) { - queueCmd(new GDBCommand(MI::NonMI, QString("tbreak *%1").arg(address))); - queueCmd(new GDBCommand(MI::NonMI, QString("jump *%1").arg(address))); + queueCmd(new MICommand(MI::NonMI, QString("tbreak *%1").arg(address))); + queueCmd(new MICommand(MI::NonMI, QString("jump *%1").arg(address))); } } void DebugSession::jumpTo(const QUrl& url, int line) { if (stateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (url.isValid()) { - queueCmd(new GDBCommand(MI::NonMI, QString("tbreak %1:%2").arg(url.toLocalFile()).arg(line))); - queueCmd(new GDBCommand(MI::NonMI, QString("jump %1:%2").arg(url.toLocalFile()).arg(line))); + queueCmd(new MICommand(MI::NonMI, QString("tbreak %1:%2").arg(url.toLocalFile()).arg(line))); + queueCmd(new MICommand(MI::NonMI, QString("jump %1:%2").arg(url.toLocalFile()).arg(line))); } } // ************************************************************************** // FIXME: connect to GDB's slot. void DebugSession::defaultErrorHandler(const MI::ResultRecord& result) { QString msg = result["msg"].literal(); if (msg.contains("No such process")) { setState(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_gdb.data()->currentCommand()->stateReloading()) raiseEvent(program_state_changed); } void DebugSession::gdbReady() { stateReloadInProgress_ = false; executeCmd(); if (m_gdb->isReady()) { /* There is nothing in the command queue and no command is currently executing. */ if (stateIsOn(s_automaticContinue)) { if (!stateIsOn(s_appRunning)) { qCDebug(DEBUGGERGDB) << "Posting automatic continue"; - queueCmd(new GDBCommand(MI::ExecContinue, QString(), CmdMaybeStartsRunning)); + queueCmd(new MICommand(MI::ExecContinue, QString(), CmdMaybeStartsRunning)); } setStateOff(s_automaticContinue); return; } if (state_reload_needed && !stateIsOn(s_appRunning)) { qCDebug(DEBUGGERGDB) << "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. state_reload_needed = false; reloadProgramState(); } qCDebug(DEBUGGERGDB) << "No more commands"; setStateOff(s_dbgBusy); raiseEvent(debugger_ready); } } void DebugSession::gdbExited() { qCDebug(DEBUGGERGDB); /* 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. */ setStateOn(s_appNotStarted); setStateOn(s_dbgNotStarted); setStateOff(s_shuttingDown); } void DebugSession::ensureGdbListening() { Q_ASSERT(m_gdb); m_gdb->interrupt(); setStateOn(s_interruptSent); if (stateIsOn(s_appRunning)) setStateOn(s_automaticContinue); setStateOff(s_dbgNotListening); } // FIXME: I don't fully remember what is the business with // stateReloadInProgress_ and whether we can lift it to the // generic level. void DebugSession::raiseEvent(event_t e) { if (e == program_exited || e == debugger_exited) { stateReloadInProgress_ = false; } if (e == program_state_changed) { stateReloadInProgress_ = true; qCDebug(DEBUGGERGDB) << "State reload in progress\n"; } IDebugSession::raiseEvent(e); if (e == program_state_changed) { stateReloadInProgress_ = false; } } // ************************************************************************** void DebugSession::slotUserGDBCmd(const QString& cmd) { 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 (!stateIsOn(s_appNotStarted) && !stateIsOn(s_programExited)) raiseEvent(program_state_changed); } void DebugSession::explainDebuggerStatus() { - GDBCommand* currentCmd_ = m_gdb.data()->currentCommand(); + MICommand* currentCmd_ = m_gdb.data()->currentCommand(); QString information = i18np("1 command in queue\n", "%1 commands in queue\n", 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", state_); 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")); } bool DebugSession::stateIsOn(DBGStateFlags state) const { return state_ & state; } DBGStateFlags DebugSession::debuggerState() const { return state_; } void DebugSession::setStateOn(DBGStateFlags stateOn) { DBGStateFlags oldState = state_; debugStateChange(state_, state_ | stateOn); state_ |= stateOn; _gdbStateChanged(oldState, state_); } void DebugSession::setStateOff(DBGStateFlags stateOff) { DBGStateFlags oldState = state_; debugStateChange(state_, state_ & ~stateOff); state_ &= ~stateOff; _gdbStateChanged(oldState, state_); } void DebugSession::setState(DBGStateFlags newState) { DBGStateFlags oldState = state_; debugStateChange(state_, newState); state_ = newState; _gdbStateChanged(oldState, state_); } void DebugSession::debugStateChange(DBGStateFlags oldState, DBGStateFlags newState) { int delta = oldState ^ newState; if (delta) { QString out = "STATE:"; #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(DEBUGGERGDB) << out; } } void DebugSession::programRunning() { setStateOn(s_appRunning); raiseEvent(program_running); if (commandQueue_->haveImmediateCommand() || (m_gdb->currentCommand() && (m_gdb->currentCommand()->flags() & (CmdImmediately | CmdInterrupt)))) { ensureGdbListening(); } else { setStateOn(s_dbgNotListening); } } 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 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::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(); } } - -} - - diff --git a/debuggers/gdb/debugsession.h b/debuggers/gdb/debugsession.h index da2f0ce793..6bbd7a2872 100644 --- a/debuggers/gdb/debugsession.h +++ b/debuggers/gdb/debugsession.h @@ -1,306 +1,309 @@ /* * 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 #include #include #include #include "breakpointcontroller.h" #include "gdbglobal.h" #include "mi/mi.h" class IExecutePlugin; class KToolBar; namespace KTextEditor { class Cursor; } namespace KDevelop { class ProcessLineMaker; class ILaunchConfiguration; } +namespace MI { +class MICommand; +} + namespace GDBDebugger { class STTY; class CommandQueue; -class GDBCommand; class GDB; 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"; class DebugSession : public KDevelop::IDebugSession { Q_OBJECT public: DebugSession(); ~DebugSession() override; DebuggerState state() const override; bool restartAvaliable() const override; BreakpointController* breakpointController() const override; KDevelop::IVariableController* variableController() const override; KDevelop::IFrameStackModel* frameStackModel() const override; bool hasCrashed() const; using IDebugSession::event; Q_SIGNALS: void applicationStandardOutputLines(const QStringList& lines); void applicationStandardErrorLines(const QStringList& lines); void showMessage(const QString& message, int timeout); void reset(); void programStopped(const MI::AsyncRecord& mi_record); public Q_SLOTS: /** * Start the debugger, and execute the program specified by \a run. */ bool startProgram(KDevelop::ILaunchConfiguration* run, IExecutePlugin* execute); 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; /** * Start the debugger and examine the core file given by \a coreFile. */ void examineCoreFile(const QUrl& debugee, const QUrl& coreFile); /** * Attach to currently running process with the given \a pid. */ void attachToProcess(int pid); protected: /** * Testing mode affects a (very!) limited number of settings in an attempt to create * a cleaner and more reproducible environment for unit tests. */ void setTesting(bool testing); Q_SIGNALS: void raiseGdbConsoleViews(); private Q_SLOTS: void slotDebuggerAbnormalExit(); private: void _gdbStateChanged(DBGStateFlags oldState, DBGStateFlags newState); void setupController(); void setSessionState(KDevelop::IDebugSession::DebuggerState state); public: /** * 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); /** 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 addCommand(GDBCommand* cmd); + void addCommand(MI::MICommand* cmd); - /** Same as above, but internally constructs new GDBCommand + /** Same as above, but internally constructs new MI::MICommand instance from the string. */ void addCommand(MI::CommandType type, const QString& cmd = QString()); bool stateIsOn(DBGStateFlags state) const; DBGStateFlags debuggerState() const; using QObject::event; private: /** 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 ensureGdbListening(); void destroyCmds(); /** Called when there are no pending commands and 'state_reload_needed' 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); void setStateOn(DBGStateFlags stateOn); void setStateOff(DBGStateFlags stateOff); void setState(DBGStateFlags newState); void debugStateChange(DBGStateFlags oldState, DBGStateFlags newState); void raiseEvent(event_t e) override; bool startDebugger(KDevelop::ILaunchConfiguration* cfg); private Q_SLOTS: void gdbReady(); void gdbExited(); void slotProgramStopped(const MI::AsyncRecord& mi_record); /** 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. */ void defaultErrorHandler(const MI::ResultRecord& result); /**Triggered every time program begins/continues it's execution.*/ void programRunning(); /** Handle MI async notifications. */ void processNotification(const MI::AsyncRecord& n); // All of these slots are entered in the controller's thread, as they use queued connections or are called internally - void queueCmd(GDBCommand *cmd); + void queueCmd(MI::MICommand *cmd); void configure(); // 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(); void slotKillGdb(); void handleVersion(const QStringList& s); void handleFileExecAndSymbols(const MI::ResultRecord& r); void handleTargetAttach(const MI::ResultRecord& r); void handleCoreFile(const MI::ResultRecord& r); public Q_SLOTS: void slotKill(); void slotUserGDBCmd(const QString&); Q_SIGNALS: void rawGDBMemoryDump (char *buf); void rawGDBRegisters (char *buf); void rawGDBLibraries (char *buf); void ttyStdout (const QByteArray& output); void ttyStderr (const QByteArray& output); void gdbInternalCommandStdout (const QString& output); void gdbUserCommandStdout (const QString& output); void gdbStateChanged(DBGStateFlags oldState, DBGStateFlags newState); void debuggerAbnormalExit(); /** Emitted immediately after breakpoint is hit, before any commands are sent and before current line indicator is shown. */ void breakpointHit(int id); /** Emitted for watchpoint hit, after line indicator is shown. */ void watchpointHit(int id, const QString& oldValue, const QString& newValue); private: friend class GdbTest; BreakpointController* m_breakpointController; KDevelop::IVariableController* m_variableController; KDevelop::IFrameStackModel* m_frameStackModel; KDevelop::ProcessLineMaker *m_procLineMaker; KDevelop::ProcessLineMaker *m_gdbLineMaker; DebuggerState m_sessionState; KConfigGroup m_config; QPointer m_gdb; bool m_testing; CommandQueue* commandQueue_; QScopedPointer m_tty; QString badCore_; // Some state variables DBGStateFlags state_; /**When program stops and all commands from queue are executed and this variable is true, program state shown to the user is updated.*/ bool state_reload_needed; /**True if program has stopped and all stuff like breakpoints is being updated.*/ bool stateReloadInProgress_; /**True if process crashed*/ bool m_hasCrashed; QTime commandExecutionTime; ///Exit code of the last inferior(in format: exit normally, with code "number" e.t.c) QString m_inferiorExitCode; }; } #endif diff --git a/debuggers/gdb/disassemblewidget.cpp b/debuggers/gdb/disassemblewidget.cpp index 8ce9910151..91d99f5c65 100644 --- a/debuggers/gdb/disassemblewidget.cpp +++ b/debuggers/gdb/disassemblewidget.cpp @@ -1,442 +1,441 @@ /* * 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 "gdbcommand.h" + #include "debuggerplugin.h" +#include "debug.h" +#include "debugsession.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 -#include -#include -#include - -#include - -#include -#include -#include -#include "debugsession.h" - -#include "registers/registersmanager.h" -#include "debug.h" - using namespace MI; namespace GDBDebugger { 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); } } void DisassembleWindow::contextMenuEvent(QContextMenuEvent *e) { QMenu popup(this); popup.addAction(m_selectAddrAction); popup.addAction(m_jumpToLocation); popup.addAction(m_runUntilCursor); popup.exec(e->globalPos()); } /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ DisassembleWidget::DisassembleWidget(CppDebuggerPlugin* 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, &CppDebuggerPlugin::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() { DebugSession *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(){ DebugSession *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) { DebugSession *session = qobject_cast(s); enableControls( session != NULL ); // disable if session closed m_registersManager->setSession(session); if (session) { connect(session, &DebugSession::showStepInSource, this, &DisassembleWidget::slotShowStepInSource); connect(session,&DebugSession::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(DEBUGGERGDB) << "Disassemble widget active: " << activate; if (active_ != activate) { active_ = activate; if (active_) { 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) { DebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) return; //only get $pc if (from.isEmpty()){ s->addCommand( - new GDBCommand(DataDisassemble, "-s \"$pc\" -e \"$pc+1\" -- 0", this, &DisassembleWidget::updateExecutionAddressHandler ) ); + new MICommand(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( - new GDBCommand(DataDisassemble, cmd, this, &DisassembleWidget::disassembleMemoryHandler ) ); + new MICommand(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(); } } diff --git a/debuggers/gdb/gdb.cpp b/debuggers/gdb/gdb.cpp index 02dfe74c5f..ef0d81fd31 100644 --- a/debuggers/gdb/gdb.cpp +++ b/debuggers/gdb/gdb.cpp @@ -1,412 +1,413 @@ /* * Low level GDB interface. * * Copyright 1999 John Birch * Copyright 2007 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 "gdb.h" #include "debugsession.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include // #define DEBUG_NO_TRY //to get a backtrace to where the exception was thrown Q_LOGGING_CATEGORY(DEBUGGERGDB, "kdevelop.debuggers.gdb") using namespace GDBDebugger; +using namespace MI; GDB::GDB(QObject* parent) : QObject(parent), process_(0), currentCmd_(0) { } GDB::~GDB() { // prevent Qt warning: QProcess: Destroyed while process is still running. if (process_ && process_->state() == QProcess::Running) { disconnect(process_, static_cast(&KProcess::error), this, &GDB::processErrored); process_->kill(); process_->waitForFinished(10); } } void GDB::start(KConfigGroup& config, const QStringList& extraArguments) { // FIXME: verify that default value leads to something sensible QUrl gdbUrl = config.readEntry(GDBDebugger::gdbPathEntry, QUrl()); if (gdbUrl.isEmpty()) { gdbBinary_ = "gdb"; } else { // FIXME: verify its' a local path. gdbBinary_ = gdbUrl.url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); } process_ = new KProcess(this); process_->setOutputChannelMode( KProcess::SeparateChannels ); connect(process_, &KProcess::readyReadStandardOutput, this, &GDB::readyReadStandardOutput); connect(process_, &KProcess::readyReadStandardError, this, &GDB::readyReadStandardError); connect(process_, static_cast(&KProcess::finished), this, &GDB::processFinished); connect(process_, static_cast(&KProcess::error), this, &GDB::processErrored); QStringList arguments = extraArguments; arguments << "--interpreter=mi2" << "-quiet"; QUrl shell = config.readEntry(GDBDebugger::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") ); // FIXME: throw, or set some error message. return; } arguments.insert(0, gdbBinary_); arguments.insert(0, shell.toLocalFile()); process_->setShellCommand( KShell::joinArgs( arguments ) ); } else { process_->setProgram( gdbBinary_, arguments ); } process_->start(); qCDebug(DEBUGGERGDB) << "STARTING GDB\n"; emit userCommandOutput(shell.toLocalFile() + ' ' + gdbBinary_ + " --interpreter=mi2 -quiet\n" ); } -void GDB::execute(GDBCommand* command) +void GDB::execute(MICommand* command) { currentCmd_ = command; QString commandText = currentCmd_->cmdToSend(); qCDebug(DEBUGGERGDB) << "SEND:" << commandText.trimmed(); QByteArray commandUtf8 = commandText.toUtf8(); process_->write(commandUtf8, commandUtf8.length()); 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 GDB::isReady() const { return currentCmd_ == 0; } void GDB::interrupt() { //TODO:win32 Porting needed int pid = process_->pid(); if (pid != 0) { ::kill(pid, SIGINT); } } -GDBCommand* GDB::currentCommand() const +MICommand* GDB::currentCommand() const { return currentCmd_; } void GDB::kill() { process_->kill(); } void GDB::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 GDB::readyReadStandardError() { process_->setReadChannel(QProcess::StandardOutput); emit internalCommandOutput(QString::fromUtf8(process_->readAll())); } void GDB::processLine(const QByteArray& line) { qCDebug(DEBUGGERGDB) << "GDB output: " << line; FileSymbol file; file.contents = line; std::unique_ptr r(mi_parser_.parse(&file)); if (!r) { // FIXME: Issue an error! qCDebug(DEBUGGERGDB) << "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'); // GDB doc: "running" and "exit" are status codes equivalent to "done" if (result.reason == "done" || result.reason == "running" || result.reason == "exit") { if (!currentCmd_) { qCDebug(DEBUGGERGDB) << "Received a result without a pending command"; } else { Q_ASSERT(currentCmd_->token() == result.token); currentCmd_->invokeHandler(result); } } else if (result.reason == "error") { qCDebug(DEBUGGERGDB) << "Handling error"; // Some commands want to handle errors themself. if (currentCmd_->handlesError() && currentCmd_->invokeHandler(result)) { qCDebug(DEBUGGERGDB) << "Invoked custom handler\n"; // Done, nothing more needed } else emit error(result); } else { qCDebug(DEBUGGERGDB) << "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(DEBUGGERGDB) << "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 (currentCmd_ && currentCmd_->isUserCommand()) emit userCommandOutput(s.message); else if (s.subkind == MI::StreamRecord::Console) { emit applicationOutput(s.message); } else { emit internalCommandOutput(s.message); } if (currentCmd_) currentCmd_->newOutput(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 GDB::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); Q_UNUSED(exitStatus); qCDebug(DEBUGGERGDB) << "GDB FINISHED\n"; /* FIXME: return the status? */ emit gdbExited(); /* FIXME: revive. Need to arrange for controller to delete us. bool abnormal = exitCode != 0 || exitStatus != QProcess::NormalExit; deleteLater(); delete tty_; tty_ = 0; if (abnormal) emit debuggerAbnormalExit(); raiseEvent(debugger_exited); destroyCmds(); setState(s_dbgNotStarted|s_appNotStarted|s_programExited); emit showMessage(i18n("Process exited"), 3000); emit gdbUserCommandStdout("(gdb) Process exited\n"); */ } void GDB::processErrored(QProcess::ProcessError error) { qCDebug(DEBUGGERGDB) << "GDB 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.", gdbBinary_), i18n("Could not start debugger")); /* FIXME: make sure the controller gets rids of GDB instance emit debuggerAbnormalExit(); raiseEvent(debugger_exited); */ /* Used to be before, GDB controller might want to do the same. destroyCmds(); setState(s_dbgNotStarted|s_appNotStarted|s_programExited); emit showMessage(i18n("Process didn't start"), 3000); */ emit userCommandOutput("(gdb) didn't start\n"); } 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")); } } diff --git a/debuggers/gdb/gdb.h b/debuggers/gdb/gdb.h index 628addda91..b96272d76d 100644 --- a/debuggers/gdb/gdb.h +++ b/debuggers/gdb/gdb.h @@ -1,144 +1,144 @@ /* * Low level GDB interface. * * Copyright 2007 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 GDB_H_d5c9cb274cbad688fe7a507a84f6633b #define GDB_H_d5c9cb274cbad688fe7a507a84f6633b #include "mi/mi.h" #include "mi/miparser.h" -#include "gdbcommand.h" +#include "mi/micommand.h" #include #include #include class KConfigGroup; namespace GDBDebugger { class GDB : public QObject { Q_OBJECT public: explicit GDB(QObject* parent = 0); ~GDB() override; /** Starts GDB. This should be done after connecting to all signals the client is interested in. */ void start(KConfigGroup& config, const QStringList& extraArguments = {}); /** Executes a command. This method may be called at most once each time 'ready' is emitted. When the GDB instance is just constructed, one should wait for 'ready' as well. The ownership of 'command' is transferred to GDB. */ - void execute(GDBCommand* command); + void execute(MI::MICommand* command); /** Returns true if 'execute' can be called immediately. */ bool isReady() const; /** FIXME: temporary, to be eliminated. */ - GDBCommand* currentCommand() const; + MI::MICommand* currentCommand() const; /** Arrange to gdb to stop doing whatever it's doing, and start waiting for a command. FIXME: probably should make sure that 'ready' is emitted, or something. */ void interrupt(); /** Kills GDB. */ void kill(); Q_SIGNALS: /** Emitted when debugger becomes ready -- i.e. when isReady call will return true. */ void ready(); /** Emitted when GDB itself exits. This could happen because it just crashed due to internal bug, or we killed it explicitly. */ void gdbExited(); /** Emitted when GDB reports stop, with 'r' being the data provided by GDB. */ void programStopped(const MI::AsyncRecord& r); /** Emitted when GDB believes that the program is running. */ void programRunning(); /** Emitted for each MI stream record found. Presently only used to recognize some CLI messages that mean that the program has died. FIXME: connect to parseCliLine */ void streamRecord(const MI::StreamRecord& s); /** Reports an async notification record. */ void notification(const MI::AsyncRecord& n); /** Emitted for error that is not handled by the command being executed. */ void error(const MI::ResultRecord& s); /** Reports output from the running application. Generally output will only be available when using remote GDB targets. When running locally, the output will either appear on GDB stdout, and ignored, or routed via pty. */ void applicationOutput(const QString& s); /** Reports output of a command explicitly typed by the user, or output from .gdbinit commands. */ void userCommandOutput(const QString& s); /** Reports output of a command issued internally by KDevelop. At the moment, stderr output from GDB and the 'log' MI channel will be also routed here. */ void internalCommandOutput(const QString& s); private Q_SLOTS: void readyReadStandardOutput(); void readyReadStandardError(); void processFinished(int exitCode, QProcess::ExitStatus exitStatus); void processErrored(QProcess::ProcessError); private: void processLine(const QByteArray& line); private: QString gdbBinary_; KProcess* process_; - GDBCommand* currentCmd_; + MI::MICommand* currentCmd_; MIParser mi_parser_; /** The unprocessed output from gdb. Output is processed as soon as we see newline. */ QByteArray buffer_; }; } #endif diff --git a/debuggers/gdb/gdbcommandqueue.cpp b/debuggers/gdb/gdbcommandqueue.cpp index 960db67ef1..fa9325e9d6 100644 --- a/debuggers/gdb/gdbcommandqueue.cpp +++ b/debuggers/gdb/gdbcommandqueue.cpp @@ -1,108 +1,108 @@ // ************************************************************************* // 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 "gdbcommandqueue.h" #include "mi/mi.h" -#include "gdbcommand.h" +#include "mi/micommand.h" using namespace GDBDebugger; using namespace MI; CommandQueue::CommandQueue() : m_tokenCounter(0) { } CommandQueue::~CommandQueue() { qDeleteAll(m_commandList); } -void CommandQueue::enqueue(GDBCommand* command) +void CommandQueue::enqueue(MICommand* command) { ++m_tokenCounter; if (m_tokenCounter == 0) m_tokenCounter = 1; command->setToken(m_tokenCounter); m_commandList.append(command); if (command->flags() & (CmdImmediately | CmdInterrupt)) ++m_immediatelyCounter; rationalizeQueue(command); } -void CommandQueue::rationalizeQueue(GDBCommand * command) +void CommandQueue::rationalizeQueue(MICommand * command) { if (command->type() >= ExecAbort && command->type() <= ExecUntil) // Changing execution location, abort any variable updates removeVariableUpdates(); } void CommandQueue::removeVariableUpdates() { - QMutableListIterator it = m_commandList; + QMutableListIterator it = m_commandList; while (it.hasNext()) { - GDBCommand* command = it.next(); + 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::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; } -GDBCommand* CommandQueue::nextCommand() +MICommand* CommandQueue::nextCommand() { if (m_commandList.isEmpty()) return nullptr; - GDBCommand* command = m_commandList.takeAt(0); + MICommand* command = m_commandList.takeAt(0); if (command->flags() & (CmdImmediately | CmdInterrupt)) --m_immediatelyCounter; return command; } diff --git a/debuggers/gdb/gdbcommandqueue.h b/debuggers/gdb/gdbcommandqueue.h index 9525301bf0..cd29d338f6 100644 --- a/debuggers/gdb/gdbcommandqueue.h +++ b/debuggers/gdb/gdbcommandqueue.h @@ -1,61 +1,64 @@ // ************************************************************************* // 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. * // * * // ************************************************************************** #ifndef GDBCOMMANDQUEUE_H #define GDBCOMMANDQUEUE_H #include #include "gdbglobal.h" -namespace GDBDebugger +namespace MI { +class MICommand; +} -class GDBCommand; +namespace GDBDebugger +{ class CommandQueue { public: CommandQueue(); ~CommandQueue(); - void enqueue(GDBCommand* command); + void enqueue(MI::MICommand* command); bool isEmpty() const; int count() const; void clear(); /// Whether the queue contains a command with CmdImmediately or CmdInterrupt flags. bool haveImmediateCommand() const; /** * Retrieve and remove the next command from the list. */ - GDBCommand* nextCommand(); + MI::MICommand* nextCommand(); private: - void rationalizeQueue(GDBCommand* command); + void rationalizeQueue(MI::MICommand* command); void removeVariableUpdates(); - QList m_commandList; + QList m_commandList; int m_immediatelyCounter = 0; uint32_t m_tokenCounter; }; } #endif // GDBCOMMANDQUEUE_H diff --git a/debuggers/gdb/gdbframestackmodel.cpp b/debuggers/gdb/gdbframestackmodel.cpp index 8a65edfdc2..84cbb408b6 100644 --- a/debuggers/gdb/gdbframestackmodel.cpp +++ b/debuggers/gdb/gdbframestackmodel.cpp @@ -1,141 +1,143 @@ /* * GDB-specific implementation of thread and frame model. * * 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 "gdbframestackmodel.h" -#include "gdbcommand.h" + +#include "mi/micommand.h" #include using namespace KDevelop; +using namespace MI; -QString getFunctionOrAddress(const MI::Value &frame) +QString getFunctionOrAddress(const Value &frame) { if (frame.hasField("func")) return frame["func"].literal(); else return frame["addr"].literal(); } -QPair getSource(const MI::Value &frame) +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; } void GdbFrameStackModel::fetchThreads() { session()->addCommand( - new GDBCommand(MI::ThreadInfo, "", + new MICommand(ThreadInfo, "", this, &GdbFrameStackModel::handleThreadInfo)); } -void GdbFrameStackModel::handleThreadInfo(const MI::ResultRecord& r) +void GdbFrameStackModel::handleThreadInfo(const ResultRecord& r) { - const MI::Value& threads = r["threads"]; + const Value& threads = r["threads"]; // Traverse GDB threads in backward order -- since GDB // reports them in backward order. We want UI to // show thread IDs in the natural order. // FIXME: make the code independent of whatever craziness // gdb might have tomorrow. QList threadsList; int gidx = threads.size()-1; for (; gidx >= 0; --gidx) { KDevelop::FrameStackModel::ThreadItem i; - const MI::Value & threadMI = threads[gidx]; + const Value & threadMI = threads[gidx]; i.nr = threadMI["id"].toInt(); if (threadMI["state"].literal() == "stopped") { i.name = getFunctionOrAddress(threads[gidx]["frame"]); } 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); } } } -struct FrameListHandler : public GDBCommandHandler +struct FrameListHandler : public MICommandHandler { FrameListHandler(GdbFrameStackModel* model, int thread, int to) : model(model), m_thread(thread) , m_to(to) {} - void handle(const MI::ResultRecord &r) override + void handle(const ResultRecord &r) override { - const MI::Value& stack = r["stack"]; + const Value& stack = r["stack"]; int first = stack[0]["level"].toInt(); QList frames; for (int i = 0; i< stack.size(); ++i) { - const MI::Value& frame = stack[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: GdbFrameStackModel* model; int m_thread; int m_to; }; void GdbFrameStackModel::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); - GDBCommand *c = new GDBCommand(MI::StackListFrames, arg, + MICommand *c = new MICommand(StackListFrames, arg, new FrameListHandler(this, threadNumber, to)); c->setThread(threadNumber); session()->addCommand(c); } diff --git a/debuggers/gdb/gdbvariable.cpp b/debuggers/gdb/gdbvariable.cpp index e85b3c753c..6dd9b84e4b 100644 --- a/debuggers/gdb/gdbvariable.cpp +++ b/debuggers/gdb/gdbvariable.cpp @@ -1,383 +1,385 @@ /* * GDB-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 "gdbvariable.h" -#include "gdbcommand.h" + #include "debugsession.h" +#include "mi/micommand.h" -#include #include +#include using namespace KDevelop; using namespace GDBDebugger; +using namespace MI; QMap GdbVariable::allVariables_; static bool hasStartedSession() { if (!ICore::self()->debugController()) return false; //happens on shutdown IDebugSession *session = ICore::self()->debugController()->currentSession(); if (!session) return false; IDebugSession::DebuggerState s = session->state(); return s != IDebugSession::NotStartedState && s != IDebugSession::EndedState; } GdbVariable::GdbVariable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) : Variable(model, parent, expression, display) { } GdbVariable::~GdbVariable() { if (!varobj_.isEmpty()) { // Delete only top-level variable objects. if (topLevel()) { if (hasStartedSession()) { IDebugSession* is = ICore::self()->debugController()->currentSession(); DebugSession* s = static_cast(is); - s->addCommand(new GDBCommand(MI::VarDelete, + s->addCommand(new MICommand(VarDelete, QString("\"%1\"").arg(varobj_))); } } allVariables_.remove(varobj_); } } GdbVariable* GdbVariable::findByVarobjName(const QString& varobjName) { if (allVariables_.count(varobjName) == 0) return 0; return allVariables_[varobjName]; } void GdbVariable::setVarobj(const QString& v) { 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 allVariables_.remove(varobj_); } varobj_ = v; allVariables_[varobj_] = this; } static int nextId = 0; -class CreateVarobjHandler : public GDBCommandHandler +class CreateVarobjHandler : public MICommandHandler { public: CreateVarobjHandler(GdbVariable *variable, QObject *callback, const char *callbackMethod) : m_variable(variable), m_callback(callback), m_callbackMethod(callbackMethod) {} - void handle(const MI::ResultRecord &r) override + void handle(const ResultRecord &r) override { if (!m_variable) return; bool hasValue = false; GdbVariable* 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 GdbVariable::attachMaybe(QObject *callback, const char *callbackMethod) { if (!varobj_.isEmpty()) return; if (hasStartedSession()) { IDebugSession* is = ICore::self()->debugController()->currentSession(); DebugSession* s = static_cast(is); s->addCommand( - new GDBCommand( - MI::VarCreate, + new MICommand( + VarCreate, QString("var%1 @ %2").arg(nextId++).arg(enquotedExpression()), new CreateVarobjHandler(this, callback, callbackMethod))); } } void GdbVariable::markAllDead() { QMap::iterator i, e; for (i = allVariables_.begin(), e = allVariables_.end(); i != e; ++i) { i.value()->varobj_.clear(); } allVariables_.clear(); } -class FetchMoreChildrenHandler : public GDBCommandHandler +class FetchMoreChildrenHandler : public MICommandHandler { public: FetchMoreChildrenHandler(GdbVariable *variable, DebugSession *session) : m_variable(variable), m_session(session), m_activeCommands(1) {} - void handle(const MI::ResultRecord &r) override + void handle(const ResultRecord &r) override { if (!m_variable) return; --m_activeCommands; GdbVariable* variable = m_variable.data(); if (r.hasField("children")) { - const MI::Value& children = r["children"]; + const Value& children = r["children"]; for (int i = 0; i < children.size(); ++i) { - const MI::Value& child = children[i]; + const Value& child = children[i]; const QString& exp = child["exp"].literal(); if (exp == "public" || exp == "protected" || exp == "private") { ++m_activeCommands; m_session->addCommand( - new GDBCommand(MI::VarListChildren, + new MICommand(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()); GdbVariable* 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; DebugSession *m_session; int m_activeCommands; }; void GdbVariable::fetchMoreChildren() { int c = childItems.size(); // FIXME: should not even try this if app is not started. // Probably need to disable open, or something if (hasStartedSession()) { IDebugSession* is = ICore::self()->debugController()->currentSession(); DebugSession* s = static_cast(is); s->addCommand( - new GDBCommand(MI::VarListChildren, + new MICommand(VarListChildren, QString("--all-values \"%1\" %2 %3").arg(varobj_) .arg( c ).arg( c + fetchStep ), // fetch from .. to .. new FetchMoreChildrenHandler(this, s))); } } -void GdbVariable::handleUpdate(const MI::Value& var) +void GdbVariable::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 MI::Value& children = var["new_children"]; + const Value& children = var["new_children"]; for (int i = 0; i < children.size(); ++i) { - const MI::Value& child = children[i]; + const Value& child = children[i]; const QString& exp = child["exp"].literal(); IDebugSession* is = ICore::self()->debugController()->currentSession(); DebugSession* s = static_cast(is); KDevelop::Variable* xvar = s->variableController()-> createVariable(model(), this, exp); GdbVariable* 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& GdbVariable::varobj() const { return varobj_; } QString GdbVariable::enquotedExpression() const { QString expr = expression(); expr.replace('"', "\\\""); expr = expr.prepend('"').append('"'); return expr; } -class SetFormatHandler : public GDBCommandHandler +class SetFormatHandler : public MICommandHandler { public: SetFormatHandler(GdbVariable *var) : m_variable(var) {} - void handle(const MI::ResultRecord &r) override + void handle(const ResultRecord &r) override { if(r.hasField("value")) m_variable.data()->setValue(r["value"].literal()); } private: QPointer m_variable; }; void GdbVariable::formatChanged() { if(childCount()) { foreach(TreeItem* item, childItems) { Q_ASSERT(dynamic_cast(item)); if( GdbVariable* var=dynamic_cast(item)) var->setFormat(format()); } } else { if (hasStartedSession()) { IDebugSession* is = ICore::self()->debugController()->currentSession(); DebugSession* s = static_cast(is); s->addCommand( - new GDBCommand(MI::VarSetFormat, + new MICommand(VarSetFormat, QString(" \"%1\" %2 ").arg(varobj_).arg(format2str(format())), new SetFormatHandler(this) ) ); } } } diff --git a/debuggers/gdb/memviewdlg.cpp b/debuggers/gdb/memviewdlg.cpp index fc8bba7b40..818f6ab2a8 100644 --- a/debuggers/gdb/memviewdlg.cpp +++ b/debuggers/gdb/memviewdlg.cpp @@ -1,530 +1,529 @@ /*************************************************************************** begin : Tue Oct 5 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org ************************************************************************** * Copyright 2006 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. * * * ***************************************************************************/ #include "memviewdlg.h" -#include "gdbcommand.h" + +#include "debugsession.h" +#include "mi/micommand.h" #include "gdbglobal.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 - -#include -#include -#include - -#include +#include -#include -#include - -#include "debugsession.h" namespace GDBDebugger { /** Container for controls that select memory range. The memory range selection is embedded into memory view widget, it's not a standalone dialog. However, we want to have easy way to hide/show all controls, so we group them in this class. */ class MemoryRangeSelector : public QWidget { public: QLineEdit* startAddressLineEdit; QLineEdit* amountLineEdit; QPushButton* okButton; QPushButton* cancelButton; MemoryRangeSelector(QWidget* parent) : QWidget(parent) { QVBoxLayout* l = new QVBoxLayout(this); // Grid layout: labels + address field QGridLayout* gl = new QGridLayout(this); l->addLayout(gl); QLabel* l1 = new QLabel(i18n("Start"), this); gl->addWidget(l1, 0, 1); startAddressLineEdit = new QLineEdit(this); gl->addWidget(startAddressLineEdit, 0, 3); QLabel* l2 = new QLabel(i18n("Amount"), this); gl->addWidget(l2, 2, 1); amountLineEdit = new QLineEdit(this); gl->addWidget(amountLineEdit, 2, 3); l->addSpacing(2); QHBoxLayout* hb = new QHBoxLayout(this); l->addLayout(hb); hb->addStretch(); okButton = new QPushButton(i18n("OK"), this); hb->addWidget(okButton); cancelButton = new QPushButton(i18n("Cancel"), this); hb->addWidget(cancelButton); l->addSpacing(2); setLayout(l); connect(startAddressLineEdit, SIGNAL(returnPressed()), okButton, SLOT(animateClick())); connect(amountLineEdit, SIGNAL(returnPressed()), okButton, SLOT(animateClick())); } }; MemoryView::MemoryView(QWidget* parent) : QWidget(parent), // New memory view can be created only when debugger is active, // so don't set s_appNotStarted here. khexedit2_widget(0), amount_(0), data_(0), debuggerState_(0) { setWindowTitle(i18n("Memory view")); emit captionChanged(windowTitle()); initWidget(); if (isOk()) slotEnableOrDisable(); connect(KDevelop::ICore::self()->debugController(), SIGNAL(currentSessionChanged(KDevelop::IDebugSession*)), SLOT(currentSessionChanged(KDevelop::IDebugSession*))); } void MemoryView::currentSessionChanged(KDevelop::IDebugSession* s) { DebugSession *session = qobject_cast(s); if (!session) return; connect(session, SIGNAL(gdbStateChanged(DBGStateFlags,DBGStateFlags)), SLOT(slotStateChanged(DBGStateFlags,DBGStateFlags))); } void MemoryView::slotStateChanged(DBGStateFlags oldState, DBGStateFlags newState) { Q_UNUSED(oldState); debuggerStateChanged(newState); } void MemoryView::initWidget() { QVBoxLayout *l = new QVBoxLayout(this); khexedit2_widget = KHE::createBytesEditWidget(this); if (!khexedit2_widget) { QTextEdit* edit = new QTextEdit(this); l->addWidget(edit); edit->setText( "

Not Available

" "

Could not open a KHexEdit2 interface. " "Installing Okteta should provide the required components.

"); return; } KHE::BytesEditInterface *bytesEdit = KHE::bytesEditInterface(khexedit2_widget); if (bytesEdit) { bytesEdit->setReadOnly(false); bytesEdit->setOverwriteMode(true); bytesEdit->setOverwriteOnly(true); bytesEdit->setAutoDelete(false); } KHE::ValueColumnInterface *valueColumn = KHE::valueColumnInterface(khexedit2_widget); if (valueColumn) { valueColumn->setCoding(KHE::ValueColumnInterface::HexadecimalCoding); valueColumn->setNoOfGroupedBytes(4); valueColumn->setByteSpacingWidth(2); valueColumn->setGroupSpacingWidth(12); valueColumn->setResizeStyle(KHE::ValueColumnInterface::LockGrouping); } KHE::CharColumnInterface *charColumn = KHE::charColumnInterface(khexedit2_widget); if(charColumn) { charColumn->setShowUnprintable(false); charColumn->setSubstituteChar('*'); } rangeSelector_ = new MemoryRangeSelector(this); l->addWidget(rangeSelector_); connect(rangeSelector_->okButton, SIGNAL(clicked()), this, SLOT(slotChangeMemoryRange())); connect(rangeSelector_->cancelButton, SIGNAL(clicked()), this, SLOT(slotHideRangeDialog())); connect(rangeSelector_->startAddressLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotEnableOrDisable())); connect(rangeSelector_->amountLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotEnableOrDisable())); l->addWidget(khexedit2_widget); } void MemoryView::debuggerStateChanged(DBGStateFlags state) { if (isOk()) { debuggerState_ = state; slotEnableOrDisable(); } } void MemoryView::slotHideRangeDialog() { rangeSelector_->hide(); } void MemoryView::slotChangeMemoryRange() { DebugSession *session = qobject_cast( KDevelop::ICore::self()->debugController()->currentSession()); if (!session) return; session->addCommand(new ExpressionValueCommand( rangeSelector_->amountLineEdit->text(), this, &MemoryView::sizeComputed)); } void MemoryView::sizeComputed(const QString& size) { DebugSession *session = qobject_cast( KDevelop::ICore::self()->debugController()->currentSession()); if (!session) return; session->addCommand(new GDBCommand(MI::DataReadMemory, QString("%1 x 1 1 %2") .arg(rangeSelector_->startAddressLineEdit->text()) .arg(size), this, &MemoryView::memoryRead)); } void MemoryView::memoryRead(const MI::ResultRecord& r) { const MI::Value& content = r["memory"][0]["data"]; bool startStringConverted; start_ = r["addr"].literal().toULongLong(&startStringConverted, 16); amount_ = content.size(); startAsString_ = rangeSelector_->startAddressLineEdit->text(); amountAsString_ = rangeSelector_->amountLineEdit->text(); setWindowTitle(i18np("%2 (1 byte)","%2 (%1 bytes)",amount_,startAsString_)); emit captionChanged(windowTitle()); KHE::BytesEditInterface* bytesEditor = KHE::bytesEditInterface(khexedit2_widget); bytesEditor->setData(this->data_, 0); delete[] this->data_; this->data_ = new char[amount_]; for(int i = 0; i < content.size(); ++i) { this->data_[i] = content[i].literal().toInt(0, 16); } bytesEditor->setData(this->data_, amount_); slotHideRangeDialog(); } void MemoryView::memoryEdited(int start, int end) { DebugSession *session = qobject_cast( KDevelop::ICore::self()->debugController()->currentSession()); if (!session) return; for(int i = start; i <= end; ++i) { session->addCommand(new GDBCommand(MI::GdbSet, QString("*(char*)(%1 + %2) = %3") .arg(start_) .arg(i) .arg(QString::number(data_[i])))); } } void MemoryView::contextMenuEvent(QContextMenuEvent *e) { if (!isOk()) return; KHE::BytesEditInterface *bytesEdit = KHE::bytesEditInterface(khexedit2_widget); KHE::ValueColumnInterface *valueColumn = KHE::valueColumnInterface(khexedit2_widget); QMenu menu; bool app_running = !(debuggerState_ & s_appNotStarted); QAction* reload = menu.addAction(i18n("&Reload")); reload->setIcon(QIcon::fromTheme("view-refresh")); reload->setEnabled(app_running && amount_ != 0); QActionGroup *formatGroup = NULL; QActionGroup *groupingGroup = NULL; if (valueColumn) { // make Format menu with action group QMenu *formatMenu = new QMenu(i18n("&Format")); formatGroup = new QActionGroup(formatMenu); QAction *binary = formatGroup->addAction(i18n("&Binary")); binary->setData(KHE::ValueColumnInterface::BinaryCoding); binary->setShortcut(Qt::Key_B); formatMenu->addAction(binary); QAction *octal = formatGroup->addAction(i18n("&Octal")); octal->setData(KHE::ValueColumnInterface::OctalCoding); octal->setShortcut(Qt::Key_O); formatMenu->addAction(octal); QAction *decimal = formatGroup->addAction(i18n("&Decimal")); decimal->setData(KHE::ValueColumnInterface::DecimalCoding); decimal->setShortcut(Qt::Key_D); formatMenu->addAction(decimal); QAction *hex = formatGroup->addAction(i18n("&Hexadecimal")); hex->setData(KHE::ValueColumnInterface::HexadecimalCoding); hex->setShortcut(Qt::Key_H); formatMenu->addAction(hex); foreach(QAction* act, formatGroup->actions()) { act->setCheckable(true); act->setChecked(act->data().toInt() == valueColumn->coding()); act->setShortcutContext(Qt::WidgetWithChildrenShortcut); } menu.addMenu(formatMenu); // make Grouping menu with action group QMenu *groupingMenu = new QMenu(i18n("&Grouping")); groupingGroup = new QActionGroup(groupingMenu); QAction *group0 = groupingGroup->addAction(i18n("&0")); group0->setData(0); group0->setShortcut(Qt::Key_0); groupingMenu->addAction(group0); QAction *group1 = groupingGroup->addAction(i18n("&1")); group1->setData(1); group1->setShortcut(Qt::Key_1); groupingMenu->addAction(group1); QAction *group2 = groupingGroup->addAction(i18n("&2")); group2->setData(2); group2->setShortcut(Qt::Key_2); groupingMenu->addAction(group2); QAction *group4 = groupingGroup->addAction(i18n("&4")); group4->setData(4); group4->setShortcut(Qt::Key_4); groupingMenu->addAction(group4); QAction *group8 = groupingGroup->addAction(i18n("&8")); group8->setData(8); group8->setShortcut(Qt::Key_8); groupingMenu->addAction(group8); QAction *group16 = groupingGroup->addAction(i18n("1&6")); group16->setData(16); group16->setShortcut(Qt::Key_6); groupingMenu->addAction(group16); foreach(QAction* act, groupingGroup->actions()) { act->setCheckable(true); act->setChecked(act->data().toInt() == valueColumn->noOfGroupedBytes()); act->setShortcutContext(Qt::WidgetWithChildrenShortcut); } menu.addMenu(groupingMenu); } QAction* write = menu.addAction(i18n("Write changes")); write->setIcon(QIcon::fromTheme("document-save")); write->setEnabled(app_running && bytesEdit && bytesEdit->isModified()); QAction* range = menu.addAction(i18n("Change memory range")); range->setEnabled(app_running && !rangeSelector_->isVisible()); range->setIcon(QIcon::fromTheme("document-edit")); QAction* close = menu.addAction(i18n("Close this view")); close->setIcon(QIcon::fromTheme("window-close")); QAction* result = menu.exec(e->globalPos()); if (result == reload) { // We use numeric start_ and amount_ stored in this, // not textual startAsString_ and amountAsString_, // because program position might have changes and expressions // are no longer valid. DebugSession *session = qobject_cast( KDevelop::ICore::self()->debugController()->currentSession()); if (session) { session->addCommand(new GDBCommand(MI::DataReadMemory, QString("%1 x 1 1 %2").arg(start_).arg(amount_), this, &MemoryView::memoryRead)); } } if (result && formatGroup && formatGroup == result->actionGroup()) valueColumn->setCoding((KHE::ValueColumnInterface::KCoding)result->data().toInt()); if (result && groupingGroup && groupingGroup == result->actionGroup()) valueColumn->setNoOfGroupedBytes(result->data().toInt()); if (result == write) { memoryEdited(0, amount_); bytesEdit->setModified(false); } if (result == range) { rangeSelector_->startAddressLineEdit->setText(startAsString_); rangeSelector_->amountLineEdit->setText(amountAsString_); rangeSelector_->show(); rangeSelector_->startAddressLineEdit->setFocus(); } if (result == close) delete this; } bool MemoryView::isOk() const { return khexedit2_widget; } void MemoryView::slotEnableOrDisable() { bool app_started = !(debuggerState_ & s_appNotStarted); bool enabled_ = app_started && !rangeSelector_->startAddressLineEdit->text().isEmpty() && !rangeSelector_->amountLineEdit->text().isEmpty(); rangeSelector_->okButton->setEnabled(enabled_); } MemoryViewerWidget::MemoryViewerWidget(CppDebuggerPlugin* /*plugin*/, QWidget* parent) : QWidget(parent) { setWindowIcon(QIcon::fromTheme("server-database", windowIcon())); setWindowTitle(i18n("Memory viewer")); QAction * newMemoryViewerAction = new QAction(this); newMemoryViewerAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); newMemoryViewerAction->setText(i18n("New memory viewer")); newMemoryViewerAction->setToolTip(i18nc("@info:tooltip", "Open a new memory viewer.")); newMemoryViewerAction->setIcon(QIcon::fromTheme("window-new")); connect(newMemoryViewerAction, SIGNAL(triggered(bool)), this, SLOT(slotAddMemoryView())); addAction(newMemoryViewerAction); QVBoxLayout *l = new QVBoxLayout(this); toolBox_ = new QToolBox(this); l->addWidget(toolBox_); // Start with one empty memory view. slotAddMemoryView(); } void MemoryViewerWidget::slotAddMemoryView() { MemoryView* widget = new MemoryView(this); toolBox_->addItem(widget, widget->windowTitle()); toolBox_->setCurrentIndex(toolBox_->indexOf(widget)); memoryViews_.push_back(widget); connect(widget, SIGNAL(captionChanged(QString)), this, SLOT(slotChildCaptionChanged(QString))); connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(slotChildDestroyed(QObject*))); } void MemoryViewerWidget::slotChildCaptionChanged(const QString& caption) { const QWidget* s = static_cast(sender()); QWidget* ncs = const_cast(s); QString cap = caption; // Prevent intepreting '&' as accelerator specifier. cap.replace('&', "&&"); toolBox_->setItemText(toolBox_->indexOf(ncs), cap); } void MemoryViewerWidget::slotChildDestroyed(QObject* child) { QList::iterator i, e; for(i = memoryViews_.begin(), e = memoryViews_.end(); i != e; ++i) { if (*i == child) { memoryViews_.erase(i); break; } } } } diff --git a/debuggers/gdb/registers/registercontroller.cpp b/debuggers/gdb/registers/registercontroller.cpp index 7a4f28f768..07d865ee09 100644 --- a/debuggers/gdb/registers/registercontroller.cpp +++ b/debuggers/gdb/registers/registercontroller.cpp @@ -1,410 +1,407 @@ /* * Class to fetch/change/send registers to the debugger. * 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 "registercontroller.h" -#include -#include - - +#include "converters.h" #include "mi/mi.h" +#include "mi/micommand.h" #include "../debugsession.h" -#include "../gdbcommand.h" #include "../debug.h" -#include "converters.h" -namespace GDBDebugger -{ +#include +#include + +using namespace MI; +using namespace GDBDebugger; void IRegisterController::setSession(DebugSession* debugSession) { m_debugSession = debugSession; } void IRegisterController::updateRegisters(const GroupsName& group) { if (!m_debugSession || m_debugSession->stateIsOn(s_dbgNotStarted | s_shuttingDown)) { return; } if (m_pendingGroups.contains(group)) { qCDebug(DEBUGGERGDB) << "Already updating " << group.name(); return; } if (group.name().isEmpty()) { foreach (const GroupsName & g, namesOfRegisterGroups()) { IRegisterController::updateRegisters(g); } return; } else { qCDebug(DEBUGGERGDB) << "Updating: " << group.name(); m_pendingGroups << group; } QString registers; Format currentFormat = formats(group).first(); switch (currentFormat) { case Binary: registers = "t "; break; case Octal: registers = "o "; break; case Decimal : registers = "d "; break; case Hexadecimal: registers = "x "; break; case Raw: registers = "r "; break; case Unsigned: registers = "u "; break; default: break; } //float point registers have only two reasonable format. Mode currentMode = modes(group).first(); if (((currentMode >= v4_float && currentMode <= v2_double) || (currentMode >= f32 && currentMode <= f64) || group.type() == floatPoint) && currentFormat != Raw) { registers = "N "; } if (group.type() == flag) { registers += numberForName(group.flagName()); } else { foreach (const QString & name, registerNamesForGroup(group)) { registers += numberForName(name) + ' '; } } //Not initialized yet. They'll be updated afterwards. if (registers.contains("-1")) { qCDebug(DEBUGGERGDB) << "Will update later"; m_pendingGroups.clear(); return; } - void (IRegisterController::* handler)(const MI::ResultRecord&); + void (IRegisterController::* handler)(const ResultRecord&); if (group.type() == structured && currentFormat != Raw) { handler = &IRegisterController::structuredRegistersHandler; } else { handler = &IRegisterController::generalRegistersHandler; } - m_debugSession->addCommand(new GDBCommand(MI::DataListRegisterValues, registers, this, handler)); + m_debugSession->addCommand(new MICommand(DataListRegisterValues, registers, this, handler)); } -void IRegisterController::registerNamesHandler(const MI::ResultRecord& r) +void IRegisterController::registerNamesHandler(const ResultRecord& r) { - const MI::Value& names = r["register-names"]; + const Value& names = r["register-names"]; m_rawRegisterNames.clear(); for (int i = 0; i < names.size(); ++i) { - const MI::Value& entry = names[i]; + const Value& entry = names[i]; m_rawRegisterNames.push_back(entry.literal()); } //When here probably request for updating registers was sent, but m_rawRegisterNames were not initialized yet, so it wasn't successful. Update everything once again. updateRegisters(); } -void IRegisterController::generalRegistersHandler(const MI::ResultRecord& r) +void IRegisterController::generalRegistersHandler(const ResultRecord& r) { Q_ASSERT(!m_rawRegisterNames.isEmpty()); QString registerName; - const MI::Value& values = r["register-values"]; + const Value& values = r["register-values"]; for (int i = 0; i < values.size(); ++i) { - const MI::Value& entry = values[i]; + const Value& entry = values[i]; int number = entry["number"].literal().toInt(); Q_ASSERT(m_rawRegisterNames.size() > number); if (!m_rawRegisterNames[number].isEmpty()) { if (registerName.isEmpty()) { registerName = m_rawRegisterNames[number]; } const QString value = entry["value"].literal(); m_registers.insert(m_rawRegisterNames[number], value); } } GroupsName group = groupForRegisterName(registerName); if (m_pendingGroups.contains(group)) { emit registersChanged(registersFromGroup(group)); m_pendingGroups.remove(m_pendingGroups.indexOf(group)); } } void IRegisterController::setRegisterValue(const Register& reg) { Q_ASSERT(!m_registers.isEmpty()); const GroupsName group = groupForRegisterName(reg.name); if (!group.name().isEmpty()) { setRegisterValueForGroup(group, reg); } } QString IRegisterController::registerValue(const QString& name) const { QString value; if (!name.isEmpty()) { if (m_registers.contains(name)) { value = m_registers.value(name); } } return value; } bool IRegisterController::initializeRegisters() { if (!m_debugSession || m_debugSession->stateIsOn(s_dbgNotStarted | s_shuttingDown)) { return false; } m_debugSession->addCommand( - new GDBCommand(MI::DataListRegisterNames, "", this, &IRegisterController::registerNamesHandler)); + new MICommand(DataListRegisterNames, "", this, &IRegisterController::registerNamesHandler)); return true; } GroupsName IRegisterController::groupForRegisterName(const QString& name) const { foreach (const GroupsName & group, namesOfRegisterGroups()) { const QStringList registersInGroup = registerNamesForGroup(group); if (group.flagName() == name) { return group; } foreach (const QString & n, registersInGroup) { if (n == name) { return group; } } } return GroupsName(); } void IRegisterController::updateValuesForRegisters(RegistersGroup* registers) const { Q_ASSERT(!m_registers.isEmpty()); for (int i = 0; i < registers->registers.size(); i++) { if (m_registers.contains(registers->registers[i].name)) { registers->registers[i].value = m_registers.value(registers->registers[i].name); } } } void IRegisterController::setFlagRegister(const Register& reg, const FlagRegister& flag) { quint32 flagsValue = registerValue(flag.registerName).toUInt(0, 16); const int idx = flag.flags.indexOf(reg.name); if (idx != -1) { flagsValue ^= static_cast(qPow(2, flag.bits[idx].toUInt())); setGeneralRegister(Register(flag.registerName, QString("0x%1").arg(flagsValue, 0, 16)), flag.groupName); } else { updateRegisters(flag.groupName); qCDebug(DEBUGGERGDB) << reg.name << ' ' << reg.value << "is incorrect flag name/value"; } } void IRegisterController::setGeneralRegister(const Register& reg, const GroupsName& group) { if (!m_debugSession || m_debugSession->stateIsOn(s_dbgNotStarted | s_shuttingDown)) { return; } const QString command = QString("set var $%1=%2").arg(reg.name).arg(reg.value); qCDebug(DEBUGGERGDB) << "Setting register: " << command; - m_debugSession->addCommand(new GDBCommand(MI::NonMI, command)); + m_debugSession->addCommand(new MICommand(NonMI, command)); updateRegisters(group); } IRegisterController::IRegisterController(DebugSession* debugSession, QObject* parent) : QObject(parent), m_debugSession(debugSession) {} IRegisterController::~IRegisterController() {} void IRegisterController::updateFlagValues(RegistersGroup* flagsGroup, const FlagRegister& flagRegister) const { const quint32 flagsValue = registerValue(flagRegister.registerName).toUInt(0, 16); for (int idx = 0; idx < flagRegister.flags.count(); idx++) { flagsGroup->registers[idx].value = ((flagsValue >> flagRegister.bits[idx].toInt()) & 1) ? "1" : "0"; } } QVector IRegisterController::formats(const GroupsName& group) { int idx = -1; foreach (const GroupsName & g, namesOfRegisterGroups()) { if (g == group) { idx = g.index(); } } Q_ASSERT(idx != -1); return m_formatsModes[idx].formats; } GroupsName IRegisterController::createGroupName(const QString& name, int idx, RegisterType t, const QString flag) const { return GroupsName(name, idx, t, flag); } void IRegisterController::setFormat(Format f, const GroupsName& group) { foreach (const GroupsName & g, namesOfRegisterGroups()) { if (g == group) { int i = m_formatsModes[g.index()].formats.indexOf(f); if (i != -1) { m_formatsModes[g.index()].formats.remove(i); m_formatsModes[g.index()].formats.prepend(f); } } } } QString IRegisterController::numberForName(const QString& name) const { //Requests for number come in order(if the previous was, let's say 10, then most likely the next one will be 11) static int previousNumber = -1; if (m_rawRegisterNames.isEmpty()) { previousNumber = -1; return QString::number(previousNumber); } if (previousNumber != -1 && m_rawRegisterNames.size() > ++previousNumber) { if (m_rawRegisterNames[previousNumber] == name) { return QString::number(previousNumber); } } for (int number = 0; number < m_rawRegisterNames.size(); number++) { if (name == m_rawRegisterNames[number]) { previousNumber = number; return QString::number(number); } } previousNumber = -1; return QString::number(previousNumber); } void IRegisterController::setStructuredRegister(const Register& reg, const GroupsName& group) { Register r = reg; r.value = r.value.trimmed(); r.value.replace(' ', ','); if (r.value.contains(',')) { r.value.append('}'); r.value.prepend('{'); } r.name += '.' + Converters::modeToString(m_formatsModes[group.index()].modes.first()); setGeneralRegister(r, group); } -void IRegisterController::structuredRegistersHandler(const MI::ResultRecord& r) +void IRegisterController::structuredRegistersHandler(const ResultRecord& r) { //Parsing records in format like: //{u8 = {0, 0, 128, 146, 0, 48, 197, 65}, u16 = {0, 37504, 12288, 16837}, u32 = {2457862144, 1103441920}, u64 = 4739246961893310464, f32 = {-8.07793567e-28, 24.6484375}, f64 = 710934821} //{u8 = {0 }, u16 = {0, 0, 0, 0, 0, 0, 0, 0}, u32 = {0, 0, 0, 0}, u64 = {0, 0}, f32 = {0, 0, 0, 0}, f64 = {0, 0}} QRegExp rx("^\\s*=\\s*\\{(.*)\\}"); rx.setMinimal(true); QString registerName; Mode currentMode = LAST_MODE; GroupsName group; - const MI::Value& values = r["register-values"]; + const Value& values = r["register-values"]; Q_ASSERT(!m_rawRegisterNames.isEmpty()); for (int i = 0; i < values.size(); ++i) { - const MI::Value& entry = values[i]; + const Value& entry = values[i]; int number = entry["number"].literal().toInt(); registerName = m_rawRegisterNames[number]; if (currentMode == LAST_MODE) { group = groupForRegisterName(registerName); currentMode = modes(group).first(); } QString record = entry["value"].literal(); int start = record.indexOf(Converters::modeToString(currentMode)); Q_ASSERT(start != -1); start += Converters::modeToString(currentMode).size(); QString value = record.right(record.size() - start); int idx = rx.indexIn(value); value = rx.cap(1); if (idx == -1) { //if here then value without braces: u64 = 4739246961893310464, f32 = {-8.07793567e-28, 24.6484375}, f64 = 710934821} QRegExp rx2("=\\s+(.*)(\\}|,)"); rx2.setMinimal(true); rx2.indexIn(record, start); value = rx2.cap(1); } value = value.trimmed().remove(','); m_registers.insert(registerName, value); } if (m_pendingGroups.contains(group)) { emit registersChanged(registersFromGroup(group)); m_pendingGroups.remove(m_pendingGroups.indexOf(group)); } } QVector< Mode > IRegisterController::modes(const GroupsName& group) { int idx = -1; foreach (const GroupsName & g, namesOfRegisterGroups()) { if (g == group) { idx = g.index(); } } Q_ASSERT(idx != -1); return m_formatsModes[idx].modes; } void IRegisterController::setMode(Mode m, const GroupsName& group) { foreach (const GroupsName & g, namesOfRegisterGroups()) { if (g == group) { int i = m_formatsModes[g.index()].modes.indexOf(m); if (i != -1) { m_formatsModes[g.index()].modes.remove(i); m_formatsModes[g.index()].modes.prepend(m); } } } } - -} diff --git a/debuggers/gdb/registers/registersmanager.cpp b/debuggers/gdb/registers/registersmanager.cpp index 5662575b36..a33b36bd3c 100644 --- a/debuggers/gdb/registers/registersmanager.cpp +++ b/debuggers/gdb/registers/registersmanager.cpp @@ -1,176 +1,171 @@ /* * Creates RegistersView and RegisterController based on current architecture. * 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 "registersmanager.h" -#include "registercontroller_x86.h" #include "registercontroller_arm.h" +#include "registercontroller_x86.h" #include "registersview.h" +#include "mi/micommand.h" #include "modelsmanager.h" - -#include "../gdbcommand.h" #include "../debugsession.h" #include "../debug.h" - - -namespace GDBDebugger -{ +using namespace MI; +using namespace GDBDebugger; void ArchitectureParser::parseArchitecture() { Architecture arch = other; foreach (const QString & reg, m_registerNames) { if (reg == "rax") { arch = x86_64; break; } else if (reg == "r0") { arch = arm; break; } else if (reg == "eax") { arch = x86; //we don't break because x86_64 contains eax too. } } emit architectureParsed(arch); } -void ArchitectureParser::registerNamesHandler(const MI::ResultRecord& r) +void ArchitectureParser::registerNamesHandler(const ResultRecord& r) { - const MI::Value& names = r["register-names"]; + const Value& names = r["register-names"]; m_registerNames.clear(); for (int i = 0; i < names.size(); ++i) { - const MI::Value& entry = names[i]; + const Value& entry = names[i]; if (!entry.literal().isEmpty()) { m_registerNames << entry.literal(); } } parseArchitecture(); } void ArchitectureParser::determineArchitecture(DebugSession* debugSession) { if (!debugSession || debugSession->stateIsOn(s_dbgNotStarted | s_shuttingDown)) { return; } debugSession->addCommand( - new GDBCommand(MI::DataListRegisterNames, "", this, &ArchitectureParser::registerNamesHandler)); + new MICommand(DataListRegisterNames, "", this, &ArchitectureParser::registerNamesHandler)); } RegistersManager::RegistersManager(QWidget* parent) : QObject(parent), m_registersView(new RegistersView(parent)), m_registerController(0), m_architectureParser(new ArchitectureParser(this)), m_debugSession(0), m_modelsManager(new ModelsManager(this)), m_currentArchitecture(undefined), m_needToCheckArch(false) { connect(m_architectureParser, &ArchitectureParser::architectureParsed, this, &RegistersManager::architectureParsedSlot); m_registersView->setModel(m_modelsManager); setController(0); } void RegistersManager::architectureParsedSlot(Architecture arch) { qCDebug(DEBUGGERGDB) << " Current controller: " << m_registerController << "Current arch " << m_currentArchitecture; if (m_registerController || m_currentArchitecture != undefined) { return; } switch (arch) { case x86: m_registerController.reset(new RegisterController_x86(m_debugSession)) ; qCDebug(DEBUGGERGDB) << "Found x86 architecture"; break; case x86_64: m_registerController.reset(new RegisterController_x86_64(m_debugSession)); qCDebug(DEBUGGERGDB) << "Found x86_64 architecture"; break; case arm: m_registerController.reset(new RegisterController_Arm(m_debugSession)); qCDebug(DEBUGGERGDB) << "Found Arm architecture"; break; default: m_registerController.reset(); qWarning() << "Unsupported architecture. Registers won't be available."; break; } m_currentArchitecture = arch; setController(m_registerController.data()); if (m_registerController) { updateRegisters(); } } void RegistersManager::setSession(DebugSession* debugSession) { qCDebug(DEBUGGERGDB) << "Change session " << debugSession; m_debugSession = debugSession; if (m_registerController) { m_registerController->setSession(debugSession); } if (!m_debugSession) { qCDebug(DEBUGGERGDB) << "Will reparse arch"; m_needToCheckArch = true; setController(0); } } void RegistersManager::updateRegisters() { if (!m_debugSession || m_debugSession->stateIsOn(s_dbgNotStarted | s_shuttingDown)) { return; } qCDebug(DEBUGGERGDB) << "Updating registers"; if (m_needToCheckArch) { m_needToCheckArch = false; m_currentArchitecture = undefined; setController(0); } if (m_currentArchitecture == undefined) { m_architectureParser->determineArchitecture(m_debugSession); } if (m_registerController) { m_registersView->updateRegisters(); } else { qCDebug(DEBUGGERGDB) << "No registerController, yet?"; } } ArchitectureParser::ArchitectureParser(QObject* parent) : QObject(parent) {} void RegistersManager::setController(IRegisterController* c) { m_registerController.reset(c); m_modelsManager->setController(c); m_registersView->enable(c ? true : false); } - -} diff --git a/debuggers/gdb/unittests/test_gdb.cpp b/debuggers/gdb/unittests/test_gdb.cpp index 89b16d2f49..33a1f4530f 100644 --- a/debuggers/gdb/unittests/test_gdb.cpp +++ b/debuggers/gdb/unittests/test_gdb.cpp @@ -1,2033 +1,2033 @@ /* 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 -#include -#include -#include -#include -#include -#include - -#include -#include -#include +#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 "gdbcommand.h" -#include "debugsession.h" -#include "gdbframestackmodel.h" -#include -#include +#include +#include +#include + +#include +#include +#include +#include #include +#include +#include +#include using KDevelop::AutoTestShell; namespace GDBDebugger { 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 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; } private: KConfigGroup cfg; KConfig *c; }; class TestFrameStackModel : public KDevelop::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() { setTesting(true); 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) \ compareData((index), (expected), __FILE__, __LINE__) void compareData(QModelIndex index, QString expected, const char *file, int line) { QString s = index.model()->data(index, Qt::DisplayRole).toString(); if (s != expected) { QFAIL(qPrintable(QString("'%0' didn't match expected '%1' in %2:%3").arg(s).arg(expected).arg(file).arg(line))); } } 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, SIGNAL(applicationStandardOutputLines(QStringList))); TestLaunchConfiguration cfg; session->startProgram(&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::testBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startProgram(&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->startProgram(&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->startProgram(&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 startProgram breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QCOMPARE(breakpoints()->rowCount(), 1); breakpoints()->removeRow(0); QCOMPARE(breakpoints()->rowCount(), 0); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); session->startProgram(&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->startProgram(&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; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(breakpoints()->rowCount(), 1); session->startProgram(&cfg, m_iface); //insert custom command as user might do it using GDB console - session->addCommand(new UserCommand(MI::NonMI, "break "+debugeeFileName+":28")); + session->addCommand(new MI::UserCommand(MI::NonMI, "break "+debugeeFileName+":28")); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(100); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); 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::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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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); 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, SIGNAL(showStepInSource(QUrl,int,QString))); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); session->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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(GDBDebugger::remoteGdbRunEntry, QUrl::fromLocalFile(findSourceFile("gdb_script_empty"))); QVERIFY(session->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stopDebugger(); QTest::qWait(300); } void GdbTest::testVariablesStartSecondSession() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, 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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->slotUserGDBCmd("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->startProgram(&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->startProgram(&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->startProgram(&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(GDBDebugger::remoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile("debugee.cpp"), 31); QVERIFY(session->startProgram(&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(GDBDebugger::remoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); for (int i = 0; i < 2; ++i) { TestDebugSession* session = new TestDebugSession; QVERIFY(session->startProgram(&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(GDBDebugger::remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startProgram(&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(GDBDebugger::remoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); grp.writeEntry(GDBDebugger::remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startProgram(&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(GDBDebugger::remoteGdbShellEntry, QUrl::fromLocalFile(shellScript.fileName()+"-copy")); grp.writeEntry(GDBDebugger::remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startProgram(&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(GDBDebugger::remoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); grp.writeEntry(GDBDebugger::remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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(new GDBCommand(MI::NonMI, "catch throw")); + session->addCommand(new MI::MICommand(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() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeethreads")); QString fileName = findSourceFile("debugeethreads.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QSignalSpy outputSpy(session, SIGNAL(gdbUserCommandStdout(QString))); session->addCommand( - new UserCommand(MI::ThreadInfo,"")); - session->addCommand(new UserCommand(MI::StackListLocals, QLatin1String("0"))); + 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() { 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\"}"); 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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&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->startProgram(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); - session->addCommand(new GDBCommand(MI::NonMI, "rbreak .*aPl.*B")); + session->addCommand(new MI::MICommand(MI::NonMI, "rbreak .*aPl.*B")); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 3); - session->addCommand(new GDBCommand(MI::BreakDelete, "")); + session->addCommand(new MI::MICommand(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->startProgram(&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->startProgram(&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->startProgram(&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(GDBDebugger::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(); while (s.data()->state() != state || (waitForIdle && s->stateIsOn(s_dbgBusy))) { if (stopWatch.elapsed() > 5000) { qWarning() << "current state" << s.data()->state() << "waiting for" << state; QTest::qFail(qPrintable(QString("Timeout before reaching state %0").arg(state)), file, line); return false; } QTest::qWait(20); if (!s) { if (state == DebugSession::EndedState) break; QTest::qFail(qPrintable(QString("Session ended before reaching state %0").arg(state)), file, line); return false; } } if (!waitForIdle && state != DebugSession::EndedState) { // legacy behavior for tests that implicitly may require waiting for idle, // but which were written before waitForIdle was added QTest::qWait(100); } qDebug() << "Reached state " << state << " in " << file << ':' << line; return true; } } QTEST_MAIN(GDBDebugger::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 75764c6bdb..a0029f68db 100644 --- a/debuggers/gdb/unittests/test_gdb.h +++ b/debuggers/gdb/unittests/test_gdb.h @@ -1,109 +1,109 @@ /* 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 +#include namespace KDevelop { class TestCore; } namespace GDBDebugger { class GdbTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void init(); void testStdOut(); 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(GDBDebugger::DebugSession *session, KDevelop::IDebugSession::DebuggerState state, const char *file, int line, bool waitForIdle = false); IExecutePlugin* m_iface; }; } #endif // GDBTEST_H diff --git a/debuggers/gdb/variablecontroller.cpp b/debuggers/gdb/variablecontroller.cpp index e401961680..b925dfd403 100644 --- a/debuggers/gdb/variablecontroller.cpp +++ b/debuggers/gdb/variablecontroller.cpp @@ -1,257 +1,258 @@ /* * 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 "variablecontroller.h" -#include +#include "debug.h" +#include "debugsession.h" +#include "gdbvariable.h" +#include "mi/mi.h" +#include "mi/micommand.h" +#include "stringhelpers.h" + +#include #include +#include #include #include -#include - -#include "mi/mi.h" -#include "gdbcommand.h" -#include "debugsession.h" -#include "stringhelpers.h" -#include "gdbvariable.h" -#include "debug.h" #include using namespace GDBDebugger; using namespace KDevelop; +using namespace MI; VariableController::VariableController(DebugSession* parent) : KDevelop::IVariableController(parent) { Q_ASSERT(parent); connect(parent, &DebugSession::programStopped, this, &VariableController::programStopped); connect(parent, &DebugSession::stateChanged, this, &VariableController::stateChanged); } DebugSession *VariableController::debugSession() const { return static_cast(const_cast(QObject::parent())); } -void VariableController::programStopped(const MI::AsyncRecord& r) +void VariableController::programStopped(const AsyncRecord& r) { if (debugSession()->stateIsOn(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 VariableController::update() { qCDebug(DEBUGGERGDB) << autoUpdate(); if (autoUpdate() & UpdateWatches) { variableCollection()->watches()->reinstall(); } if (autoUpdate() & UpdateLocals) { updateLocals(); } if ((autoUpdate() & UpdateLocals) || ((autoUpdate() & UpdateWatches) && variableCollection()->watches()->childCount() > 0)) { debugSession()->addCommand( - new GDBCommand(MI::VarUpdate, "--all-values *", this, + new MICommand(VarUpdate, "--all-values *", this, &VariableController::handleVarUpdate)); } } -void VariableController::handleVarUpdate(const MI::ResultRecord& r) +void VariableController::handleVarUpdate(const ResultRecord& r) { - const MI::Value& changed = r["changelist"]; + const Value& changed = r["changelist"]; for (int i = 0; i < changed.size(); ++i) { - const MI::Value& var = changed[i]; + const Value& var = changed[i]; GdbVariable* v = GdbVariable::findByVarobjName(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 GDBCommandHandler +class StackListArgumentsHandler : public MICommandHandler { public: StackListArgumentsHandler(QStringList localsName) : m_localsName(localsName) {} - void handle(const MI::ResultRecord &r) override + void handle(const ResultRecord &r) override { if (!KDevelop::ICore::self()->debugController()) return; //happens on shutdown // FIXME: handle error. - const MI::Value& locals = r["stack-args"][0]["args"]; + 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 GDBCommandHandler +class StackListLocalsHandler : public MICommandHandler { public: StackListLocalsHandler(DebugSession *session) : m_session(session) {} - void handle(const MI::ResultRecord &r) override + void handle(const ResultRecord &r) override { // FIXME: handle error. - const MI::Value& locals = r["locals"]; + const Value& locals = r["locals"]; QStringList localsName; for (int i = 0; i < locals.size(); i++) { - const MI::Value& var = locals[i]; + const Value& var = locals[i]; localsName << var["name"].literal(); } int frame = m_session->frameStackModel()->currentFrame(); m_session->addCommand( //dont'show value, low-frame, high-frame - new GDBCommand(MI::StackListArguments, QString("0 %1 %2").arg(frame).arg(frame), + new MICommand(StackListArguments, QString("0 %1 %2").arg(frame).arg(frame), new StackListArgumentsHandler(localsName))); } private: DebugSession *m_session; }; void VariableController::updateLocals() { debugSession()->addCommand( - new GDBCommand(MI::StackListLocals, "--simple-values", + new MICommand(StackListLocals, "--simple-values", new StackListLocalsHandler(debugSession()))); } KTextEditor::Range VariableController::expressionRangeUnderCursor(KTextEditor::Document* doc, const KTextEditor::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 { KTextEditor::Cursor{cursor.line(), start}, KTextEditor::Cursor{cursor.line(), end} }; } void VariableController::addWatch(KDevelop::Variable* variable) { // FIXME: should add async 'get full expression' method // to GdbVariable, not poke at varobj. In that case, // we will be able to make addWatch a generic method, not // gdb-specific one. if (GdbVariable *gv = dynamic_cast(variable)) { debugSession()->addCommand( - new GDBCommand(MI::VarInfoPathExpression, + new MICommand(VarInfoPathExpression, gv->varobj(), this, &VariableController::addWatch)); } } void VariableController::addWatchpoint(KDevelop::Variable* variable) { // FIXME: should add async 'get full expression' method // to GdbVariable, not poke at varobj. In that case, // we will be able to make addWatchpoint a generic method, not // gdb-specific one. if (GdbVariable *gv = dynamic_cast(variable)) { debugSession()->addCommand( - new GDBCommand(MI::VarInfoPathExpression, + new MICommand(VarInfoPathExpression, gv->varobj(), this, &VariableController::addWatchpoint)); } } -void VariableController::addWatch(const MI::ResultRecord& r) +void VariableController::addWatch(const ResultRecord& r) { // FIXME: handle error. if (r.reason == "done" && !r["path_expr"].literal().isEmpty()) { variableCollection()->watches()->add(r["path_expr"].literal()); } } -void VariableController::addWatchpoint(const MI::ResultRecord& r) +void VariableController::addWatchpoint(const ResultRecord& r) { if (r.reason == "done" && !r["path_expr"].literal().isEmpty()) { KDevelop::ICore::self()->debugController()->breakpointModel()->addWatchpoint(r["path_expr"].literal()); } } KDevelop::Variable* VariableController:: createVariable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) { return new GdbVariable(model, parent, expression, display); } void VariableController::handleEvent(IDebugSession::event_t event) { IVariableController::handleEvent(event); } void VariableController::stateChanged(IDebugSession::DebuggerState state) { if (state == IDebugSession::EndedState) { GdbVariable::markAllDead(); } }