diff --git a/debuggers/common/mi/micommand.cpp b/debuggers/common/mi/micommand.cpp index 0647073e91..9985b7999b 100644 --- a/debuggers/common/mi/micommand.cpp +++ b/debuggers/common/mi/micommand.cpp @@ -1,621 +1,606 @@ /*************************************************************************** begin : Sun Aug 8 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "micommand.h" using namespace KDevMI::MI; FunctionCommandHandler::FunctionCommandHandler(const FunctionCommandHandler::Function& callback, CommandFlags flags) : _flags(flags) , _callback(callback) { } bool FunctionCommandHandler::handlesError() { return _flags & CmdHandlesError; } void FunctionCommandHandler::handle(const ResultRecord& r) { _callback(r); } MICommand::MICommand(CommandType type, const QString& command, CommandFlags flags) : type_(type) - , flags_(flags & ~CmdHandlesError) - , command_(command) - , commandHandler_(0) - , stateReloading_(false) - , m_thread(-1) - , m_frame(-1) -{ -} - -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) -{ -} - -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)) + , command_(command) + , commandHandler_(nullptr) , stateReloading_(false) , m_thread(-1) , m_frame(-1) { } MICommand::~MICommand() { if (commandHandler_ && commandHandler_->autoDelete()) { delete commandHandler_; } commandHandler_ = nullptr; } QString MICommand::cmdToSend() { return initialString() + '\n'; } QString MICommand::initialString() const { QString result = QString::number(token()); if (type() == NonMI) { result += command_; } else { result += miCommand(); if (m_thread != -1) result = result + QString(" --thread %1").arg(m_thread); if (m_frame != -1) result = result + QString(" --frame %1").arg(m_frame); if (!command_.isEmpty()) result += ' ' + command_; } return result; } bool MICommand::isUserCommand() const { return false; } void MICommand::setHandler(MICommandHandler* handler) { if (commandHandler_ && commandHandler_->autoDelete()) delete commandHandler_; commandHandler_ = handler; + + if (!commandHandler_) { + flags_ = flags_ & ~CmdHandlesError; + } +} + +void MICommand::setHandler(const FunctionCommandHandler::Function& callback) +{ + setHandler(new FunctionCommandHandler(callback, flags())); } bool MICommand::invokeHandler(const ResultRecord& r) { if (commandHandler_) { //ask before calling handler as it might deleted itself in handler bool autoDelete = commandHandler_->autoDelete(); commandHandler_->handle(r); if (autoDelete) { delete commandHandler_; } commandHandler_ = 0; return true; } else { return false; } } void MICommand::newOutput(const QString& line) { lines.push_back(line); } const QStringList& MICommand::allStreamOutput() const { return lines; } bool MICommand::handlesError() const { return commandHandler_ ? commandHandler_->handlesError() : false; } UserCommand::UserCommand(CommandType type, const QString& s) : MICommand(type, s, CmdMaybeStartsRunning) { } bool UserCommand::isUserCommand() const { return true; } QString MICommand::miCommand() const { QString command; switch (type()) { case NonMI: command = ""; break; case BreakAfter: command = "break-after";//"ignore" break; - case BreakCatch: + case BreakCatch: // FIXME: non-exist command command = "break-catch"; break; case BreakCommands: command = "break-commands"; break; case BreakCondition: command = "break-condition";//"cond" break; case BreakDelete: command = "break-delete";//"delete breakpoint" break; case BreakDisable: command = "break-disable";//"disable breakpoint" break; case BreakEnable: command = "break-enable";//"enable breakpoint" break; case BreakInfo: command = "break-info";//"info break" break; case BreakInsert: command = "break-insert -f"; break; case BreakList: command = "break-list";//"info break" break; case BreakWatch: command = "break-watch"; break; case DataDisassemble: command = "data-disassemble"; break; case DataEvaluateExpression: command = "data-evaluate-expression"; break; case DataListChangedRegisters: command = "data-list-changed-registers"; break; case DataListRegisterNames: command = "data-list-register-names"; break; case DataListRegisterValues: command = "data-list-register-values"; break; case DataReadMemory: command = "data-read-memory"; break; case DataWriteMemory: command = "data-write-memory"; break; case DataWriteRegisterVariables: command = "data-write-register-values"; break; case EnablePrettyPrinting: command = "enable-pretty-printing"; break; case EnableTimings: command = "enable-timings"; break; case EnvironmentCd: command = "environment-cd"; break; case EnvironmentDirectory: command = "environment-directory"; break; case EnvironmentPath: command = "environment-path"; break; case EnvironmentPwd: command = "environment-pwd"; break; case ExecAbort: command = "exec-abort"; break; case ExecArguments: command = "exec-arguments";//"set args" break; case ExecContinue: command = "exec-continue"; break; case ExecFinish: command = "exec-finish"; break; case ExecInterrupt: command = "exec-interrupt"; break; case ExecNext: command = "exec-next"; break; case ExecNextInstruction: command = "exec-next-instruction"; break; case ExecReturn: command = "exec-command ="; break; case ExecRun: command = "exec-run"; break; case ExecShowArguments: command = "exec-show-arguments"; break; case ExecSignal: command = "exec-signal"; break; case ExecStep: command = "exec-step"; break; case ExecStepInstruction: command = "exec-step-instruction"; break; case ExecUntil: command = "exec-until"; break; case FileClear: command = "file-clear"; break; case FileExecAndSymbols: command = "file-exec-and-symbols";//"file" break; case FileExecFile: command = "file-exec-file";//"exec-file" break; case FileListExecSections: command = "file-list-exec-sections"; break; case FileListExecSourceFile: command = "file-list-exec-source-file"; break; case FileListExecSourceFiles: command = "file-list-exec-source-files"; break; case FileListSharedLibraries: command = "file-list-shared-libraries"; break; case FileListSymbolFiles: command = "file-list-symbol-files"; break; case FileSymbolFile: command = "file-symbol-file";//"symbol-file" break; case GdbComplete: command = "gdb-complete"; break; case GdbExit: command = "gdb-exit"; break; case GdbSet: command = "gdb-set";//"set" break; case GdbShow: command = "gdb-show";//"show" break; case GdbSource: command = "gdb-source"; break; case GdbVersion: command = "gdb-version";//"show version" break; case InferiorTtySet: command = "inferior-tty-set"; break; case InferiorTtyShow: command = "inferior-tty-show"; break; case InterpreterExec: command = "interpreter-exec"; break; case ListFeatures: command = "list-features"; break; case OverlayAuto: command = "overlay-auto"; break; case OverlayListMappingState: command = "overlay-list-mapping-state"; break; case OverlayListOverlays: command = "overlay-list-overlays"; break; case OverlayMap: command = "overlay-map"; break; case OverlayOff: command = "overlay-off"; break; case OverlayOn: command = "overlay-on"; break; case OverlayUnmap: command = "overlay-unmap"; break; case SignalHandle: return "handle"; //command = "signal-handle"; break; case SignalListHandleActions: command = "signal-list-handle-actions"; break; case SignalListSignalTypes: command = "signal-list-signal-types"; break; case StackInfoDepth: command = "stack-info-depth"; break; case StackInfoFrame: command = "stack-info-frame"; break; case StackListArguments: command = "stack-list-arguments"; break; case StackListExceptionHandlers: command = "stack-list-exception-handlers"; break; case StackListFrames: command = "stack-list-frames"; break; case StackListLocals: command = "stack-list-locals"; break; case StackSelectFrame: command = "stack-select-frame"; break; case SymbolInfoAddress: command = "symbol-info-address"; break; case SymbolInfoFile: command = "symbol-info-file"; break; case SymbolInfoFunction: command = "symbol-info-function"; break; case SymbolInfoLine: command = "symbol-info-line"; break; case SymbolInfoSymbol: command = "symbol-info-symbol"; break; case SymbolListFunctions: command = "symbol-list-functions"; break; case SymbolListLines: command = "symbol-list-lines"; break; case SymbolListTypes: command = "symbol-list-types"; break; case SymbolListVariables: command = "symbol-list-variables"; break; case SymbolLocate: command = "symbol-locate"; break; case SymbolType: command = "symbol-type"; break; case TargetAttach: command = "target-attach"; break; case TargetCompareSections: command = "target-compare-sections"; break; case TargetDetach: command = "target-detach";//"detach" break; case TargetDisconnect: command = "target-disconnect";//"disconnect" break; case TargetDownload: command = "target-download"; break; case TargetExecStatus: command = "target-exec-status"; break; case TargetListAvailableTargets: command = "target-list-available-targets"; break; case TargetListCurrentTargets: command = "target-list-current-targets"; break; case TargetListParameters: command = "target-list-parameters"; break; case TargetSelect: command = "target-select"; break; case ThreadInfo: command = "thread-info"; break; case ThreadListAllThreads: command = "thread-list-all-threads"; break; case ThreadListIds: command = "thread-list-ids"; break; case ThreadSelect: command = "thread-select"; break; case TraceActions: command = "trace-actions"; break; case TraceDelete: command = "trace-delete"; break; case TraceDisable: command = "trace-disable"; break; case TraceDump: command = "trace-dump"; break; case TraceEnable: command = "trace-enable"; break; case TraceExists: command = "trace-exists"; break; case TraceFind: command = "trace-find"; break; case TraceFrameNumber: command = "trace-frame-number"; break; case TraceInfo: command = "trace-info"; break; case TraceInsert: command = "trace-insert"; break; case TraceList: command = "trace-list"; break; case TracePassCount: command = "trace-pass-count"; break; case TraceSave: command = "trace-save"; break; case TraceStart: command = "trace-start"; break; case TraceStop: command = "trace-stop"; break; case VarAssign: command = "var-assign"; break; case VarCreate: command = "var-create"; break; case VarDelete: command = "var-delete"; break; case VarEvaluateExpression: command = "var-evaluate-expression"; break; case VarInfoPathExpression: command = "var-info-path-expression"; break; case VarInfoExpression: command = "var-info-expression"; break; case VarInfoNumChildren: command = "var-info-num-children"; break; case VarInfoType: command = "var-info-type"; break; case VarListChildren: command = "var-list-children"; break; case VarSetFormat: command = "var-set-format"; break; case VarSetFrozen: command = "var-set-frozen"; break; case VarShowAttributes: command = "var-show-attributes"; break; case VarShowFormat: command = "var-show-format"; break; case VarUpdate: command = "var-update"; break; default: command = "unknown"; break; } return '-' + command; } CommandType MICommand::type() const { return type_; } int MICommand::thread() const { return m_thread; } void MICommand::setThread(int thread) { m_thread = thread; } int MICommand::frame() const { return m_frame; } void MICommand::setFrame(int frame) { m_frame = frame; } QString MICommand::command() const { return command_; } void MICommand::setStateReloading(bool f) { stateReloading_ = f; } bool MICommand::stateReloading() const { return stateReloading_; } diff --git a/debuggers/common/mi/micommand.h b/debuggers/common/mi/micommand.h index a4dbf86d3d..33a2c11502 100644 --- a/debuggers/common/mi/micommand.h +++ b/debuggers/common/mi/micommand.h @@ -1,352 +1,350 @@ /*************************************************************************** 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 _MICOMMAND_H_ #define _MICOMMAND_H_ #include "mi/mi.h" #include #include #include #include -namespace KDevMI { namespace MI { +namespace KDevMI { + +class MIDebugSession; + +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 MICommandHandler { public: 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 MICommandHandler { public: typedef std::function Function; FunctionCommandHandler(const Function& callback, CommandFlags flags = 0); virtual void handle(const ResultRecord&) override; virtual bool handlesError() override; private: CommandFlags _flags; Function _callback; }; /** * @author John Birch */ class MICommand { -public: +protected: MICommand(CommandType type, const QString& arguments = QString(), CommandFlags flags = 0); - - template - MICommand(CommandType type, const QString& arguments, - Handler* handler_this, - void (Handler::* handler_method)(const ResultRecord&), - CommandFlags flags = 0); - - MICommand(CommandType type, const QString& arguments, MICommandHandler* handler, - CommandFlags flags = 0); - - MICommand(CommandType type, const QString& arguments, - const FunctionCommandHandler::Function& callback, - CommandFlags flags = 0); + friend class KDevMI::MIDebugSession; +public: virtual ~MICommand(); CommandType type() const; - QString miCommand() const; + virtual 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 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(MICommandHandler* handler); + void setHandler(const FunctionCommandHandler::Function &callback); + + template + void setHandler(Handler* handler_this, void (Handler::* handler_method)(const ResultRecord&)); /* 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 ResultRecord& r); // Returns 'true' if 'invokeHandler' should be invoked even // on MI errors. bool handlesError() const; // 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: CommandType type_; CommandFlags flags_; uint32_t token_ = 0; QString command_; MICommandHandler *commandHandler_; QStringList lines; bool stateReloading_; private: int m_thread; int m_frame; }; class UserCommand : public MICommand { public: 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 MICommand { public: template 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 MICommand { public: typedef std::function Function; template SentinelCommand(Handler* handler_this, void (Handler::* handler_method)(), CommandFlags flags = 0) : 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) : MICommand(NonMI, QString(), flags) , handler(handler) { } using MICommand::invokeHandler; void invokeHandler() { handler(); } QString cmdToSend() override { return ""; } private: Function handler; }; 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&)) - : MICommand(DataEvaluateExpression, expression, this, - &ExpressionValueCommand::handleResponse), + : MICommand(DataEvaluateExpression, expression), handler_this(handler_this), handler_method(static_cast(handler_method)) - {} + { + setHandler(this, &ExpressionValueCommand::handleResponse); + } void handleResponse(const ResultRecord& r) { (handler_this.data()->*handler_method)(r["value"].literal()); } private: QPointer handler_this; handler_method_t handler_method; }; +template +FunctionCommandHandler::Function guarded_callback(Handler *handler_this, + void (Handler::* handler_method)(const ResultRecord&)) +{ + QPointer guarded_this(handler_this); + return [guarded_this, handler_method](const ResultRecord& r) { + if (guarded_this) { + (guarded_this.data()->*handler_method)(r); + } + }; +} template -MICommand::MICommand( - CommandType type, - const QString& command, - Handler* handler_this, - 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) +void MICommand::setHandler(Handler* handler_this, + void (Handler::* handler_method)(const ResultRecord&)) { QPointer guarded_this(handler_this); setHandler(new FunctionCommandHandler([guarded_this, handler_method](const ResultRecord& r) { if (guarded_this) { (guarded_this.data()->*handler_method)(r); } - }, flags)); + }, flags())); } template CliCommand::CliCommand( CommandType type, const QString& command, Handler* handler_this, void (Handler::* handler_method)(const QStringList&), CommandFlags flags) : MICommand(type, command) { QPointer guarded_this(handler_this); 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 } // end of namespace KDevMI Q_DECLARE_OPERATORS_FOR_FLAGS(KDevMI::MI::CommandFlags) #endif diff --git a/debuggers/common/mibreakpointcontroller.cpp b/debuggers/common/mibreakpointcontroller.cpp index 80732cfdd1..35e64288f7 100644 --- a/debuggers/common/mibreakpointcontroller.cpp +++ b/debuggers/common/mibreakpointcontroller.cpp @@ -1,762 +1,761 @@ /* 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 "mibreakpointcontroller.h" #include "debuglog.h" #include "midebugsession.h" #include "mi/micommand.h" #include "stringhelpers.h" #include #include #include #include #include using namespace KDevMI; using namespace KDevMI::MI; using namespace KDevelop; struct MIBreakpointController::Handler : public MICommandHandler { Handler(MIBreakpointController* 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; } MIBreakpointController* controller; BreakpointDataPtr breakpoint; BreakpointModel::ColumnFlags columns; }; struct MIBreakpointController::UpdateHandler : public MIBreakpointController::Handler { UpdateHandler(MIBreakpointController* 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 MIBreakpointController::InsertedHandler : public MIBreakpointController::Handler { InsertedHandler(MIBreakpointController* 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->debuggerId = miBkpt["number"].toInt(); if (row >= 0) { controller->updateFromDebugger(row, miBkpt); if (breakpoint->dirty != 0) controller->sendUpdates(row); } else { // breakpoint was deleted while insertion was in flight - controller->debugSession()->addCommand( - new MICommand(BreakDelete, QString::number(breakpoint->debuggerId), - CmdImmediately)); + controller->debugSession()->addCommand(BreakDelete, + QString::number(breakpoint->debuggerId), + CmdImmediately); } } if (row >= 0) { controller->recalculateState(row); } } }; struct MIBreakpointController::DeleteHandler : MIBreakpointController::Handler { DeleteHandler(MIBreakpointController* c, const BreakpointDataPtr& b) : Handler(c, b, 0) {} void handle(const ResultRecord&) override { controller->m_pendingDeleted.removeAll(breakpoint); } }; struct MIBreakpointController::IgnoreChanges { IgnoreChanges(MIBreakpointController& controller) : controller(controller) { ++controller.m_ignoreChanges; } ~IgnoreChanges() { --controller.m_ignoreChanges; } MIBreakpointController& controller; }; MIBreakpointController::MIBreakpointController(MIDebugSession * parent) : IBreakpointController(parent) { Q_ASSERT(parent); connect(parent, &MIDebugSession::inferiorStopped, this, &MIBreakpointController::programStopped); int numBreakpoints = breakpointModel()->breakpoints().size(); for (int row = 0; row < numBreakpoints; ++row) breakpointAdded(row); } MIDebugSession *MIBreakpointController::debugSession() const { Q_ASSERT(QObject::parent()); return static_cast(const_cast(QObject::parent())); } int MIBreakpointController::breakpointRow(const BreakpointDataPtr& breakpoint) { return m_breakpoints.indexOf(breakpoint); } void MIBreakpointController::setDeleteDuplicateBreakpoints(bool enable) { m_deleteDuplicateBreakpoints = enable; } void MIBreakpointController::initSendBreakpoints() { for (int row = 0; row < m_breakpoints.size(); ++row) { BreakpointDataPtr breakpoint = m_breakpoints[row]; if (breakpoint->debuggerId < 0 && breakpoint->sent == 0) { createBreakpoint(row); } } } void MIBreakpointController::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; createBreakpoint(row); } void MIBreakpointController::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->debuggerId < 0) { createBreakpoint(row); } else { sendUpdates(row); } } void MIBreakpointController::breakpointAboutToBeDeleted(int row) { if (m_ignoreChanges > 0) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); m_breakpoints.removeAt(row); if (breakpoint->debuggerId < 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()->debuggerStateIsOn(s_dbgNotStarted)) return; debugSession()->addCommand( - new MICommand( BreakDelete, QString::number(breakpoint->debuggerId), - new DeleteHandler(this, breakpoint), CmdImmediately)); + new DeleteHandler(this, breakpoint), CmdImmediately); m_pendingDeleted << breakpoint; } // Note: despite the name, this is in fact session state changed. void MIBreakpointController::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 MIBreakpointController::createBreakpoint(int row) { if (debugSession()->debuggerStateIsOn(s_dbgNotStarted)) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); Q_ASSERT(breakpoint->debuggerId < 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 MICommand logic QString arguments; if (!modelBreakpoint->enabled()) arguments += "-d "; if (!modelBreakpoint->condition().isEmpty()) arguments += QString("-c %0 ").arg(Utils::quoteExpression(modelBreakpoint->condition())); if (modelBreakpoint->ignoreHits() != 0) arguments += QString("-i %0 ").arg(modelBreakpoint->ignoreHits()); arguments += Utils::quoteExpression(location); BreakpointModel::ColumnFlags sent = BreakpointModel::EnableColumnFlag | BreakpointModel::ConditionColumnFlag | BreakpointModel::IgnoreHitsColumnFlag | BreakpointModel::LocationColumnFlag; - debugSession()->addCommand( - new MICommand(BreakInsert, arguments, - new InsertedHandler(this, breakpoint, sent), - CmdImmediately)); + debugSession()->addCommand(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 MICommand( - BreakWatch, - opt + Utils::quoteExpression(modelBreakpoint->location()), - new InsertedHandler(this, breakpoint, BreakpointModel::LocationColumnFlag), - CmdImmediately)); + debugSession()->addCommand(BreakWatch, + opt + Utils::quoteExpression(modelBreakpoint->location()), + new InsertedHandler(this, breakpoint, + BreakpointModel::LocationColumnFlag), + CmdImmediately); } recalculateState(row); } void MIBreakpointController::sendUpdates(int row) { if (debugSession()->debuggerStateIsOn(s_dbgNotStarted)) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); Q_ASSERT(breakpoint->debuggerId >= 0 && breakpoint->sent == 0); if (breakpoint->dirty & BreakpointModel::LocationColumnFlag) { // Gdb considers locations as fixed, so delete and re-create the breakpoint - debugSession()->addCommand( - new MICommand(BreakDelete, QString::number(breakpoint->debuggerId), CmdImmediately)); + debugSession()->addCommand(BreakDelete, + QString::number(breakpoint->debuggerId), CmdImmediately); breakpoint->debuggerId = -1; createBreakpoint(row); return; } if (breakpoint->dirty & BreakpointModel::EnableColumnFlag) { - debugSession()->addCommand( - new MICommand(modelBreakpoint->enabled() ? BreakEnable : BreakDisable, - QString::number(breakpoint->debuggerId), - new UpdateHandler(this, breakpoint, BreakpointModel::EnableColumnFlag), - CmdImmediately)); + debugSession()->addCommand(modelBreakpoint->enabled() ? BreakEnable : BreakDisable, + QString::number(breakpoint->debuggerId), + new UpdateHandler(this, breakpoint, + BreakpointModel::EnableColumnFlag), + CmdImmediately); } if (breakpoint->dirty & BreakpointModel::IgnoreHitsColumnFlag) { - debugSession()->addCommand( - new MICommand(BreakAfter, - QString("%0 %1").arg(breakpoint->debuggerId).arg(modelBreakpoint->ignoreHits()), - new UpdateHandler(this, breakpoint, BreakpointModel::IgnoreHitsColumnFlag), - CmdImmediately)); + debugSession()->addCommand(BreakAfter, + QString("%0 %1").arg(breakpoint->debuggerId) + .arg(modelBreakpoint->ignoreHits()), + new UpdateHandler(this, breakpoint, + BreakpointModel::IgnoreHitsColumnFlag), + CmdImmediately); } if (breakpoint->dirty & BreakpointModel::ConditionColumnFlag) { - debugSession()->addCommand( - new MICommand(BreakCondition, - QString("%0 %1").arg(breakpoint->debuggerId).arg(modelBreakpoint->condition()), - new UpdateHandler(this, breakpoint, BreakpointModel::ConditionColumnFlag), - CmdImmediately)); + debugSession()->addCommand(BreakCondition, + QString("%0 %1").arg(breakpoint->debuggerId) + .arg(modelBreakpoint->condition()), + new UpdateHandler(this, breakpoint, + BreakpointModel::ConditionColumnFlag), + CmdImmediately); } recalculateState(row); } void MIBreakpointController::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()->debuggerStateIsOn(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 MIBreakpointController::rowFromDebuggerId(int gdbId) const { for (int row = 0; row < m_breakpoints.size(); ++row) { if (gdbId == m_breakpoints[row]->debuggerId) return row; } return -1; } void MIBreakpointController::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; createFromDebugger(miBkpt); } void MIBreakpointController::notifyBreakpointModified(const AsyncRecord& r) { const Value& miBkpt = r["bkpt"]; const int gdbId = miBkpt["number"].toInt(); const int row = rowFromDebuggerId(gdbId); if (row < 0) { for (const auto& breakpoint : m_pendingDeleted) { if (breakpoint->debuggerId == gdbId) { // Received a modification of a breakpoint whose deletion is currently // in-flight; simply ignore it. return; } } qCWarning(DEBUGGERCOMMON) << "Received a modification of an unknown breakpoint"; createFromDebugger(miBkpt); } else { updateFromDebugger(row, miBkpt); } } void MIBreakpointController::notifyBreakpointDeleted(const AsyncRecord& r) { const int gdbId = r["id"].toInt(); const int row = rowFromDebuggerId(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 MIBreakpointController::createFromDebugger(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 { qCWarning(DEBUGGERCOMMON) << "Unknown breakpoint type " << type; return; } // During debugger startup, we want to avoid creating duplicate breakpoints when the same breakpoint // appears both in our model and in a init file e.g. .gdbinit 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->debuggerId >= 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 = Utils::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 && Utils::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 (Utils::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->debuggerId = 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; updateFromDebugger(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->debuggerId = miBkpt["number"].toInt(); updateFromDebugger(row, miBkpt); } // This method is required for the legacy interface which will be removed void MIBreakpointController::sendMaybe(KDevelop::Breakpoint*) { Q_ASSERT(false); } void MIBreakpointController::updateFromDebugger(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(Utils::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(Utils::unquoteExpression(rx.cap(1))), rx.cap(2).toInt()-1); } else { modelBreakpoint->setData(Breakpoint::LocationColumn, Utils::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 MIBreakpointController::programStopped(const AsyncRecord& r) { if (!r.hasField("reason")) return; const QString reason = r["reason"].literal(); int debuggerId = -1; if (reason == "breakpoint-hit") { debuggerId = r["bkptno"].toInt(); } else if (reason == "watchpoint-trigger") { debuggerId = r["wpt"]["number"].toInt(); } else if (reason == "read-watchpoint-trigger") { debuggerId = r["hw-rwpt"]["number"].toInt(); } else if (reason == "access-watchpoint-trigger") { debuggerId = r["hw-awpt"]["number"].toInt(); } if (debuggerId < 0) return; int row = rowFromDebuggerId(debuggerId); 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/common/mibreakpointcontroller.h b/debuggers/common/mibreakpointcontroller.h index 18c1d7bbab..7e00ccea0f 100644 --- a/debuggers/common/mibreakpointcontroller.h +++ b/debuggers/common/mibreakpointcontroller.h @@ -1,123 +1,122 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh 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. */ #ifndef MIBREAKPOINTCONTROLLER_H #define MIBREAKPOINTCONTROLLER_H #include "dbgglobal.h" #include #include #include class QModelIndex; namespace KDevMI { namespace MI { struct AsyncRecord; struct ResultRecord; struct Value; } struct BreakpointData { int debuggerId; KDevelop::BreakpointModel::ColumnFlags dirty; KDevelop::BreakpointModel::ColumnFlags sent; KDevelop::BreakpointModel::ColumnFlags errors; bool pending; BreakpointData() : debuggerId(-1) , pending(false) {} }; typedef QSharedPointer BreakpointDataPtr; class MIDebugSession; /** * Handles signals from the editor that relate to breakpoints and the execution * point of the debugger. * We may change, add or remove breakpoints in this class. */ class MIBreakpointController : public KDevelop::IBreakpointController { Q_OBJECT public: - MIBreakpointController( MIDebugSession* parent); + MIBreakpointController(MIDebugSession* parent); using IBreakpointController::breakpointModel; /** * Controls whether when duplicate breakpoints are received via async notification from GDB, * one of the duplicates will be deleted. */ void setDeleteDuplicateBreakpoints(bool enable); void breakpointAdded(int row) override; void breakpointModelChanged(int row, KDevelop::BreakpointModel::ColumnFlags columns) override; void breakpointAboutToBeDeleted(int row) override; void debuggerStateChanged(KDevelop::IDebugSession::DebuggerState) override; void notifyBreakpointCreated(const MI::AsyncRecord& r); void notifyBreakpointModified(const MI::AsyncRecord& r); void notifyBreakpointDeleted(const MI::AsyncRecord& r); public Q_SLOTS: void initSendBreakpoints(); private Q_SLOTS: void programStopped(const MI::AsyncRecord &r); private: MIDebugSession* debugSession() const; int breakpointRow(const BreakpointDataPtr& breakpoint); void createBreakpoint(int row); void sendUpdates(int row); void recalculateState(int row); void sendMaybe(KDevelop::Breakpoint *breakpoint) override; - // TODO: what's this void createFromDebugger(const MI::Value& miBkpt); void updateFromDebugger(int row, const MI::Value& miBkpt, KDevelop::BreakpointModel::ColumnFlags lockedColumns = 0); int rowFromDebuggerId(int gdbId) const; struct Handler; struct InsertedHandler; struct UpdateHandler; struct DeleteHandler; struct IgnoreChanges; QList m_breakpoints; QList m_pendingDeleted; int m_ignoreChanges = 0; bool m_deleteDuplicateBreakpoints = false; }; } // end of namespace KDevMI #endif // MIBREAKPOINTCONTROLLER_H diff --git a/debuggers/common/midebugger.cpp b/debuggers/common/midebugger.cpp index 8eff014c9c..85b2dc7644 100644 --- a/debuggers/common/midebugger.cpp +++ b/debuggers/common/midebugger.cpp @@ -1,334 +1,334 @@ /* * Low level GDB interface. * * Copyright 1999 John Birch * Copyright 2007 Vladimir Prus * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "midebugger.h" #include "debuglog.h" #include "mi/micommand.h" #include "sys/signal.h" #include #include #include #include #include #include // #define DEBUG_NO_TRY //to get a backtrace to where the exception was thrown using namespace KDevMI; using namespace KDevMI::MI; MIDebugger::MIDebugger(QObject* parent) : QObject(parent) , process_(nullptr) , currentCmd_(nullptr) { process_ = new KProcess(this); process_->setOutputChannelMode(KProcess::SeparateChannels); connect(process_, &KProcess::readyReadStandardOutput, this, &MIDebugger::readyReadStandardOutput); connect(process_, &KProcess::readyReadStandardError, this, &MIDebugger::readyReadStandardError); connect(process_, static_cast(&KProcess::finished), this, &MIDebugger::processFinished); connect(process_, static_cast(&KProcess::error), this, &MIDebugger::processErrored); } MIDebugger::~MIDebugger() { // prevent Qt warning: QProcess: Destroyed while process is still running. if (process_ && process_->state() == QProcess::Running) { disconnect(process_, static_cast(&KProcess::error), this, &MIDebugger::processErrored); process_->kill(); process_->waitForFinished(10); } } void MIDebugger::execute(MICommand* command) { currentCmd_ = command; QString commandText = currentCmd_->cmdToSend(); qCDebug(DEBUGGERCOMMON) << "SEND:" << commandText.trimmed(); QByteArray commandUtf8 = commandText.toUtf8(); process_->write(commandUtf8, commandUtf8.length()); QString prettyCmd = currentCmd_->cmdToSend(); prettyCmd.remove( QRegExp("set prompt \032.\n") ); prettyCmd = "(gdb) " + prettyCmd; if (currentCmd_->isUserCommand()) emit userCommandOutput(prettyCmd); else emit internalCommandOutput(prettyCmd); } bool MIDebugger::isReady() const { return currentCmd_ == 0; } void MIDebugger::interrupt() { //TODO:win32 Porting needed int pid = process_->pid(); if (pid != 0) { ::kill(pid, SIGINT); } } MICommand* MIDebugger::currentCommand() const { return currentCmd_; } void MIDebugger::kill() { process_->kill(); } void MIDebugger::readyReadStandardOutput() { process_->setReadChannel(QProcess::StandardOutput); buffer_ += process_->readAll(); for (;;) { /* In MI mode, all messages are exactly one line. See if we have any complete lines in the buffer. */ int i = buffer_.indexOf('\n'); if (i == -1) break; QByteArray reply(buffer_.left(i)); buffer_ = buffer_.mid(i+1); processLine(reply); } } void MIDebugger::readyReadStandardError() { - process_->setReadChannel(QProcess::StandardOutput); + process_->setReadChannel(QProcess::StandardError); emit internalCommandOutput(QString::fromUtf8(process_->readAll())); } void MIDebugger::processLine(const QByteArray& line) { qCDebug(DEBUGGERCOMMON) << "Debugger (" << process_->pid() <<") output: " << line; FileSymbol file; file.contents = line; std::unique_ptr r(mi_parser_.parse(&file)); if (!r) { // FIXME: Issue an error! qCDebug(DEBUGGERCOMMON) << "Invalid MI message:" << line; // We don't consider the current command done. // So, if a command results in unparseable reply, // we'll just wait for the "right" reply, which might // never come. However, marking the command as // done in this case is even more risky. // It's probably possible to get here if we're debugging // natively without PTY, though this is uncommon case. return; } #ifndef DEBUG_NO_TRY try { #endif switch(r->kind) { case MI::Record::Result: { MI::ResultRecord& result = static_cast(*r); emit internalCommandOutput(QString::fromUtf8(line) + '\n'); // GDB doc: "running" and "exit" are status codes equivalent to "done" if (result.reason == "done" || result.reason == "running" || result.reason == "exit") { if (!currentCmd_) { qCDebug(DEBUGGERCOMMON) << "Received a result without a pending command"; } else { qCDebug(DEBUGGERCOMMON) << "Result token is" << result.token; Q_ASSERT(currentCmd_->token() == result.token); currentCmd_->invokeHandler(result); } } else if (result.reason == "error") { qCDebug(DEBUGGERCOMMON) << "Handling error"; // Some commands want to handle errors themself. if (currentCmd_->handlesError() && currentCmd_->invokeHandler(result)) { qCDebug(DEBUGGERCOMMON) << "Invoked custom handler\n"; // Done, nothing more needed } else emit error(result); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled result code: " << result.reason; } delete currentCmd_; currentCmd_ = nullptr; emit ready(); break; } case MI::Record::Async: { MI::AsyncRecord& async = dynamic_cast(*r); switch (async.subkind) { case MI::AsyncRecord::Exec: { // Prefix '*'; asynchronous state changes of the target if (async.reason == "stopped") { emit programStopped(async); } else if (async.reason == "running") { emit programRunning(); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled exec notification: " << async.reason; } break; } case MI::AsyncRecord::Notify: { // Prefix '='; supplementary information that we should handle (new breakpoint etc.) emit notification(async); break; } case MI::AsyncRecord::Status: { // Prefix '+'; GDB documentation: // On-going status information about progress of a slow operation; may be ignored break; } default: Q_ASSERT(false); } break; } case MI::Record::Stream: { MI::StreamRecord& s = dynamic_cast(*r); if (s.subkind == MI::StreamRecord::Target) { emit applicationOutput(s.message); } else { if (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 MIDebugger::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(DEBUGGERCOMMON) << "GDB FINISHED\n"; bool abnormal = exitCode != 0 || exitStatus != QProcess::NormalExit; emit userCommandOutput("(gdb) Process exited\n"); emit exited(abnormal, i18n("Process exited")); } void MIDebugger::processErrored(QProcess::ProcessError error) { qCDebug(DEBUGGERCOMMON) << "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.", debuggerBinary_), i18n("Could not start debugger")); emit userCommandOutput("(gdb) didn't start\n"); emit exited(true, i18n("Process didn't start'")); } else if (error == QProcess::Crashed) { KMessageBox::error( qApp->activeWindow(), i18n("Gdb crashed." "

Because of that the debug session has to be ended.
" "Try to reproduce the crash with plain gdb and report a bug.
"), i18n("Gdb crashed")); emit userCommandOutput("(gdb) Process crashed\n"); emit exited(true, i18n("Process crashed")); } } diff --git a/debuggers/common/midebugsession.cpp b/debuggers/common/midebugsession.cpp index 65c478bd93..4af28a3f26 100644 --- a/debuggers/common/midebugsession.cpp +++ b/debuggers/common/midebugsession.cpp @@ -1,1268 +1,1291 @@ /* * Common code for debugger support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "midebugsession.h" #include "debuglog.h" #include "midebugger.h" #include "mi/mi.h" #include "mi/micommand.h" #include "mi/micommandqueue.h" #include "stty.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; using namespace KDevMI; using namespace KDevMI::MI; MIDebugSession::MIDebugSession() : m_procLineMaker(new ProcessLineMaker(this)) , m_commandQueue(new CommandQueue) , m_sessionState(NotStartedState) , m_debugger(nullptr) , m_debuggerState(s_dbgNotStarted | s_appNotStarted) , m_stateReloadInProgress(false) , m_stateReloadNeeded(false) , m_tty(nullptr) , m_hasCrashed(false) , m_sourceInitFile(true) { // setup signals connect(m_procLineMaker, &ProcessLineMaker::receivedStdoutLines, this, &MIDebugSession::inferiorStdoutLines); connect(m_procLineMaker, &ProcessLineMaker::receivedStderrLines, this, &MIDebugSession::inferiorStderrLines); // forward tty output to process line maker connect(this, &MIDebugSession::inferiorTtyStdout, m_procLineMaker, &ProcessLineMaker::slotReceivedStdout); connect(this, &MIDebugSession::inferiorTtyStderr, m_procLineMaker, &ProcessLineMaker::slotReceivedStderr); // FIXME: see if this still works //connect(statusBarIndicator, SIGNAL(doubleClicked()), // controller, SLOT(explainDebuggerStatus())); // FIXME: reimplement / re-enable //connect(this, SIGNAL(addWatchVariable(QString)), controller->variables(), SLOT(slotAddWatchVariable(QString))); //connect(this, SIGNAL(evaluateExpression(QString)), controller->variables(), SLOT(slotEvaluateExpression(QString))); } MIDebugSession::~MIDebugSession() { qCDebug(DEBUGGERCOMMON) << "Destroying MIDebugSession"; // Deleting the session involves shutting down gdb nicely. // When were attached to a process, we must first detach so that the process // can continue running as it was before being attached. gdb is quite slow to // detach from a process, so we must process events within here to get a "clean" // shutdown. if (!debuggerStateIsOn(s_dbgNotStarted)) { stopDebugger(); } } IDebugSession::DebuggerState MIDebugSession::state() const { return m_sessionState; } bool MIDebugSession::restartAvaliable() const { if (debuggerStateIsOn(s_attached) || debuggerStateIsOn(s_core)) { return false; } else { return true; } } bool MIDebugSession::startDebugger(ILaunchConfiguration *cfg) { qCDebug(DEBUGGERCOMMON) << "Starting new debugger instance"; if (m_debugger) { qCWarning(DEBUGGERCOMMON) << "m_debugger object still exists"; delete m_debugger; m_debugger = nullptr; } m_debugger = createDebugger(); m_debugger->setParent(this); // output signals connect(m_debugger, &MIDebugger::applicationOutput, this, [this](const QString &output) { emit inferiorStdoutLines(output.split(QRegularExpression("[\r\n]"), QString::SkipEmptyParts)); }); connect(m_debugger, &MIDebugger::userCommandOutput, this, &MIDebugSession::debuggerUserCommandOutput); connect(m_debugger, &MIDebugger::internalCommandOutput, this, &MIDebugSession::debuggerInternalCommandOutput); // state signals connect(m_debugger, &MIDebugger::programStopped, this, &MIDebugSession::inferiorStopped); connect(m_debugger, &MIDebugger::programRunning, this, &MIDebugSession::inferiorRunning); // internal handlers connect(m_debugger, &MIDebugger::ready, this, &MIDebugSession::slotDebuggerReady); connect(m_debugger, &MIDebugger::exited, this, &MIDebugSession::slotDebuggerExited); connect(m_debugger, &MIDebugger::programStopped, this, &MIDebugSession::slotInferiorStopped); connect(m_debugger, &MIDebugger::programRunning, this, &MIDebugSession::slotInferiorRunning); connect(m_debugger, &MIDebugger::notification, this, &MIDebugSession::processNotification); // start the debugger. Do this after connecting all signals so that initial // debugger output, and important events like the debugger died are reported. QStringList extraArguments; if (!m_sourceInitFile) extraArguments << "--nx"; auto config = cfg ? cfg->config() // FIXME: this is only used when attachToProcess or examineCoreFile. // Change to use a global launch configuration when calling : KConfigGroup(KSharedConfig::openConfig(), "GDB Config"); m_debugger->start(config, extraArguments); // FIXME: here, we should wait until the debugger is up and waiting for input. // Then, clear s_dbgNotStarted // It's better to do this right away so that the state bit is always correct. setDebuggerStateOff(s_dbgNotStarted); // Initialise debugger. At this stage debugger is sitting wondering what to do, // and to whom. initializeDebugger(); qCDebug(DEBUGGERCOMMON) << "Debugger instance started"; return true; } bool MIDebugSession::startDebugging(ILaunchConfiguration* cfg, IExecutePlugin* iexec) { qCDebug(DEBUGGERCOMMON) << "Starting new debug session"; Q_ASSERT(cfg); Q_ASSERT(iexec); // Ensure debugger is started first if (debuggerStateIsOn(s_appNotStarted)) { emit showMessage(i18n("Running program"), 1000); } if (debuggerStateIsOn(s_dbgNotStarted)) { if (!startDebugger(cfg)) return false; } if (debuggerStateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERCOMMON) << "Tried to run when debugger shutting down"; return false; } // Set up the tty for the inferior bool config_useExternalTerminal = iexec->useTerminal(cfg); QString config_ternimalName = iexec->terminal(cfg); if (!config_ternimalName.isEmpty()) { // the external terminal cmd contains additional arguments, just get the terminal name config_ternimalName = KShell::splitArgs(config_ternimalName).first(); } m_tty.reset(new STTY(config_useExternalTerminal, config_ternimalName)); if (!config_useExternalTerminal) { connect(m_tty.get(), &STTY::OutOutput, this, &MIDebugSession::inferiorTtyStdout); connect(m_tty.get(), &STTY::ErrOutput, this, &MIDebugSession::inferiorTtyStderr); } QString tty(m_tty->getSlave()); if (tty.isEmpty()) { KMessageBox::information(qApp->activeWindow(), m_tty->lastError(), i18n("warning")); m_tty.reset(nullptr); return false; } - queueCmd(new MICommand(InferiorTtySet, tty)); + addCommand(InferiorTtySet, tty); // Only dummy err here, actual erros have been checked already in the job and we don't get here if there were any QString err; QString executable = iexec->executable(cfg, err).toLocalFile(); QStringList arguments = iexec->arguments(cfg, err); // Change the working directory to the correct one QString dir = iexec->workingDirectory(cfg).toLocalFile(); if (dir.isEmpty()) { dir = QFileInfo(executable).absolutePath(); } - queueCmd(new MICommand(MI::EnvironmentCd, '"' + dir + '"')); + addCommand(EnvironmentCd, '"' + dir + '"'); // Set the environment variables EnvironmentGroupList l(KSharedConfig::openConfig()); QString envgrp = iexec->environmentGroup(cfg); if (envgrp.isEmpty()) { qCWarning(DEBUGGERCOMMON) << i18n("No environment group specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment group.", cfg->name()); envgrp = l.defaultGroup(); } for (const auto &envvar : l.createEnvironment(envgrp, {})) { - queueCmd(new MICommand(MI::GdbSet, "environment " + envvar)); + addCommand(GdbSet, "environment " + envvar); } // Set the run arguments if (!arguments.isEmpty()) - queueCmd(new MICommand(MI::ExecArguments, KShell::joinArgs(arguments))); + addCommand(ExecArguments, KShell::joinArgs(arguments)); // Do other debugger specific config options and actually start the inferior program if (!execInferior(cfg, executable)) { return false; } QString config_startWith = cfg->config().readEntry(startWithEntry, QStringLiteral("ApplicationOutput")); if (config_startWith == "GdbConsole") { emit raiseDebuggerConsoleViews(); } else if (config_startWith == "FrameStack") { emit raiseFramestackViews(); } else { // ApplicationOutput is raised in DebugJob (by setting job to Verbose/Silent) } return true; } // FIXME: use same configuration process as startDebugging bool MIDebugSession::attachToProcess(int pid) { qCDebug(DEBUGGERCOMMON) << "Attach to process" << pid; if (debuggerStateIsOn(s_dbgNotStarted)) { // FIXME: use global launch configuration rather than nullptr if (!startDebugger(nullptr)) { return false; } } setDebuggerStateOn(s_attached); //set current state to running, after attaching we will get *stopped response setDebuggerStateOn(s_appRunning); // Currently, we always start debugger with a name of binary, // we might be connecting to a different binary completely, // so cancel all symbol tables gdb has. // We can't omit application name from gdb invocation // because for libtool binaries, we have no way to guess // real binary name. - queueCmd(new MICommand(MI::FileExecAndSymbols)); + addCommand(MI::FileExecAndSymbols); - queueCmd(new MICommand(MI::TargetAttach, QString::number(pid), - this, &MIDebugSession::handleTargetAttach, - CmdHandlesError)); + addCommand(TargetAttach, QString::number(pid), + this, &MIDebugSession::handleTargetAttach, + CmdHandlesError); - queueCmd(new SentinelCommand(breakpointController(), - &MIBreakpointController::initSendBreakpoints)); + addCommand(new SentinelCommand(breakpointController(), + &MIBreakpointController::initSendBreakpoints)); raiseEvent(connected_to_program); emit raiseFramestackViews(); return true; } void MIDebugSession::handleTargetAttach(const MI::ResultRecord& r) { if (r.reason == "error") { KMessageBox::error( qApp->activeWindow(), i18n("Could not attach debugger:
")+ r["msg"].literal(), i18n("Startup error")); stopDebugger(); } } bool MIDebugSession::examineCoreFile(const QUrl &debugee, const QUrl &coreFile) { if (debuggerStateIsOn(s_dbgNotStarted)) { // FIXME: use global launch configuration rather than nullptr if (!startDebugger(nullptr)) { return false; } } // FIXME: support non-local URLs - queueCmd(new MICommand(MI::FileExecAndSymbols, debugee.toLocalFile())); - queueCmd(new MICommand(MI::NonMI, "core " + coreFile.toLocalFile(), - this, &MIDebugSession::handleCoreFile, CmdHandlesError)); + addCommand(FileExecAndSymbols, debugee.toLocalFile()); + addCommand(NonMI, "core " + coreFile.toLocalFile(), + this, &MIDebugSession::handleCoreFile, + CmdHandlesError); raiseEvent(connected_to_program); raiseEvent(program_state_changed); return true; } void MIDebugSession::handleCoreFile(const MI::ResultRecord& r) { if (r.reason != "error") { setDebuggerStateOn(s_programExited|s_core); } else { KMessageBox::information( qApp->activeWindow(), i18n("Failed to load core file" "

Debugger reported the following error:" "

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

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

Debugger reported the following error:" "

%1", result["msg"].literal()), i18n("Debugger error")); // Error most likely means that some change made in GUI // was not communicated to the gdb, so GUI is now not // in sync with gdb. Resync it. // // Another approach is to make each widget reload it content // on errors from commands that it sent, but that's too complex. // Errors are supposed to happen rarely, so full reload on error // is not a big deal. Well, maybe except for memory view, but // it's no auto-reloaded anyway. // // Also, don't reload state on errors appeared during state // reloading! if (!m_debugger->currentCommand()->stateReloading()) raiseEvent(program_state_changed); } void MIDebugSession::setSourceInitFile(bool enable) { m_sourceInitFile = enable; } diff --git a/debuggers/common/midebugsession.h b/debuggers/common/midebugsession.h index 2e8ce1e483..0e45df9307 100644 --- a/debuggers/common/midebugsession.h +++ b/debuggers/common/midebugsession.h @@ -1,317 +1,345 @@ /* * Common code for debugger support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef MIDEBUGSESSION_H #define MIDEBUGSESSION_H #include #include "dbgglobal.h" #include "mibreakpointcontroller.h" #include "mi/mi.h" +#include "mi/micommand.h" #include class IExecutePlugin; namespace KDevelop { class ILaunchConfiguration; class ProcessLineMaker; } namespace KDevMI { namespace MI { class CommandQueue; -class MICommand; } class MIDebugger; class STTY; class MIDebugSession : public KDevelop::IDebugSession { Q_OBJECT public: MIDebugSession(); ~MIDebugSession() override; Q_SIGNALS: /** * Emits when received standard output lines from inferior */ void inferiorStdoutLines(const QStringList &lines); /** * Emits when received standard error lines from inferior */ void inferiorStderrLines(const QStringList &lines); void inferiorStopped(const MI::AsyncRecord &r); void inferiorRunning(); /** * Emits when received standard output from debugger for user commands */ void debuggerUserCommandOutput(const QString &output); /** * Emits when received standard output from debugger for internal commands */ void debuggerInternalCommandOutput(const QString &output); /** * Emits when received standard output from inferior's tty */ void inferiorTtyStdout(const QByteArray &output); /** * Emits when received standard output from inferior's tty */ void inferiorTtyStderr(const QByteArray &output); /** * Emits when the debugger instance state changes */ void debuggerStateChanged(DBGStateFlags oldState, DBGStateFlags newState); /** * Emits when there's message needed to be show to user. */ void showMessage(const QString& message, int timeout); /** * Emits when the debugger console view need to be raised. */ void raiseDebuggerConsoleViews(); /** * Emits when need to reset */ void reset(); public: bool debuggerStateIsOn(DBGStateFlags state) const; DBGStateFlags debuggerState() const; bool hasCrashed() const; // BEGIN IDebugSession overrides public: DebuggerState state() const override; bool restartAvaliable() const override; MIBreakpointController * breakpointController() const override = 0; public Q_SLOTS: void restartDebugger() override; void stopDebugger() override; void interruptDebugger() override; void run() override; void runToCursor() override; void jumpToCursor() override; void stepOver() override; void stepIntoInstruction() override; void stepInto() override; void stepOverInstruction() override; void stepOut() override; // END IDebugSession overrides public Q_SLOTS: /** * Run currently executing program to the given \a url and \a line. */ void runUntil(const QUrl& url, int line); /** * Run currently executing program to the given \a address */ void runUntil(const QString& address); /** * Move the execution point of the currently executing program to the given \a url and \a line. */ void jumpTo(const QUrl& url, int line); /** * Move the execution point of the currently executing program to the given \a address. *Note: It can be really very dangerous, so use jumpTo instead. */ void jumpToMemoryAddress(const QString& address); /** * Start the debugger, and execute the inferior program specified by \a cfg. */ bool startDebugging(KDevelop::ILaunchConfiguration *cfg, IExecutePlugin *iexec); /** * Start the debugger, and examine the core file given by \a coreFile. */ bool examineCoreFile(const QUrl &debugee, const QUrl &coreFile); /** * Start the debugger, and attach to a currently running process with the given \a pid. */ bool attachToProcess(int pid); +public: + virtual MI::MICommand *createCommand(MI::CommandType type, const QString& arguments, + MI::CommandFlags flags = 0) const; /** Adds a command to the end of queue of commands to be executed by gdb. The command will be actually sent to gdb only when replies from all previous commands are received and full processed. The literal command sent to gdb is obtained by calling cmd->cmdToSend. The call is made immediately before sending the command, so it's possible to use results of prior commands when computing the exact command to send. */ + void addUserCommand(const QString &cmd); + void addCommand(MI::MICommand* cmd); - /** Same as above, but internally constructs new MI::MICommand - instance from the string. */ - void addCommand(MI::CommandType type, const QString& cmd = QString()); + /** Same as above, but internally constructs MICommand using createCommand() */ + void addCommand(MI::CommandType type, const QString& arguments = QString(), + MI::CommandFlags flags = 0); - void addUserCommand(const QString &cmd); + void addCommand(MI::CommandType type, const QString& arguments, + MI::MICommandHandler* handler, + MI::CommandFlags flags = 0); + + void addCommand(MI::CommandType type, const QString& arguments, + const MI::FunctionCommandHandler::Function& callback, + MI::CommandFlags flags = 0); + + template + void addCommand(MI::CommandType type, const QString& arguments, + Handler* handler_this, + void (Handler::* handler_method)(const MI::ResultRecord&), + MI::CommandFlags flags = 0); protected Q_SLOTS: virtual void slotDebuggerReady(); virtual void slotDebuggerExited(bool abnormal, const QString &msg); virtual void slotInferiorStopped(const MI::AsyncRecord &r); /** * Triggered every time program begins/continues it's execution. */ virtual void slotInferiorRunning(); /** * Handle MI async notifications. */ virtual void processNotification(const MI::AsyncRecord &n); /** Default handler for errors. Tries to guess is the error message is telling that target is gone, if so, informs the user. Otherwise, shows a dialog box and reloads view state. */ virtual void defaultErrorHandler(const MI::ResultRecord &result); /** * Update session state when debugger state changes, and show messages */ virtual void handleDebuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState); void handleNoInferior(const QString &msg); void handleInferiorFinished(const QString &msg); protected: void queueCmd(MI::MICommand *cmd); /** Try to execute next command in the queue. If GDB is not busy with previous command, and there's a command in the queue, sends it. */ void executeCmd(); void ensureDebuggerListening(); void destroyCmds(); /** * Start the debugger instance */ bool startDebugger(KDevelop::ILaunchConfiguration *cfg); /** * MIDebugSession takes the ownership of the created instance. */ virtual MIDebugger *createDebugger() const = 0; /** * Initialize debugger and set default configurations. */ virtual void initializeDebugger() = 0; /** * Further config the debugger and start the inferior program (either local or remote). */ virtual bool execInferior(KDevelop::ILaunchConfiguration *cfg, const QString &executable) = 0; /** * Manipulate debugger instance state */ void setDebuggerStateOn(DBGStateFlags stateOn); void setDebuggerStateOff(DBGStateFlags stateOff); void setDebuggerState(DBGStateFlags newState); void debuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState); /** * Manipulate the session state */ void setSessionState(DebuggerState state); void raiseEvent(event_t e) override; /** Called when there are no pending commands and 'm_stateReloadNeeded' is true. Also can be used to immediately reload program state. Issues commands to completely reload all program state shown to the user. */ void reloadProgramState(); void programNoApp(const QString &msg); void programFinished(const QString &msg); // FIXME: Whether let the debugger source init files when starting, // only used in unit test currently, potentially could be made a user // configurable option void setSourceInitFile(bool enable); private Q_SLOTS: void handleTargetAttach(const MI::ResultRecord& r); void handleCoreFile(const MI::ResultRecord& r); // Pops up a dialog box with some hopefully // detailed information about which state debugger // is in, which commands were sent and so on. void explainDebuggerStatus(); protected: KDevelop::ProcessLineMaker *m_procLineMaker; std::unique_ptr m_commandQueue; // Though the misleading class name, this is the session level state. // see m_debuggerState for debugger instance state DebuggerState m_sessionState; MIDebugger *m_debugger; DBGStateFlags m_debuggerState; bool m_stateReloadInProgress; bool m_stateReloadNeeded; std::unique_ptr m_tty; bool m_hasCrashed; bool m_sourceInitFile; }; +template +void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, + Handler* handler_this, + void (Handler::* handler_method)(const MI::ResultRecord&), + MI::CommandFlags flags) +{ + auto cmd = createCommand(type, arguments, flags); + cmd->setHandler(handler_this, handler_method); + queueCmd(cmd); +} + } // end of namespace KDevMI #endif // MIDEBUGSESSION_H diff --git a/debuggers/common/miframestackmodel.cpp b/debuggers/common/miframestackmodel.cpp index fd0e1f1ff5..d54f883158 100644 --- a/debuggers/common/miframestackmodel.cpp +++ b/debuggers/common/miframestackmodel.cpp @@ -1,158 +1,156 @@ /* * Implementation of thread and frame model that are common to debuggers using MI. * * Copyright 2009 Vladimir Prus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "miframestackmodel.h" #include "midebugsession.h" #include "mi/micommand.h" #include using namespace KDevelop; using namespace KDevMI; using namespace KDevMI::MI; QString getFunctionOrAddress(const Value &frame) { if (frame.hasField("func")) return frame["func"].literal(); else return frame["addr"].literal(); } QPair getSource(const Value &frame) { QPair ret(QString(), -1); if (frame.hasField("fullname")) ret=qMakePair(frame["fullname"].literal(), frame["line"].toInt()-1); else if (frame.hasField("file")) ret=qMakePair(frame["file"].literal(), frame["line"].toInt()-1); else if (frame.hasField("from")) ret.first=frame["from"].literal(); return ret; } MIFrameStackModel::MIFrameStackModel(MIDebugSession * session) : FrameStackModel(session) { } MIDebugSession * MIFrameStackModel::session() { return static_cast(FrameStackModel::session()); } void MIFrameStackModel::fetchThreads() { // TODO: preliminary test shows there might be a bug in lldb-mi // that's causing std::logic_error when executing -thread-info with // more than one threads. Find a workaround for this (and report bug // if it truely is). - session()->addCommand( - new MICommand(ThreadInfo, "", - this, &MIFrameStackModel::handleThreadInfo)); + session()->addCommand(ThreadInfo, "", this, &MIFrameStackModel::handleThreadInfo); } void MIFrameStackModel::handleThreadInfo(const ResultRecord& r) { 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: at least GDB 7.11 is reporting in the right order, // consider sort the list afterwards. QList threadsList; int gidx = threads.size()-1; for (; gidx >= 0; --gidx) { KDevelop::FrameStackModel::ThreadItem i; 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 MICommandHandler { FrameListHandler(MIFrameStackModel* model, int thread, int to) : model(model), m_thread(thread) , m_to(to) {} void handle(const ResultRecord &r) override { const Value& stack = r["stack"]; int first = stack[0]["level"].toInt(); QList frames; for (int i = 0; i< stack.size(); ++i) { const Value& frame = stack[i]; KDevelop::FrameStackModel::FrameItem f; f.nr = frame["level"].toInt(); f.name = getFunctionOrAddress(frame); QPair loc = getSource(frame); f.file = QUrl::fromLocalFile(loc.first); f.line = loc.second; frames << f; } bool hasMore = false; if (!frames.isEmpty()) { if (frames.last().nr == m_to+1) { frames.takeLast(); hasMore = true; } } if (first == 0) { model->setFrames(m_thread, frames); } else { model->insertFrames(m_thread, frames); } model->setHasMoreFrames(m_thread, hasMore); } private: MIFrameStackModel* model; int m_thread; int m_to; }; void MIFrameStackModel::fetchFrames(int threadNumber, int from, int to) { //to+1 so we know if there are more QString arg = QString("%1 %2").arg(from).arg(to+1); - MICommand *c = new MICommand(StackListFrames, arg, - new FrameListHandler(this, threadNumber, to)); + MICommand *c = session()->createCommand(StackListFrames, arg); + c->setHandler(new FrameListHandler(this, threadNumber, to)); c->setThread(threadNumber); session()->addCommand(c); } diff --git a/debuggers/common/mivariable.cpp b/debuggers/common/mivariable.cpp index 21e63e6ad0..cdbf4e4285 100644 --- a/debuggers/common/mivariable.cpp +++ b/debuggers/common/mivariable.cpp @@ -1,385 +1,376 @@ /* * MI based debugger specific Variable * * Copyright 2009 Vladimir Prus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mivariable.h" #include "midebugsession.h" #include "mi/micommand.h" #include #include using namespace KDevelop; using namespace KDevMI; using namespace KDevMI::MI; QMap MIVariable::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; } MIVariable::MIVariable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) : Variable(model, parent, expression, display) { } MIVariable::~MIVariable() { if (!varobj_.isEmpty()) { // Delete only top-level variable objects. if (topLevel()) { if (hasStartedSession()) { IDebugSession* is = ICore::self()->debugController()->currentSession(); MIDebugSession * s = static_cast(is); - s->addCommand(new MICommand(VarDelete, - QString("\"%1\"").arg(varobj_))); + s->addCommand(VarDelete, QString("\"%1\"").arg(varobj_)); } } allVariables_.remove(varobj_); } } MIVariable* MIVariable::findByVarobjName(const QString& varobjName) { if (allVariables_.count(varobjName) == 0) return 0; return allVariables_[varobjName]; } void MIVariable::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 MICommandHandler { public: CreateVarobjHandler(MIVariable *variable, QObject *callback, const char *callbackMethod) : m_variable(variable), m_callback(callback), m_callbackMethod(callbackMethod) {} void handle(const ResultRecord &r) override { if (!m_variable) return; bool hasValue = false; MIVariable* variable = m_variable.data(); variable->deleteChildren(); variable->setInScope(true); if (r.reason == "error") { variable->setShowError(true); } else { variable->setVarobj(r["name"].literal()); bool hasMore = false; if (r.hasField("has_more") && r["has_more"].toInt()) // GDB swears there are more children. Trust it hasMore = true; else // There are no more children in addition to what // numchild reports. But, in KDevelop, the variable // is not yet expanded, and those numchild are not // fetched yet. So, if numchild != 0, hasMore should // be true. hasMore = r["numchild"].toInt() != 0; variable->setHasMore(hasMore); variable->setType(r["type"].literal()); variable->setValue(r["value"].literal()); hasValue = !r["value"].literal().isEmpty(); if (variable->isExpanded() && r["numchild"].toInt()) { variable->fetchMoreChildren(); } if (variable->format() != KDevelop::Variable::Natural) { //TODO doesn't work for children as they are not yet loaded variable->formatChanged(); } } if (m_callback && m_callbackMethod) { QMetaObject::invokeMethod(m_callback, m_callbackMethod, Q_ARG(bool, hasValue)); } } bool handlesError() override { return true; } private: QPointer m_variable; QObject *m_callback; const char *m_callbackMethod; }; void MIVariable::attachMaybe(QObject *callback, const char *callbackMethod) { if (!varobj_.isEmpty()) return; if (hasStartedSession()) { IDebugSession* is = ICore::self()->debugController()->currentSession(); MIDebugSession * s = static_cast(is); - s->addCommand( - new MICommand( - VarCreate, - QString("var%1 @ %2").arg(nextId++).arg(enquotedExpression()), - new CreateVarobjHandler(this, callback, callbackMethod))); + s->addCommand(VarCreate, + QString("var%1 @ %2").arg(nextId++).arg(enquotedExpression()), + new CreateVarobjHandler(this, callback, callbackMethod)); } } void MIVariable::markAllDead() { QMap::iterator i, e; for (i = allVariables_.begin(), e = allVariables_.end(); i != e; ++i) { i.value()->varobj_.clear(); } allVariables_.clear(); } class FetchMoreChildrenHandler : public MICommandHandler { public: FetchMoreChildrenHandler(MIVariable *variable, MIDebugSession *session) : m_variable(variable), m_session(session), m_activeCommands(1) {} void handle(const ResultRecord &r) override { if (!m_variable) return; --m_activeCommands; MIVariable* variable = m_variable.data(); if (r.hasField("children")) { const Value& children = r["children"]; for (int i = 0; i < children.size(); ++i) { const Value& child = children[i]; const QString& exp = child["exp"].literal(); if (exp == "public" || exp == "protected" || exp == "private") { ++m_activeCommands; - m_session->addCommand( - new MICommand(VarListChildren, - QString("--all-values \"%1\"") - .arg(child["name"].literal()), - this/*use again as handler*/)); + m_session->addCommand(VarListChildren, + QString("--all-values \"%1\"").arg(child["name"].literal()), + this/*use again as handler*/); } else { KDevelop::Variable* xvar = m_session->variableController()-> createVariable(variable->model(), variable, child["exp"].literal()); MIVariable* var = static_cast(xvar); var->setTopLevel(false); var->setVarobj(child["name"].literal()); bool hasMore = child["numchild"].toInt() != 0 || ( child.hasField("dynamic") && child["dynamic"].toInt()!=0 ); var->setHasMoreInitial(hasMore); variable->appendChild(var); var->setType(child["type"].literal()); var->setValue(child["value"].literal()); } } } /* Note that we don't set hasMore to true if there are still active commands. The reason is that we don't want the user to have even theoretical ability to click on "..." item and confuse us. */ bool hasMore = false; if (r.hasField("has_more")) hasMore = r["has_more"].toInt(); variable->setHasMore(hasMore); if (m_activeCommands == 0) { variable->emitAllChildrenFetched(); delete this; } } bool handlesError() override { // FIXME: handle error? return false; } bool autoDelete() override { // we delete ourselve return false; } private: QPointer m_variable; MIDebugSession *m_session; int m_activeCommands; }; void MIVariable::fetchMoreChildren() { int c = childItems.size(); // FIXME: should not even try this if app is not started. // Probably need to disable open, or something if (hasStartedSession()) { IDebugSession* is = ICore::self()->debugController()->currentSession(); MIDebugSession * s = static_cast(is); - s->addCommand( - new MICommand(VarListChildren, - QString("--all-values \"%1\" %2 %3").arg(varobj_) - .arg( c ).arg( c + fetchStep ), // fetch from .. to .. - new FetchMoreChildrenHandler(this, s))); + s->addCommand(VarListChildren, + QString("--all-values \"%1\" %2 %3") + .arg(varobj_).arg( c ).arg( c + fetchStep ), // fetch from .. to .. + new FetchMoreChildrenHandler(this, s)); } } void MIVariable::handleUpdate(const Value& var) { if (var.hasField("type_changed") && var["type_changed"].literal() == "true") { deleteChildren(); // FIXME: verify that this check is right. setHasMore(var["new_num_children"].toInt() != 0); fetchMoreChildren(); } if (var.hasField("in_scope") && var["in_scope"].literal() == "false") { setInScope(false); } else { setInScope(true); if (var.hasField("new_num_children")) { int nc = var["new_num_children"].toInt(); Q_ASSERT(nc != -1); setHasMore(false); while (childCount() > nc) { TreeItem *c = child(childCount()-1); removeChild(childCount()-1); delete c; } } // FIXME: the below code is essentially copy-paste from // FetchMoreChildrenHandler. We need to introduce GDB-specific // subclass of KDevelop::Variable that is capable of creating // itself from MI output directly, and relay to that. if (var.hasField("new_children")) { const Value& children = var["new_children"]; for (int i = 0; i < children.size(); ++i) { const Value& child = children[i]; const QString& exp = child["exp"].literal(); IDebugSession* is = ICore::self()->debugController()->currentSession(); MIDebugSession * s = static_cast(is); KDevelop::Variable* xvar = s->variableController()-> createVariable(model(), this, exp); MIVariable* var = static_cast(xvar); var->setTopLevel(false); var->setVarobj(child["name"].literal()); bool hasMore = child["numchild"].toInt() != 0 || ( child.hasField("dynamic") && child["dynamic"].toInt()!=0 ); var->setHasMoreInitial(hasMore); appendChild(var); var->setType(child["type"].literal()); var->setValue(child["value"].literal()); var->setChanged(true); } } if (var.hasField("type_changed") && var["type_changed"].literal() == "true") { setType(var["new_type"].literal()); } setValue(var["value"].literal()); setChanged(true); setHasMore(var.hasField("has_more") && var["has_more"].toInt()); } } const QString& MIVariable::varobj() const { return varobj_; } QString MIVariable::enquotedExpression() const { QString expr = expression(); expr.replace('"', "\\\""); expr = expr.prepend('"').append('"'); return expr; } class SetFormatHandler : public MICommandHandler { public: SetFormatHandler(MIVariable *var) : m_variable(var) {} void handle(const ResultRecord &r) override { if(r.hasField("value")) m_variable.data()->setValue(r["value"].literal()); } private: QPointer m_variable; }; void MIVariable::formatChanged() { if(childCount()) { foreach(TreeItem* item, childItems) { Q_ASSERT(dynamic_cast(item)); if( MIVariable* var=dynamic_cast(item)) var->setFormat(format()); } } else { if (hasStartedSession()) { IDebugSession* is = ICore::self()->debugController()->currentSession(); MIDebugSession * s = static_cast(is); - s->addCommand( - new MICommand(VarSetFormat, - QString(" \"%1\" %2 ").arg(varobj_).arg(format2str(format())), - new SetFormatHandler(this) - ) - ); + s->addCommand(VarSetFormat, + QString(" \"%1\" %2 ").arg(varobj_).arg(format2str(format())), + new SetFormatHandler(this)); } } } diff --git a/debuggers/common/mivariablecontroller.cpp b/debuggers/common/mivariablecontroller.cpp index dec37c7848..a92de48e86 100644 --- a/debuggers/common/mivariablecontroller.cpp +++ b/debuggers/common/mivariablecontroller.cpp @@ -1,260 +1,255 @@ /* * GDB Debugger Support * * Copyright 2007 Hamish Rodda * Copyright 2008 Vladimir Prus * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mivariablecontroller.h" #include "debuglog.h" #include "midebugsession.h" #include "mivariable.h" #include "mi/mi.h" #include "mi/micommand.h" #include "stringhelpers.h" #include #include #include #include #include #include using namespace KDevelop; using namespace KDevMI; using namespace KDevMI::MI; using KTextEditor::Cursor; using KTextEditor::Document; using KTextEditor::Range; MIVariableController::MIVariableController(MIDebugSession *parent) : IVariableController(parent) { Q_ASSERT(parent); connect(parent, &MIDebugSession::inferiorStopped, this, &MIVariableController::programStopped); connect(parent, &MIDebugSession::stateChanged, this, &MIVariableController::stateChanged); } MIDebugSession *MIVariableController::debugSession() const { return static_cast(const_cast(QObject::parent())); } void MIVariableController::programStopped(const AsyncRecord& r) { if (debugSession()->debuggerStateIsOn(s_shuttingDown)) return; if (r.hasField("reason") && r["reason"].literal() == "function-finished" && r.hasField("gdb-result-var")) { variableCollection()->watches()->addFinishResult(r["gdb-result-var"].literal()); } else { variableCollection()->watches()->removeFinishResult(); } } void MIVariableController::update() { qCDebug(DEBUGGERGDB) << autoUpdate(); if (autoUpdate() & UpdateWatches) { variableCollection()->watches()->reinstall(); } if (autoUpdate() & UpdateLocals) { updateLocals(); } if ((autoUpdate() & UpdateLocals) || ((autoUpdate() & UpdateWatches) && variableCollection()->watches()->childCount() > 0)) { - debugSession()->addCommand( - new MICommand(VarUpdate, "--all-values *", this, - &MIVariableController::handleVarUpdate)); + debugSession()->addCommand(VarUpdate, "--all-values *", this, + &MIVariableController::handleVarUpdate); } } void MIVariableController::handleVarUpdate(const ResultRecord& r) { const Value& changed = r["changelist"]; for (int i = 0; i < changed.size(); ++i) { const Value& var = changed[i]; MIVariable* v = MIVariable::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 MICommandHandler { public: StackListArgumentsHandler(QStringList localsName) : m_localsName(localsName) {} void handle(const ResultRecord &r) override { if (!KDevelop::ICore::self()->debugController()) return; //happens on shutdown // FIXME: handle error. const Value& locals = r["stack-args"][0]["args"]; for (int i = 0; i < locals.size(); i++) { m_localsName << locals[i].literal(); } QList variables = KDevelop::ICore::self()->debugController()->variableCollection() ->locals()->updateLocals(m_localsName); foreach (Variable *v, variables) { v->attachMaybe(); } } private: QStringList m_localsName; }; class StackListLocalsHandler : public MICommandHandler { public: StackListLocalsHandler(MIDebugSession *session) : m_session(session) {} void handle(const ResultRecord &r) override { // FIXME: handle error. const Value& locals = r["locals"]; QStringList localsName; for (int i = 0; i < locals.size(); i++) { const Value& var = locals[i]; localsName << var["name"].literal(); } int frame = m_session->frameStackModel()->currentFrame(); - m_session->addCommand( //dont'show value, low-frame, high-frame - new MICommand(StackListArguments, QString("0 %1 %2").arg(frame).arg(frame), - new StackListArgumentsHandler(localsName))); + m_session->addCommand(StackListArguments, + //dont'show value, low-frame, high-frame + QString("0 %1 %2").arg(frame).arg(frame), + new StackListArgumentsHandler(localsName)); } private: MIDebugSession *m_session; }; void MIVariableController::updateLocals() { - debugSession()->addCommand( - new MICommand(StackListLocals, "--simple-values", - new StackListLocalsHandler(debugSession()))); + debugSession()->addCommand(StackListLocals, "--simple-values", + new StackListLocalsHandler(debugSession())); } Range MIVariableController::expressionRangeUnderCursor(Document* doc, const Cursor& cursor) { QString line = doc->line(cursor.line()); int index = cursor.column(); QChar c = line[index]; if (!c.isLetterOrNumber() && c != '_') return {}; int start = Utils::expressionAt(line, index+1); int end = index; for (; end < line.size(); ++end) { QChar c = line[end]; if (!(c.isLetterOrNumber() || c == '_')) break; } if (!(start < end)) return {}; return { Cursor{cursor.line(), start}, Cursor{cursor.line(), end} }; } void MIVariableController::addWatch(KDevelop::Variable* variable) { // FIXME: should add async 'get full expression' method // to MIVariable, not poke at varobj. In that case, // we will be able to make addWatch a generic method, not // gdb-specific one. if (MIVariable *gv = dynamic_cast(variable)) { - debugSession()->addCommand( - new MICommand(VarInfoPathExpression, - gv->varobj(), - this, - &MIVariableController::addWatch)); + debugSession()->addCommand(VarInfoPathExpression, + gv->varobj(), + this, &MIVariableController::addWatch); } } void MIVariableController::addWatchpoint(KDevelop::Variable* variable) { // FIXME: should add async 'get full expression' method // to MIVariable, not poke at varobj. In that case, // we will be able to make addWatchpoint a generic method, not // gdb-specific one. if (MIVariable *gv = dynamic_cast(variable)) { - debugSession()->addCommand( - new MICommand(VarInfoPathExpression, - gv->varobj(), - this, - &MIVariableController::addWatchpoint)); + debugSession()->addCommand(VarInfoPathExpression, + gv->varobj(), + this, &MIVariableController::addWatchpoint); } } void MIVariableController::addWatch(const ResultRecord& r) { // FIXME: handle error. if (r.reason == "done" && !r["path_expr"].literal().isEmpty()) { variableCollection()->watches()->add(r["path_expr"].literal()); } } void MIVariableController::addWatchpoint(const ResultRecord& r) { if (r.reason == "done" && !r["path_expr"].literal().isEmpty()) { KDevelop::ICore::self()->debugController()->breakpointModel()->addWatchpoint(r["path_expr"].literal()); } } Variable* MIVariableController::createVariable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) { return new MIVariable(model, parent, expression, display); } void MIVariableController::handleEvent(IDebugSession::event_t event) { IVariableController::handleEvent(event); } void MIVariableController::stateChanged(IDebugSession::DebuggerState state) { if (state == IDebugSession::EndedState) { MIVariable::markAllDead(); } } diff --git a/debuggers/gdb/debugsession.cpp b/debuggers/gdb/debugsession.cpp index bba068b9b7..1f337a56ff 100644 --- a/debuggers/gdb/debugsession.cpp +++ b/debuggers/gdb/debugsession.cpp @@ -1,278 +1,278 @@ /* * GDB Debugger Support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "debugsession.h" #include "debuglog.h" #include "gdb.h" #include "gdbbreakpointcontroller.h" #include "gdbframestackmodel.h" #include "mi/micommand.h" #include "stty.h" #include "variablecontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevMI::GDB; using namespace KDevMI::MI; using namespace KDevelop; DebugSession::DebugSession() : MIDebugSession() , m_breakpointController(nullptr) , m_variableController(nullptr) , m_frameStackModel(nullptr) { m_breakpointController = new BreakpointController(this); m_variableController = new VariableController(this); m_frameStackModel = new GdbFrameStackModel(this); } DebugSession::~DebugSession() { } BreakpointController *DebugSession::breakpointController() const { return m_breakpointController; } VariableController *DebugSession::variableController() const { return m_variableController; } GdbFrameStackModel *DebugSession::frameStackModel() const { return m_frameStackModel; } GdbDebugger *DebugSession::createDebugger() const { return new GdbDebugger; } void DebugSession::initializeDebugger() { - //queueCmd(new GDBCommand(GDBMI::EnableTimings, "yes")); + //addCommand(new GDBCommand(GDBMI::EnableTimings, "yes")); - queueCmd(new CliCommand(MI::GdbShow, "version", this, &DebugSession::handleVersion)); + addCommand(new CliCommand(MI::GdbShow, "version", this, &DebugSession::handleVersion)); // This makes gdb pump a variable out on one line. - queueCmd(new MICommand(MI::GdbSet, "width 0")); - queueCmd(new MICommand(MI::GdbSet, "height 0")); + addCommand(MI::GdbSet, "width 0"); + addCommand(MI::GdbSet, "height 0"); - 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")); + addCommand(MI::SignalHandle, "SIG32 pass nostop noprint"); + addCommand(MI::SignalHandle, "SIG41 pass nostop noprint"); + addCommand(MI::SignalHandle, "SIG42 pass nostop noprint"); + addCommand(MI::SignalHandle, "SIG43 pass nostop noprint"); - queueCmd(new MICommand(MI::EnablePrettyPrinting)); + addCommand(MI::EnablePrettyPrinting); - queueCmd(new MICommand(MI::GdbSet, "charset UTF-8")); - queueCmd(new MICommand(MI::GdbSet, "print sevenbit-strings off")); + addCommand(MI::GdbSet, "charset UTF-8"); + addCommand(MI::GdbSet, "print sevenbit-strings off"); QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevgdb/printers/gdbinit"); if (!fileName.isEmpty()) { QFileInfo fileInfo(fileName); QString quotedPrintersPath = fileInfo.dir().path() .replace('\\', "\\\\") .replace('"', "\\\""); - queueCmd(new MICommand(MI::NonMI, - QString("python sys.path.insert(0, \"%0\")").arg(quotedPrintersPath))); - queueCmd(new MICommand(MI::NonMI, "source " + fileName)); + addCommand(MI::NonMI, + QString("python sys.path.insert(0, \"%0\")").arg(quotedPrintersPath)); + addCommand(MI::NonMI, "source " + fileName); } qCDebug(DEBUGGERGDB) << "Initialized GDB"; } void DebugSession::configure(ILaunchConfiguration *cfg) { // Read Configuration values KConfigGroup grp = cfg->config(); bool breakOnStart = grp.readEntry(KDevMI::breakOnStartEntry, false); bool displayStaticMembers = grp.readEntry(KDevMI::staticMembersEntry, false); bool asmDemangle = grp.readEntry(KDevMI::demangleNamesEntry, true); if (breakOnStart) { BreakpointModel* m = ICore::self()->debugController()->breakpointModel(); bool found = false; foreach (Breakpoint *b, m->breakpoints()) { if (b->location() == "main") { found = true; break; } } if (!found) { m->addCodeBreakpoint("main"); } } // Needed so that breakpoint widget has a chance to insert breakpoints. // FIXME: a bit hacky, as we're really not ready for new commands. setDebuggerStateOn(s_dbgBusy); raiseEvent(debugger_ready); if (displayStaticMembers) { - queueCmd(new MICommand(MI::GdbSet, "print static-members on")); + addCommand(MI::GdbSet, "print static-members on"); } else { - queueCmd(new MICommand(MI::GdbSet, "print static-members off")); + addCommand(MI::GdbSet, "print static-members off"); } if (asmDemangle) { - queueCmd(new MICommand(MI::GdbSet, "print asm-demangle on")); + addCommand(MI::GdbSet, "print asm-demangle on"); } else { - queueCmd(new MICommand(MI::GdbSet, "print asm-demangle off")); + addCommand(MI::GdbSet, "print asm-demangle off"); } qCDebug(DEBUGGERGDB) << "Per inferior configuration done"; } bool DebugSession::execInferior(ILaunchConfiguration *cfg, const QString &executable) { qCDebug(DEBUGGERGDB) << "Executing inferior"; // debugger specific config configure(cfg); KConfigGroup grp = cfg->config(); QUrl configGdbScript = grp.readEntry(KDevMI::remoteGdbConfigEntry, QUrl()); QUrl runShellScript = grp.readEntry(KDevMI::remoteGdbShellEntry, QUrl()); QUrl runGdbScript = grp.readEntry(KDevMI::remoteGdbRunEntry, QUrl()); // handle remote debug if (configGdbScript.isValid()) { - queueCmd(new MICommand(MI::NonMI, "source " + KShell::quoteArg(configGdbScript.toLocalFile()))); + addCommand(MI::NonMI, "source " + KShell::quoteArg(configGdbScript.toLocalFile())); } // FIXME: have a check box option that controls remote debugging if (runShellScript.isValid()) { // Special for remote debug, the remote inferior is started by this shell script QByteArray tty(m_tty->getSlave().toLatin1()); QByteArray options = QByteArray(">") + tty + QByteArray(" 2>&1 <") + tty; QProcess *proc = new QProcess; KDevelop::restoreSystemEnvironment(proc); QStringList arguments; arguments << "-c" << KShell::quoteArg(runShellScript.toLocalFile()) + ' ' + KShell::quoteArg(executable) + QString::fromLatin1(options); qCDebug(DEBUGGERGDB) << "starting sh" << arguments; proc->start("sh", arguments); //PORTING TODO QProcess::DontCare); } if (runGdbScript.isValid()) { // Special for remote debug, gdb script at run is requested, to connect to remote inferior // Race notice: wait for the remote gdbserver/executable // - but that might be an issue for this script to handle... // Note: script could contain "run" or "continue" // Future: the shell script should be able to pass info (like pid) // to the gdb script... - queueCmd(new SentinelCommand([this, runGdbScript]() { + addCommand(new SentinelCommand([this, runGdbScript]() { breakpointController()->initSendBreakpoints(); breakpointController()->setDeleteDuplicateBreakpoints(true); qCDebug(DEBUGGERGDB) << "Running gdb script " << KShell::quoteArg(runGdbScript.toLocalFile()); - queueCmd(new MICommand(MI::NonMI, "source " + KShell::quoteArg(runGdbScript.toLocalFile()), - [this](const MI::ResultRecord&) { - breakpointController()->setDeleteDuplicateBreakpoints(false); - }, - CmdMaybeStartsRunning)); + addCommand(MI::NonMI, "source " + KShell::quoteArg(runGdbScript.toLocalFile()), + [this](const MI::ResultRecord&) { + breakpointController()->setDeleteDuplicateBreakpoints(false); + }, + CmdMaybeStartsRunning); raiseEvent(connected_to_program); }, CmdMaybeStartsRunning)); } else { // normal local debugging - queueCmd(new MICommand(MI::FileExecAndSymbols, KShell::quoteArg(executable), - this, &DebugSession::handleFileExecAndSymbols, - CmdHandlesError)); + addCommand(MI::FileExecAndSymbols, KShell::quoteArg(executable), + this, &DebugSession::handleFileExecAndSymbols, + CmdHandlesError); raiseEvent(connected_to_program); - queueCmd(new SentinelCommand([this]() { + addCommand(new SentinelCommand([this]() { breakpointController()->initSendBreakpoints(); - queueCmd(new MICommand(MI::ExecRun, QString(), CmdMaybeStartsRunning)); + addCommand(MI::ExecRun, QString(), CmdMaybeStartsRunning); }, CmdMaybeStartsRunning)); } return true; } void DebugSession::handleVersion(const QStringList& s) { qCDebug(DEBUGGERGDB) << s.first(); // minimal version is 7.0,0 QRegExp rx("([7-9]+)\\.([0-9]+)(\\.([0-9]+))?"); int idx = rx.indexIn(s.first()); if (idx == -1) { if (!qobject_cast(qApp)) { //for unittest qFatal("You need a graphical application."); } KMessageBox::error( qApp->activeWindow(), i18n("You need gdb 7.0.0 or higher.
" "You are using: %1", s.first()), i18n("gdb error")); stopDebugger(); } } void DebugSession::handleFileExecAndSymbols(const MI::ResultRecord& r) { if (r.reason == "error") { KMessageBox::error( qApp->activeWindow(), i18n("Could not start debugger:
")+ r["msg"].literal(), i18n("Startup error")); stopDebugger(); } } diff --git a/debuggers/gdb/disassemblewidget.cpp b/debuggers/gdb/disassemblewidget.cpp index e6e3ddf484..41068fcf44 100644 --- a/debuggers/gdb/disassemblewidget.cpp +++ b/debuggers/gdb/disassemblewidget.cpp @@ -1,437 +1,437 @@ /* * 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 "debuggerplugin.h" #include "debuglog.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 using namespace KDevMI::GDB; using namespace KDevMI::MI; SelectAddressDialog::SelectAddressDialog(QWidget* parent) : QDialog(parent) { m_ui.setupUi(this); setWindowTitle(i18n("Address Selector")); connect(m_ui.comboBox, &KHistoryComboBox::editTextChanged, this, &SelectAddressDialog::validateInput); connect(m_ui.comboBox, static_cast(&KHistoryComboBox::returnPressed), this, &SelectAddressDialog::itemSelected); } QString SelectAddressDialog::address() const { return hasValidAddress() ? m_ui.comboBox->currentText() : QString(); } bool SelectAddressDialog::hasValidAddress() const { bool ok; m_ui.comboBox->currentText().toLongLong(&ok, 16); return ok; } void SelectAddressDialog::updateOkState() { m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(hasValidAddress()); } void SelectAddressDialog::validateInput() { updateOkState(); } void SelectAddressDialog::itemSelected() { QString text = m_ui.comboBox->currentText(); if( hasValidAddress() && m_ui.comboBox->findText(text) < 0 ) m_ui.comboBox->addItem(text); } DisassembleWindow::DisassembleWindow(QWidget *parent, DisassembleWidget* widget) : QTreeWidget(parent) { /*context menu commands */{ m_selectAddrAction = new QAction(i18n("Change &address"), this); m_selectAddrAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(m_selectAddrAction, &QAction::triggered, widget, &DisassembleWidget::slotChangeAddress); m_jumpToLocation = new QAction(QIcon::fromTheme("debug-execute-to-cursor"), i18n("&Jump to Cursor"), this); m_jumpToLocation->setWhatsThis(i18n("Sets the execution pointer to the current cursor position.")); connect(m_jumpToLocation,&QAction::triggered, widget, &DisassembleWidget::jumpToCursor); m_runUntilCursor = new QAction(QIcon::fromTheme("debug-run-cursor"), i18n("&Run to Cursor"), this); m_runUntilCursor->setWhatsThis(i18n("Continues execution until the cursor position is reached.")); connect(m_runUntilCursor,&QAction::triggered, widget, &DisassembleWidget::runToCursor); } } 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 MICommand(DataDisassemble, "-s \"$pc\" -e \"$pc+1\" -- 0", this, &DisassembleWidget::updateExecutionAddressHandler ) ); + s->addCommand(DataDisassemble, "-s \"$pc\" -e \"$pc+1\" -- 0", + this, &DisassembleWidget::updateExecutionAddressHandler); }else{ QString cmd = (to.isEmpty())? QString("-s %1 -e \"%1 + 256\" -- 0").arg(from ): QString("-s %1 -e %2+1 -- 0").arg(from).arg(to); // if both addr set - s->addCommand( - new MICommand(DataDisassemble, cmd, this, &DisassembleWidget::disassembleMemoryHandler ) ); + s->addCommand(DataDisassemble, cmd, + this, &DisassembleWidget::disassembleMemoryHandler); } } /***************************************************************************/ void DisassembleWidget::disassembleMemoryHandler(const ResultRecord& r) { const Value& content = r["asm_insns"]; QString currentFunction; m_disassembleWindow->clear(); for(int i = 0; i < content.size(); ++i) { const Value& line = content[i]; QString addr, fct, offs, inst; if( line.hasField("address") ) addr = line["address"].literal(); if( line.hasField("func-name") ) fct = line["func-name"].literal(); if( line.hasField("offset") ) offs = line["offset"].literal(); if( line.hasField("inst") ) inst = line["inst"].literal(); //We use offset at the same column where function is. if(currentFunction == fct){ if(!fct.isEmpty()){ fct = QString("+") + offs; } }else { currentFunction = fct; } m_disassembleWindow->addTopLevelItem(new QTreeWidgetItem(m_disassembleWindow, QStringList() << QString() << addr << fct << inst)); if (i == 0) { lower_ = addr.toULong(&ok,16); } else if (i == content.size()-1) { upper_ = addr.toULong(&ok,16); } } displayCurrent(); m_disassembleWindow->resizeColumnToContents(Icon); // make Icon always visible m_disassembleWindow->resizeColumnToContents(Address); // make entire address always visible } void DisassembleWidget::showEvent(QShowEvent*) { slotActivate(true); //it doesn't work for large names of functions // for (int i = 0; i < m_disassembleWindow->model()->columnCount(); ++i) // m_disassembleWindow->resizeColumnToContents(i); } void DisassembleWidget::hideEvent(QHideEvent*) { slotActivate(false); } void DisassembleWidget::slotDeactivate() { slotActivate(false); } void DisassembleWidget::enableControls(bool enabled) { m_disassembleWindow->setEnabled(enabled); } void DisassembleWidget::slotChangeAddress() { if(!m_dlg) return; m_dlg->updateOkState(); if (!m_disassembleWindow->selectedItems().isEmpty()) { m_dlg->setAddress(m_disassembleWindow->selectedItems().first()->text(Address)); } if (m_dlg->exec() == QDialog::Rejected) return; unsigned long addr = m_dlg->address().toULong(&ok,16); if (addr < lower_ || addr > upper_ || !displayCurrent()) disassembleMemoryRegion(m_dlg->address()); } void SelectAddressDialog::setAddress ( const QString& address ) { m_ui.comboBox->setCurrentItem ( address, true ); } void DisassembleWidget::update(const QString &address) { if (!active_) { return; } address_ = address.toULong(&ok, 16); if (!displayCurrent()) { disassembleMemoryRegion(); } m_registersManager->updateRegisters(); } diff --git a/debuggers/gdb/registers/registercontroller.cpp b/debuggers/gdb/registers/registercontroller.cpp index 629e17f486..fc56026fec 100644 --- a/debuggers/gdb/registers/registercontroller.cpp +++ b/debuggers/gdb/registers/registercontroller.cpp @@ -1,407 +1,406 @@ /* * 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 "converters.h" #include "debuglog.h" #include "mi/mi.h" #include "mi/micommand.h" #include "../debugsession.h" #include #include using namespace KDevMI::MI; using namespace KDevMI::GDB; void IRegisterController::setSession(DebugSession* debugSession) { m_debugSession = debugSession; } void IRegisterController::updateRegisters(const GroupsName& group) { if (!m_debugSession || m_debugSession->debuggerStateIsOn(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 ResultRecord&); if (group.type() == structured && currentFormat != Raw) { handler = &IRegisterController::structuredRegistersHandler; } else { handler = &IRegisterController::generalRegistersHandler; } - m_debugSession->addCommand(new MICommand(DataListRegisterValues, registers, this, handler)); + m_debugSession->addCommand(DataListRegisterValues, registers, this, handler); } void IRegisterController::registerNamesHandler(const ResultRecord& r) { const Value& names = r["register-names"]; m_rawRegisterNames.clear(); for (int i = 0; i < names.size(); ++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 ResultRecord& r) { Q_ASSERT(!m_rawRegisterNames.isEmpty()); QString registerName; const Value& values = r["register-values"]; for (int i = 0; i < values.size(); ++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->debuggerStateIsOn(s_dbgNotStarted | s_shuttingDown)) { return false; } - m_debugSession->addCommand( - new MICommand(DataListRegisterNames, "", this, &IRegisterController::registerNamesHandler)); + m_debugSession->addCommand(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->debuggerStateIsOn(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 MICommand(NonMI, command)); + m_debugSession->addCommand(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 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 Value& values = r["register-values"]; Q_ASSERT(!m_rawRegisterNames.isEmpty()); for (int i = 0; i < values.size(); ++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 be579835d8..b881b278b0 100644 --- a/debuggers/gdb/registers/registersmanager.cpp +++ b/debuggers/gdb/registers/registersmanager.cpp @@ -1,171 +1,170 @@ /* * 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_arm.h" #include "registercontroller_x86.h" #include "registersview.h" #include "debuglog.h" #include "mi/micommand.h" #include "modelsmanager.h" #include "../debugsession.h" using namespace KDevMI::MI; using namespace KDevMI::GDB; 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 ResultRecord& r) { const Value& names = r["register-names"]; m_registerNames.clear(); for (int i = 0; i < names.size(); ++i) { const Value& entry = names[i]; if (!entry.literal().isEmpty()) { m_registerNames << entry.literal(); } } parseArchitecture(); } void ArchitectureParser::determineArchitecture(DebugSession* debugSession) { if (!debugSession || debugSession->debuggerStateIsOn(s_dbgNotStarted | s_shuttingDown)) { return; } - debugSession->addCommand( - new MICommand(DataListRegisterNames, "", this, &ArchitectureParser::registerNamesHandler)); + debugSession->addCommand(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->debuggerStateIsOn(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 2761cd5bcb..634150b433 100644 --- a/debuggers/gdb/unittests/test_gdb.cpp +++ b/debuggers/gdb/unittests/test_gdb.cpp @@ -1,2036 +1,2036 @@ /* Copyright 2009 Niko Sams Copyright 2013 Vlas Puhov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_gdb.h" #include "debugsession.h" #include "gdbframestackmodel.h" #include "mi/micommand.h" #include "mi/milexer.h" #include "mi/miparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using KDevelop::AutoTestShell; namespace KDevMI { namespace GDB { QUrl findExecutable(const QString& name) { QFileInfo info(qApp->applicationDirPath() + "/unittests/" + name); Q_ASSERT(info.exists()); Q_ASSERT(info.isExecutable()); return QUrl::fromLocalFile(info.canonicalFilePath()); } QString findSourceFile(const QString& name) { QFileInfo info(QFileInfo(__FILE__).dir().absoluteFilePath(name)); Q_ASSERT(info.exists()); return info.canonicalFilePath(); } static bool isAttachForbidden(const char * file, int line) { // if on linux, ensure we can actually attach QFile canRun("/proc/sys/kernel/yama/ptrace_scope"); if (canRun.exists()) { if (!canRun.open(QIODevice::ReadOnly)) { QTest::qFail("Something is wrong: /proc/sys/kernel/yama/ptrace_scope exists but cannot be read", file, line); return true; } if (canRun.read(1).toInt() != 0) { QTest::qSkip("ptrace attaching not allowed, skipping test. To enable it, set /proc/sys/kernel/yama/ptrace_scope to 0.", file, line); return true; } } return false; } #define SKIP_IF_ATTACH_FORBIDDEN() \ do { \ if (isAttachForbidden(__FILE__, __LINE__)) \ return; \ } while(0) void GdbTest::initTestCase() { AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); m_iface = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IExecutePlugin", "kdevexecute")->extension(); Q_ASSERT(m_iface); } void GdbTest::cleanupTestCase() { KDevelop::TestCore::shutdown(); } void GdbTest::init() { //remove all breakpoints - so we can set our own in the test KConfigGroup breakpoints = KSharedConfig::openConfig()->group("breakpoints"); breakpoints.writeEntry("number", 0); breakpoints.sync(); KDevelop::BreakpointModel* m = KDevelop::ICore::self()->debugController()->breakpointModel(); m->removeRows(0, m->rowCount()); KDevelop::VariableCollection *vc = KDevelop::ICore::self()->debugController()->variableCollection(); for (int i=0; i < vc->watches()->childCount(); ++i) { delete vc->watches()->child(i); } vc->watches()->clear(); } class 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 GdbFrameStackModel { public: TestFrameStackModel(DebugSession* session) : GdbFrameStackModel(session), fetchFramesCalled(0), fetchThreadsCalled(0) {} int fetchFramesCalled; int fetchThreadsCalled; void fetchFrames(int threadNumber, int from, int to) override { fetchFramesCalled++; GdbFrameStackModel::fetchFrames(threadNumber, from, to); } void fetchThreads() override { fetchThreadsCalled++; GdbFrameStackModel::fetchThreads(); } }; class TestDebugSession : public DebugSession { Q_OBJECT public: TestDebugSession() : DebugSession() { setSourceInitFile(false); 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, &TestDebugSession::inferiorStdoutLines); TestLaunchConfiguration cfg; session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); { QCOMPARE(outputSpy.count(), 1); QList arguments = outputSpy.takeFirst(); QCOMPARE(arguments.count(), 1); QCOMPARE(arguments.first().toStringList(), QStringList() << "Hello, world!" << "Hello"); } } void GdbTest::testBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); } void GdbTest::testDisableBreakpoint() { //Description: We must stop only on the third breakpoint int firstBreakLine=28; int secondBreakLine=23; int thirdBreakLine=24; int fourthBreakLine=31; TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b; b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), firstBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //this is needed to emulate debug from GUI. If we are in edit mode, the debugSession doesn't exist. KDevelop::ICore::self()->debugController()->breakpointModel()->blockSignals(true); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), secondBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //all disabled breakpoints were added KDevelop::Breakpoint * thirdBreak = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), thirdBreakLine); KDevelop::ICore::self()->debugController()->breakpointModel()->blockSignals(false); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), thirdBreak->line()); //disable existing breakpoint thirdBreak->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //add another disabled breakpoint b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), fourthBreakLine); QTest::qWait(300); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); QTest::qWait(300); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testChangeLocationBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 27); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); QTest::qWait(100); b->setLine(28); QTest::qWait(100); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 28); QTest::qWait(500); breakpoints()->setData(breakpoints()->index(0, KDevelop::Breakpoint::LocationColumn), QString(debugeeFileName+":30")); QCOMPARE(b->line(), 29); QTest::qWait(100); QCOMPARE(b->line(), 29); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testDeleteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; QCOMPARE(breakpoints()->rowCount(), 0); //add breakpoint before startDebugging breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QCOMPARE(breakpoints()->rowCount(), 1); breakpoints()->removeRow(0); QCOMPARE(breakpoints()->rowCount(), 0); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPendingBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("test_gdb.cpp")), 10); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::PendingState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testUpdateBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; // breakpoint 1: line 29 KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(breakpoints()->rowCount(), 1); session->startDebugging(&cfg, m_iface); // breakpoint 2: line 28 //insert custom command as user might do it using GDB console session->addCommand(new MI::UserCommand(MI::NonMI, "break "+debugeeFileName+":28")); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 28 session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop after step QCOMPARE(breakpoints()->rowCount(), 2); b = breakpoints()->breakpoint(1); QCOMPARE(b->url(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(b->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); // stop at line 29 session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testIgnoreHitsBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); b1->setIgnoreHits(1); KDevelop::Breakpoint * b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); session->startDebugging(&cfg, m_iface); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b2->hitCount() == 1); b2->setIgnoreHits(1); session->run(); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b1->hitCount() == 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 39); b->setCondition("x[0] == 'H'"); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 23); b->setCondition("i==2"); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR(session, session->state() == DebugSession::PausedState && session->line() == 24); b->setCondition("i == 0"); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 39); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnWriteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addWatchpoint("i"); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnWriteWithConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); KDevelop::Breakpoint *b = breakpoints()->addWatchpoint("i"); b->setCondition("i==2"); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnReadBreakpoint() { /* test disabled because of gdb bug: http://sourceware.org/bugzilla/show_bug.cgi?id=10136 TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b = breakpoints()->addReadWatchpoint("foo::i"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); */ } void GdbTest::testBreakOnReadBreakpoint2() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addReadWatchpoint("i"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnAccessBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addAccessWatchpoint("i"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QTest::qWait(500); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 25); b->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointWhileRunningMultiple() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 24); KDevelop::Breakpoint *b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QTest::qWait(500); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 25); b1->setDeleted(); b2->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointFunctionName() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testManualBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); breakpoints()->removeRows(0, 1); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 0); session->addCommand(MI::NonMI, "break debugee.cpp:23"); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 1); KDevelop::Breakpoint* b = breakpoints()->breakpoint(0); QCOMPARE(b->line(), 22); session->addCommand(MI::NonMI, "disable 2"); session->addCommand(MI::NonMI, "condition 2 i == 1"); session->addCommand(MI::NonMI, "ignore 2 1"); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->enabled(), false); QCOMPARE(b->condition(), QString("i == 1")); QCOMPARE(b->ignoreHits(), 1); session->addCommand(MI::NonMI, "delete 2"); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testShowStepInSource() { TestDebugSession *session = new TestDebugSession; QSignalSpy showStepInSourceSpy(session, &TestDebugSession::showStepInSource); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); { QCOMPARE(showStepInSourceSpy.count(), 3); QList arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 29); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 22); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 23); } } void GdbTest::testStack() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); QCOMPARE(stackModel->rowCount(tIdx), 2); QCOMPARE(stackModel->columnCount(tIdx), 3); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "foo"); COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":23"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "main"); COMPARE_DATA(tIdx.child(1, 2), debugeeFileName+":29"); session->stepOut(); WAIT_FOR_STATE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":30"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackFetchMore() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeerecursion")); QString fileName = findSourceFile("debugeerecursion.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->frameStackModel()->fetchFramesCalled, 1); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); QCOMPARE(stackModel->rowCount(tIdx), 21); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "foo"); COMPARE_DATA(tIdx.child(0, 2), fileName+":26"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "foo"); COMPARE_DATA(tIdx.child(1, 2), fileName+":24"); COMPARE_DATA(tIdx.child(2, 0), "2"); COMPARE_DATA(tIdx.child(2, 1), "foo"); COMPARE_DATA(tIdx.child(2, 2), fileName+":24"); COMPARE_DATA(tIdx.child(19, 0), "19"); COMPARE_DATA(tIdx.child(20, 0), "20"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 2); QCOMPARE(stackModel->rowCount(tIdx), 41); COMPARE_DATA(tIdx.child(20, 0), "20"); COMPARE_DATA(tIdx.child(21, 0), "21"); COMPARE_DATA(tIdx.child(22, 0), "22"); COMPARE_DATA(tIdx.child(39, 0), "39"); COMPARE_DATA(tIdx.child(40, 0), "40"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 3); QCOMPARE(stackModel->rowCount(tIdx), 121); COMPARE_DATA(tIdx.child(40, 0), "40"); COMPARE_DATA(tIdx.child(41, 0), "41"); COMPARE_DATA(tIdx.child(42, 0), "42"); COMPARE_DATA(tIdx.child(119, 0), "119"); COMPARE_DATA(tIdx.child(120, 0), "120"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); COMPARE_DATA(tIdx.child(120, 0), "120"); COMPARE_DATA(tIdx.child(121, 0), "121"); COMPARE_DATA(tIdx.child(122, 0), "122"); COMPARE_DATA(tIdx.child(300, 0), "300"); COMPARE_DATA(tIdx.child(300, 1), "main"); COMPARE_DATA(tIdx.child(300, 2), fileName+":30"); stackModel->fetchMoreFrames(); //nothing to fetch, we are at the end QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackDeactivateAndActive() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); session->stepOut(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(200); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":30"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackSwitchThread() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeethreads")); QString fileName = findSourceFile("debugeethreads.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->rowCount(), 4); QModelIndex tIdx = stackModel->index(0,0); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), fileName+":39"); tIdx = stackModel->index(1,0); QVERIFY(stackModel->data(tIdx).toString().startsWith("#2 at ")); stackModel->setCurrentThread(2); QTest::qWait(200); int rows = stackModel->rowCount(tIdx); QVERIFY(rows > 3); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile("debugeeslow.cpp"); KProcess debugeeProcess; debugeeProcess << "nice" << findExecutable("debugeeslow").toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); QTest::qWait(100); TestDebugSession *session = new TestDebugSession; session->attachToProcess(debugeeProcess.pid()); WAIT_FOR_STATE(session, DebugSession::PausedState); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 34); QTest::qWait(100); session->run(); QTest::qWait(2000); WAIT_FOR_STATE(session, DebugSession::PausedState); if (session->line() < 34 || session->line() < 35) { QCOMPARE(session->line(), 34); } session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testManualAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile("debugeeslow.cpp"); KProcess debugeeProcess; debugeeProcess << "nice" << findExecutable("debugeeslow").toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; cfg.config().writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(findSourceFile("gdb_script_empty"))); QVERIFY(session->startDebugging(&cfg, m_iface)); session->addCommand(MI::NonMI, QString("attach %0").arg(debugeeProcess.pid())); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); QTest::qWait(2000); // give the slow inferior some extra time to run WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testCoreFile() { QFile f("core"); if (f.exists()) f.remove(); KProcess debugeeProcess; debugeeProcess.setOutputChannelMode(KProcess::MergedChannels); debugeeProcess << "bash" << "-c" << "ulimit -c unlimited; " + findExecutable("debugeecrash").toLocalFile(); debugeeProcess.start(); debugeeProcess.waitForFinished(); qDebug() << debugeeProcess.readAll(); if (!QFile::exists("core")) { QSKIP("no core dump found, check your system configuration (see /proc/sys/kernel/core_pattern).", SkipSingle); } TestDebugSession *session = new TestDebugSession; session->examineCoreFile(findExecutable("debugeecrash"), QUrl::fromLocalFile(QDir::currentPath()+"/core")); TestFrameStackModel *stackModel = session->frameStackModel(); WAIT_FOR_STATE(session, DebugSession::StoppedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } KDevelop::VariableCollection *variableCollection() { return KDevelop::ICore::self()->debugController()->variableCollection(); } void GdbTest::testVariablesLocals() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QCOMPARE(variableCollection()->rowCount(), 2); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "0"); session->run(); QTest::qWait(1000); WAIT_FOR_STATE(session, DebugSession::PausedState); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesLocalsStruct() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QModelIndex i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); int structIndex = 0; for(int j=0; j<3; ++j) { if (variableCollection()->index(j, 0, i).data().toString() == "ts") { structIndex = j; } } COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(structIndex, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatches() { TestDebugSession *session = new TestDebugSession; KDevelop::ICore::self()->debugController()->variableCollection()->variableWidgetShown(); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("ts"); QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatchesQuotes() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; const QString testString("test"); const QString quotedTestString("\"" + testString + "\""); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(quotedTestString); //just a constant string QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), quotedTestString); COMPARE_DATA(variableCollection()->index(0, 1, i), "[" + QString::number(testString.length() + 1) + "]"); QModelIndex testStr = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, testStr), "..."); variableCollection()->expanded(testStr); QTest::qWait(100); int len = testString.length(); for (int ind = 0; ind < len; ind++) { COMPARE_DATA(variableCollection()->index(ind, 0, testStr), QString::number(ind)); QChar c = testString.at(ind); QString value = QString::number(c.toLatin1()) + " '" + c + "'"; COMPARE_DATA(variableCollection()->index(ind, 1, testStr), value); } COMPARE_DATA(variableCollection()->index(len, 0, testStr), QString::number(len)); COMPARE_DATA(variableCollection()->index(len, 1, testStr), "0 '\\000'"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatchesTwoSessions() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("ts"); QTest::qWait(300); QModelIndex ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); variableCollection()->expanded(ts); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope QCOMPARE(variableCollection()->watches()->childCount(), 1); KDevelop::Variable* v = dynamic_cast(variableCollection()->watches()->child(0)); QVERIFY(v); QVERIFY(!v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(!v->inScope()); //start a second debug session session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(300); QCOMPARE(variableCollection()->watches()->childCount(), 1); ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); v = dynamic_cast(variableCollection()->watches()->child(0)); QVERIFY(v); QVERIFY(v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(v->inScope()); QCOMPARE(v->data(1, Qt::DisplayRole).toString(), QString::number(0)); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope v = dynamic_cast(variableCollection()->watches()->child(0)); QVERIFY(!v->inScope()); QVERIFY(!dynamic_cast(v->child(0))->inScope()); } void GdbTest::testVariablesStopDebugger() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stopDebugger(); QTest::qWait(300); } void GdbTest::testVariablesStartSecondSession() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&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->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); QTest::qWait(200); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); COMPARE_DATA(variableCollection()->index(2, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(2, 1, i), "1"); COMPARE_DATA(variableCollection()->index(3, 0, i), "argv"); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesQuicklySwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); QTest::qWait(300); stackModel->setCurrentFrame(0); QTest::qWait(1); stackModel->setCurrentFrame(1); QTest::qWait(1); stackModel->setCurrentFrame(0); QTest::qWait(1); stackModel->setCurrentFrame(1); QTest::qWait(500); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); QStringList locs; for (int j = 0; j < variableCollection()->rowCount(i); ++j) { locs << variableCollection()->index(j, 0, i).data().toString(); } QVERIFY(locs.contains("argc")); QVERIFY(locs.contains("argv")); QVERIFY(locs.contains("x")); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testSegfaultDebugee() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg(findExecutable("debugeecrash")); QString fileName = findSourceFile("debugeecrash.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 23); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testSwitchFrameGdbConsole() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->currentFrame(), 0); stackModel->setCurrentFrame(1); QCOMPARE(stackModel->currentFrame(), 1); QTest::qWait(500); QCOMPARE(stackModel->currentFrame(), 1); session->addUserCommand("print x"); QTest::qWait(500); //currentFrame must not reset to 0; Bug 222882 QCOMPARE(stackModel->currentFrame(), 1); } //Bug 201771 void GdbTest::testInsertAndRemoveBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); b->setDeleted(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //Bug 274390 void GdbTest::testCommandOrderFastStepping() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeeqt")); breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startDebugging(&cfg, m_iface)); for(int i=0; i<20; i++) { session->stepInto(); } WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPickupManuallyInsertedBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startDebugging(&cfg, m_iface)); session->addCommand(MI::NonMI, "break debugee.cpp:32"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); //wait for breakpoints update QCOMPARE(breakpoints()->breakpoints().count(), 2); QCOMPARE(breakpoints()->rowCount(), 2); KDevelop::Breakpoint *b = breakpoints()->breakpoint(1); QVERIFY(b); QCOMPARE(b->line(), 31); //we start with 0, gdb with 1 QCOMPARE(b->url().fileName(), QString("debugee.cpp")); } //Bug 270970 void GdbTest::testPickupManuallyInsertedBreakpointOnlyOnce() { TestDebugSession *session = new TestDebugSession; //inject here, so it behaves similar like a command from .gdbinit QTemporaryFile configScript; configScript.open(); configScript.write(QString("file %0\n").arg(findExecutable("debugee").toLocalFile()).toLocal8Bit()); configScript.write("break debugee.cpp:32\n"); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(remoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile("debugee.cpp"), 31); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPickupCatchThrowOnlyOnce() { QTemporaryFile configScript; configScript.open(); configScript.write("catch throw\n"); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(remoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); for (int i = 0; i < 2; ++i) { TestDebugSession* session = new TestDebugSession; QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::EndedState); } QCOMPARE(breakpoints()->rowCount(), 1); //one from kdevelop, one from runScript } void GdbTest::testRunGdbScript() { TestDebugSession *session = new TestDebugSession; QTemporaryFile runScript; runScript.open(); runScript.write("file " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); runScript.write("break main\n"); runScript.write("run\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testRemoteDebug() { const QString gdbserverExecutable = QStandardPaths::findExecutable("gdbserver"); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } TestDebugSession *session = new TestDebugSession; QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(remoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); grp.writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testRemoteDebugInsertBreakpoint() { const QString gdbserverExecutable = QStandardPaths::findExecutable("gdbserver"); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } TestDebugSession *session = new TestDebugSession; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 35); QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file " + findExecutable("debugee").toLocalFile().toUtf8() + '\n'); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(remoteGdbShellEntry, QUrl::fromLocalFile(shellScript.fileName()+"-copy")); grp.writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testRemoteDebugInsertBreakpointPickupOnlyOnce() { const QString gdbserverExecutable = QStandardPaths::findExecutable("gdbserver"); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } TestDebugSession *session = new TestDebugSession; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 35); QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 "+findExecutable("debugee").toLocalFile().toLatin1()+"\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file "+findExecutable("debugee").toLocalFile().toLatin1()+"\n"); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(remoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); grp.writeEntry(remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //************************** second session session = new TestDebugSession; QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testBreakpointWithSpaceInPath() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeespace")); KConfigGroup grp = cfg.config(); QString fileName = findSourceFile("debugee space.cpp"); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 20); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 20); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakpointDisabledOnStart() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28) ->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 31); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 31); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testCatchpoint() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg(findExecutable("debugeeexception")); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp")), 29); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); TestFrameStackModel* fsModel = session->frameStackModel(); QCOMPARE(fsModel->currentFrame(), 0); QCOMPARE(session->line(), 29); - session->addCommand(new MI::MICommand(MI::NonMI, "catch throw")); + session->addCommand(MI::NonMI, "catch throw"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); const QList frames = fsModel->frames(fsModel->currentThread()); QVERIFY(frames.size() >= 2); // frame 0 is somewhere inside libstdc++ QCOMPARE(frames[1].file, QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp"))); QCOMPARE(frames[1].line, 22); QCOMPARE(breakpoints()->rowCount(),2); QVERIFY(!breakpoints()->breakpoint(0)->location().isEmpty()); QVERIFY(!breakpoints()->breakpoint(1)->location().isEmpty()); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //TODO: figure out why do we need this test? And do we need it at all?? void GdbTest::testThreadAndFrameInfo() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeethreads")); QString fileName = findSourceFile("debugeethreads.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QSignalSpy outputSpy(session, &TestDebugSession::debuggerUserCommandOutput); session->addCommand( new MI::UserCommand(MI::ThreadInfo,"")); session->addCommand(new MI::UserCommand(MI::StackListLocals, QLatin1String("0"))); QTest::qWait(1000); QCOMPARE(outputSpy.count(), 2); QVERIFY(outputSpy.last().at(0).toString().contains(QLatin1String("--thread 1"))); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::parseBug304730() { MI::FileSymbol file; file.contents = QByteArray("^done,bkpt={" "number=\"1\",type=\"breakpoint\",disp=\"keep\",enabled=\"y\",addr=\"\",times=\"0\"," "original-location=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp:231\"}," "{number=\"1.1\",enabled=\"y\",addr=\"0x081d84aa\"," "func=\"PatchMatch, 2u> >" "::Propagation(ForwardPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}," "{number=\"1.2\",enabled=\"y\",addr=\"0x081d8ae2\"," "func=\"PatchMatch, 2u> >" "::Propagation(BackwardPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}," "{number=\"1.3\",enabled=\"y\",addr=\"0x081d911a\"," "func=\"PatchMatch, 2u> >" "::Propagation(AllowedPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}"); MI::MIParser parser; std::unique_ptr record(parser.parse(&file)); QVERIFY(record.get() != nullptr); } void GdbTest::testMultipleLocationsBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeemultilocbreakpoint")); breakpoints()->addCodeBreakpoint("aPlusB"); //TODO check if the additional location breakpoint is added session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 19); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBug301287() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("argc"); QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //start second debug session (same cfg) session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(300); i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testMultipleBreakpoint() { TestDebugSession *session = new TestDebugSession; //there'll be about 3-4 breakpoints, but we treat it like one. TestLaunchConfiguration c(findExecutable("debugeemultiplebreakpoint")); KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint("debugeemultiplebreakpoint.cpp:52"); session->startDebugging(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testRegularExpressionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable("debugeemultilocbreakpoint")); breakpoints()->addCodeBreakpoint("main"); session->startDebugging(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); - session->addCommand(new MI::MICommand(MI::NonMI, "rbreak .*aPl.*B")); + session->addCommand(MI::NonMI, "rbreak .*aPl.*B"); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 3); - session->addCommand(new MI::MICommand(MI::BreakDelete, "")); + session->addCommand(MI::BreakDelete, ""); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testChangeBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable("debugeeslow")); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint("debugeeslow.cpp:25"); session->startDebugging(&c, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QVERIFY(session->currentLine() >= 24 && session->currentLine() <= 26 ); session->run(); WAIT_FOR_STATE(session, DebugSession::ActiveState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //to make one loop QTest::qWait(2000); WAIT_FOR_STATE(session, DebugSession::ActiveState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testDebugInExternalTerminal() { TestLaunchConfiguration cfg; foreach (const QString & console, QStringList() << "konsole" << "xterm" << "xfce4-terminal" << "gnome-terminal") { TestDebugSession* session = 0; if (QStandardPaths::findExecutable(console).isEmpty()) { continue; } session = new TestDebugSession(); cfg.config().writeEntry("External Terminal"/*ExecutePlugin::terminalEntry*/, console); cfg.config().writeEntry("Use External Terminal"/*ExecutePlugin::useTerminalEntry*/, true); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } } // see: https://bugs.kde.org/show_bug.cgi?id=339231 void GdbTest::testPathWithSpace() { #ifdef HAVE_PATH_WITH_SPACES_TEST TestDebugSession* session = new TestDebugSession; auto debugee = findExecutable("path with space/spacedebugee"); TestLaunchConfiguration c(debugee, KIO::upUrl(debugee)); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint("spacedebugee.cpp:30"); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); #endif } bool GdbTest::waitForState(DebugSession *session, DebugSession::DebuggerState state, const char *file, int line, bool waitForIdle) { QPointer s(session); //session can get deleted in DebugController QTime stopWatch; stopWatch.start(); while (s.data()->state() != state || (waitForIdle && s->debuggerStateIsOn(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; } } // end of namespace GDB } // end of namespace KDevMI QTEST_MAIN(KDevMI::GDB::GdbTest) #include "test_gdb.moc" #include "moc_test_gdb.cpp"