diff --git a/debuggers/common/mi/micommand.cpp b/debuggers/common/mi/micommand.cpp index 9985b7999b..b726629a28 100644 --- a/debuggers/common/mi/micommand.cpp +++ b/debuggers/common/mi/micommand.cpp @@ -1,606 +1,637 @@ /*************************************************************************** begin : Sun Aug 8 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "micommand.h" +#include using namespace KDevMI::MI; FunctionCommandHandler::FunctionCommandHandler(const FunctionCommandHandler::Function& callback, CommandFlags flags) : _flags(flags) , _callback(callback) { } bool FunctionCommandHandler::handlesError() { return _flags & CmdHandlesError; } void FunctionCommandHandler::handle(const ResultRecord& r) { _callback(r); } MICommand::MICommand(CommandType type, const QString& command, CommandFlags flags) : type_(type) , flags_(flags) , command_(command) , commandHandler_(nullptr) , stateReloading_(false) , m_thread(-1) , m_frame(-1) { } MICommand::~MICommand() { if (commandHandler_ && commandHandler_->autoDelete()) { delete commandHandler_; } commandHandler_ = nullptr; } QString MICommand::cmdToSend() { return initialString() + '\n'; } QString MICommand::initialString() const { QString result = QString::number(token()); if (type() == NonMI) { result += command_; } else { result += miCommand(); if (m_thread != -1) result = result + QString(" --thread %1").arg(m_thread); if (m_frame != -1) result = result + QString(" --frame %1").arg(m_frame); if (!command_.isEmpty()) result += ' ' + command_; } return result; } bool MICommand::isUserCommand() const { return false; } void MICommand::setHandler(MICommandHandler* handler) { if (commandHandler_ && commandHandler_->autoDelete()) delete commandHandler_; commandHandler_ = handler; if (!commandHandler_) { flags_ = flags_ & ~CmdHandlesError; } } void MICommand::setHandler(const FunctionCommandHandler::Function& callback) { setHandler(new FunctionCommandHandler(callback, flags())); } bool MICommand::invokeHandler(const ResultRecord& r) { if (commandHandler_) { //ask before calling handler as it might deleted itself in handler bool autoDelete = commandHandler_->autoDelete(); commandHandler_->handle(r); if (autoDelete) { delete commandHandler_; } commandHandler_ = 0; return true; } else { return false; } } void MICommand::newOutput(const QString& line) { lines.push_back(line); } const QStringList& MICommand::allStreamOutput() const { return lines; } bool MICommand::handlesError() const { return commandHandler_ ? commandHandler_->handlesError() : false; } UserCommand::UserCommand(CommandType type, const QString& s) : MICommand(type, s, CmdMaybeStartsRunning) { } bool UserCommand::isUserCommand() const { return true; } QString MICommand::miCommand() const { QString command; switch (type()) { case NonMI: command = ""; break; case BreakAfter: command = "break-after";//"ignore" break; case BreakCatch: // FIXME: non-exist command command = "break-catch"; break; case BreakCommands: command = "break-commands"; break; case BreakCondition: command = "break-condition";//"cond" break; case BreakDelete: command = "break-delete";//"delete breakpoint" break; case BreakDisable: command = "break-disable";//"disable breakpoint" break; case BreakEnable: command = "break-enable";//"enable breakpoint" break; case BreakInfo: command = "break-info";//"info break" break; case BreakInsert: command = "break-insert -f"; break; case BreakList: command = "break-list";//"info break" break; case BreakWatch: command = "break-watch"; break; case DataDisassemble: command = "data-disassemble"; break; case DataEvaluateExpression: command = "data-evaluate-expression"; break; case DataListChangedRegisters: command = "data-list-changed-registers"; break; case DataListRegisterNames: command = "data-list-register-names"; break; case DataListRegisterValues: command = "data-list-register-values"; break; case DataReadMemory: command = "data-read-memory"; break; case DataWriteMemory: command = "data-write-memory"; break; case DataWriteRegisterVariables: command = "data-write-register-values"; break; case EnablePrettyPrinting: command = "enable-pretty-printing"; break; case EnableTimings: command = "enable-timings"; break; case EnvironmentCd: command = "environment-cd"; break; case EnvironmentDirectory: command = "environment-directory"; break; case EnvironmentPath: command = "environment-path"; break; case EnvironmentPwd: command = "environment-pwd"; break; case ExecAbort: command = "exec-abort"; break; case ExecArguments: command = "exec-arguments";//"set args" break; case ExecContinue: command = "exec-continue"; break; case ExecFinish: command = "exec-finish"; break; case ExecInterrupt: command = "exec-interrupt"; break; case ExecNext: command = "exec-next"; break; case ExecNextInstruction: command = "exec-next-instruction"; break; case ExecReturn: command = "exec-command ="; break; case ExecRun: command = "exec-run"; break; case ExecShowArguments: command = "exec-show-arguments"; break; case ExecSignal: command = "exec-signal"; break; case ExecStep: command = "exec-step"; break; case ExecStepInstruction: command = "exec-step-instruction"; break; case ExecUntil: command = "exec-until"; break; case FileClear: command = "file-clear"; break; case FileExecAndSymbols: command = "file-exec-and-symbols";//"file" break; case FileExecFile: command = "file-exec-file";//"exec-file" break; case FileListExecSections: command = "file-list-exec-sections"; break; case FileListExecSourceFile: command = "file-list-exec-source-file"; break; case FileListExecSourceFiles: command = "file-list-exec-source-files"; break; case FileListSharedLibraries: command = "file-list-shared-libraries"; break; case FileListSymbolFiles: command = "file-list-symbol-files"; break; case FileSymbolFile: command = "file-symbol-file";//"symbol-file" break; case GdbComplete: command = "gdb-complete"; break; case GdbExit: command = "gdb-exit"; break; case GdbSet: command = "gdb-set";//"set" break; case GdbShow: command = "gdb-show";//"show" break; case GdbSource: command = "gdb-source"; break; case GdbVersion: command = "gdb-version";//"show version" break; case InferiorTtySet: command = "inferior-tty-set"; break; case InferiorTtyShow: command = "inferior-tty-show"; break; case InterpreterExec: command = "interpreter-exec"; break; case ListFeatures: command = "list-features"; break; case OverlayAuto: command = "overlay-auto"; break; case OverlayListMappingState: command = "overlay-list-mapping-state"; break; case OverlayListOverlays: command = "overlay-list-overlays"; break; case OverlayMap: command = "overlay-map"; break; case OverlayOff: command = "overlay-off"; break; case OverlayOn: command = "overlay-on"; break; case OverlayUnmap: command = "overlay-unmap"; break; case SignalHandle: return "handle"; //command = "signal-handle"; break; case SignalListHandleActions: command = "signal-list-handle-actions"; break; case SignalListSignalTypes: command = "signal-list-signal-types"; break; case StackInfoDepth: command = "stack-info-depth"; break; case StackInfoFrame: command = "stack-info-frame"; break; case StackListArguments: command = "stack-list-arguments"; break; case StackListExceptionHandlers: command = "stack-list-exception-handlers"; break; case StackListFrames: command = "stack-list-frames"; break; case StackListLocals: command = "stack-list-locals"; break; case StackSelectFrame: command = "stack-select-frame"; break; case SymbolInfoAddress: command = "symbol-info-address"; break; case SymbolInfoFile: command = "symbol-info-file"; break; case SymbolInfoFunction: command = "symbol-info-function"; break; case SymbolInfoLine: command = "symbol-info-line"; break; case SymbolInfoSymbol: command = "symbol-info-symbol"; break; case SymbolListFunctions: command = "symbol-list-functions"; break; case SymbolListLines: command = "symbol-list-lines"; break; case SymbolListTypes: command = "symbol-list-types"; break; case SymbolListVariables: command = "symbol-list-variables"; break; case SymbolLocate: command = "symbol-locate"; break; case SymbolType: command = "symbol-type"; break; case TargetAttach: command = "target-attach"; break; case TargetCompareSections: command = "target-compare-sections"; break; case TargetDetach: command = "target-detach";//"detach" break; case TargetDisconnect: command = "target-disconnect";//"disconnect" break; case TargetDownload: command = "target-download"; break; case TargetExecStatus: command = "target-exec-status"; break; case TargetListAvailableTargets: command = "target-list-available-targets"; break; case TargetListCurrentTargets: command = "target-list-current-targets"; break; case TargetListParameters: command = "target-list-parameters"; break; case TargetSelect: command = "target-select"; break; case ThreadInfo: command = "thread-info"; break; case ThreadListAllThreads: command = "thread-list-all-threads"; break; case ThreadListIds: command = "thread-list-ids"; break; case ThreadSelect: command = "thread-select"; break; case TraceActions: command = "trace-actions"; break; case TraceDelete: command = "trace-delete"; break; case TraceDisable: command = "trace-disable"; break; case TraceDump: command = "trace-dump"; break; case TraceEnable: command = "trace-enable"; break; case TraceExists: command = "trace-exists"; break; case TraceFind: command = "trace-find"; break; case TraceFrameNumber: command = "trace-frame-number"; break; case TraceInfo: command = "trace-info"; break; case TraceInsert: command = "trace-insert"; break; case TraceList: command = "trace-list"; break; case TracePassCount: command = "trace-pass-count"; break; case TraceSave: command = "trace-save"; break; case TraceStart: command = "trace-start"; break; case TraceStop: command = "trace-stop"; break; case VarAssign: command = "var-assign"; break; case VarCreate: command = "var-create"; break; case VarDelete: command = "var-delete"; break; case VarEvaluateExpression: command = "var-evaluate-expression"; break; case VarInfoPathExpression: command = "var-info-path-expression"; break; case VarInfoExpression: command = "var-info-expression"; break; case VarInfoNumChildren: command = "var-info-num-children"; break; case VarInfoType: command = "var-info-type"; break; case VarListChildren: command = "var-list-children"; break; case VarSetFormat: command = "var-set-format"; break; case VarSetFrozen: command = "var-set-frozen"; break; case VarShowAttributes: command = "var-show-attributes"; break; case VarShowFormat: command = "var-show-format"; break; case VarUpdate: command = "var-update"; break; default: command = "unknown"; break; } return '-' + command; } CommandType MICommand::type() const { return type_; } int MICommand::thread() const { return m_thread; } void MICommand::setThread(int thread) { m_thread = thread; } int MICommand::frame() const { return m_frame; } void MICommand::setFrame(int frame) { m_frame = frame; } QString MICommand::command() const { return command_; } void MICommand::setStateReloading(bool f) { stateReloading_ = f; } bool MICommand::stateReloading() const { return stateReloading_; } + +void MICommand::markAsEnqueued() +{ + m_enqueueTimestamp = QDateTime::currentMSecsSinceEpoch(); +} + +void MICommand::markAsSubmitted() +{ + m_submitTimestamp = QDateTime::currentMSecsSinceEpoch(); +} + +void MICommand::markAsCompleted() +{ + m_completeTimestamp = QDateTime::currentMSecsSinceEpoch(); +} + +qint64 MICommand::gdbProcessingTime() const +{ + return m_completeTimestamp - m_submitTimestamp; +} + +qint64 MICommand::queueTime() const +{ + return m_submitTimestamp - m_enqueueTimestamp; +} + +qint64 MICommand::totalProcessingTime() const +{ + return m_completeTimestamp - m_enqueueTimestamp; +} diff --git a/debuggers/common/mi/micommand.h b/debuggers/common/mi/micommand.h index 40e27a9eba..2b343659d2 100644 --- a/debuggers/common/mi/micommand.h +++ b/debuggers/common/mi/micommand.h @@ -1,350 +1,377 @@ /*************************************************************************** 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 { 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 { protected: MICommand(CommandType type, const QString& arguments = QString(), CommandFlags flags = 0); friend class KDevMI::MIDebugSession; public: virtual ~MICommand(); CommandType type() 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; + /// Called when the command has been enqueued in the debug session + /// and the command is wait for being submitted to GDB. + void markAsEnqueued(); + + /// Called when the command has been submitted to GDB and the command + /// waits for completion by GDB. + void markAsSubmitted(); + + /// Called when the command has been completed and the response has arrived. + void markAsCompleted(); + + /// returns the amount of time (in ms) passed between submission and completion. + qint64 gdbProcessingTime() const; + + /// returns the amount of time (in ms) passed between enqueuing and submission. + qint64 queueTime() const; + + /// returns the amount of time (in ms) passed between enqueuing and completion. + qint64 totalProcessingTime() const; + protected: CommandType type_; CommandFlags flags_; uint32_t token_ = 0; QString command_; MICommandHandler *commandHandler_; QStringList lines; bool stateReloading_; int m_thread; int m_frame; + // remember the timestamps (in ms since start of the epoch) when this command + // - was added to the command queue (enqueued) + // - was submitted to GDB + // - was completed; response from GDB arrived + qint64 m_enqueueTimestamp; + qint64 m_submitTimestamp; + qint64 m_completeTimestamp; }; 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), 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 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())); } 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/mi/micommandqueue.cpp b/debuggers/common/mi/micommandqueue.cpp index 5bb7f21b92..29586293f6 100644 --- a/debuggers/common/mi/micommandqueue.cpp +++ b/debuggers/common/mi/micommandqueue.cpp @@ -1,107 +1,143 @@ // ************************************************************************* // gdbcommandqueue.cpp // ------------------- // begin : Wed Dec 5, 2007 // copyright : (C) 2007 by Hamish Rodda // email : rodda@kde.org // ************************************************************************** // // ************************************************************************** // * * // * This program is free software; you can redistribute it and/or modify * // * it under the terms of the GNU General Public License as published by * // * the Free Software Foundation; either version 2 of the License, or * // * (at your option) any later version. * // * * // ************************************************************************** #include "micommandqueue.h" #include "mi.h" #include "micommand.h" +#include "debuglog.h" using namespace KDevMI::MI; CommandQueue::CommandQueue() : m_tokenCounter(0) { } CommandQueue::~CommandQueue() { qDeleteAll(m_commandList); } void CommandQueue::enqueue(MICommand* command) { ++m_tokenCounter; if (m_tokenCounter == 0) m_tokenCounter = 1; command->setToken(m_tokenCounter); + // take the time when this command was added to the command queue + command->markAsEnqueued(); + m_commandList.append(command); if (command->flags() & (CmdImmediately | CmdInterrupt)) ++m_immediatelyCounter; rationalizeQueue(command); + dumpQueue(); +} + +void CommandQueue::dumpQueue() +{ + qCDebug(DEBUGGERCOMMON) << "Pending commands" << m_commandList.count(); + unsigned commandNum = 0; + foreach(const MICommand* command, m_commandList) { + qCDebug(DEBUGGERCOMMON) << "Command" << commandNum << command->initialString(); + ++commandNum; + } } -void CommandQueue::rationalizeQueue(MICommand * command) +void CommandQueue::rationalizeQueue(MICommand* command) { - if (command->type() >= ExecAbort && command->type() <= ExecUntil) - // Changing execution location, abort any variable updates - removeVariableUpdates(); + if ((command->type() >= ExecAbort && command->type() <= ExecUntil) && + command->type() != ExecArguments && + command->type() != ExecShowArguments ) { + // Changing execution location, abort any variable updates + removeVariableUpdates(); + // ... and stack list updates + removeStackListUpdates(); + } } void CommandQueue::removeVariableUpdates() { QMutableListIterator it = m_commandList; while (it.hasNext()) { MICommand* command = it.next(); CommandType type = command->type(); if ((type >= VarEvaluateExpression && type <= VarListChildren) || type == VarUpdate) { if (command->flags() & (CmdImmediately | CmdInterrupt)) --m_immediatelyCounter; it.remove(); delete command; } } } +void CommandQueue::removeStackListUpdates() +{ + QMutableListIterator it = m_commandList; + + while (it.hasNext()) { + MICommand* command = it.next(); + CommandType type = command->type(); + if (type >= StackListArguments && type <= StackListLocals) { + if (command->flags() & (CmdImmediately | CmdInterrupt)) + --m_immediatelyCounter; + it.remove(); + delete command; + } + } +} + void CommandQueue::clear() { qDeleteAll(m_commandList); m_commandList.clear(); m_immediatelyCounter = 0; } int CommandQueue::count() const { return m_commandList.count(); } bool CommandQueue::isEmpty() const { return m_commandList.isEmpty(); } bool CommandQueue::haveImmediateCommand() const { return m_immediatelyCounter > 0; } MICommand* CommandQueue::nextCommand() { if (m_commandList.isEmpty()) return nullptr; MICommand* command = m_commandList.takeAt(0); if (command->flags() & (CmdImmediately | CmdInterrupt)) --m_immediatelyCounter; return command; } diff --git a/debuggers/common/mi/micommandqueue.h b/debuggers/common/mi/micommandqueue.h index 28a994b878..4c9ae91130 100644 --- a/debuggers/common/mi/micommandqueue.h +++ b/debuggers/common/mi/micommandqueue.h @@ -1,61 +1,63 @@ // ************************************************************************* // micommandqueue.cpp // ------------------- // begin : Wed Dec 5, 2007 // copyright : (C) 2007 by Hamish Rodda // email : rodda@kde.org // ************************************************************************** // // ************************************************************************** // * * // * This program is free software; you can redistribute it and/or modify * // * it under the terms of the GNU General Public License as published by * // * the Free Software Foundation; either version 2 of the License, or * // * (at your option) any later version. * // * * // ************************************************************************** #ifndef MICOMMANDQUEUE_H #define MICOMMANDQUEUE_H #include "dbgglobal.h" #include namespace KDevMI { namespace MI { class MICommand; class CommandQueue { public: CommandQueue(); ~CommandQueue(); void enqueue(MICommand* command); bool isEmpty() const; int count() const; void clear(); /// Whether the queue contains a command with CmdImmediately or CmdInterrupt flags. bool haveImmediateCommand() const; /** * Retrieve and remove the next command from the list. */ MICommand* nextCommand(); private: void rationalizeQueue(MICommand* command); void removeVariableUpdates(); + void removeStackListUpdates(); + void dumpQueue(); QList m_commandList; int m_immediatelyCounter = 0; uint32_t m_tokenCounter; }; } // end of namespace MI } // end of namespace KDevMI #endif // MICOMMANDQUEUE_H diff --git a/debuggers/common/midebugger.cpp b/debuggers/common/midebugger.cpp index d339749482..f92a269689 100644 --- a/debuggers/common/midebugger.cpp +++ b/debuggers/common/midebugger.cpp @@ -1,334 +1,345 @@ /* * Low level GDB interface. * * Copyright 1999 John Birch * Copyright 2007 Vladimir Prus * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "midebugger.h" #include "debuglog.h" #include "mi/micommand.h" #include #include #include #include #include #include #include // #define DEBUG_NO_TRY //to get a backtrace to where the exception was thrown using namespace KDevMI; using namespace KDevMI::MI; MIDebugger::MIDebugger(QObject* parent) : QObject(parent) , process_(nullptr) , currentCmd_(nullptr) { process_ = new KProcess(this); process_->setOutputChannelMode(KProcess::SeparateChannels); connect(process_, &KProcess::readyReadStandardOutput, this, &MIDebugger::readyReadStandardOutput); connect(process_, &KProcess::readyReadStandardError, this, &MIDebugger::readyReadStandardError); connect(process_, static_cast(&KProcess::finished), this, &MIDebugger::processFinished); connect(process_, static_cast(&KProcess::error), this, &MIDebugger::processErrored); } MIDebugger::~MIDebugger() { // prevent Qt warning: QProcess: Destroyed while process is still running. if (process_ && process_->state() == QProcess::Running) { disconnect(process_, static_cast(&KProcess::error), this, &MIDebugger::processErrored); process_->kill(); process_->waitForFinished(10); } } void MIDebugger::execute(MICommand* command) { currentCmd_ = command; QString commandText = currentCmd_->cmdToSend(); qCDebug(DEBUGGERCOMMON) << "SEND:" << commandText.trimmed(); QByteArray commandUtf8 = commandText.toUtf8(); process_->write(commandUtf8, commandUtf8.length()); + command->markAsSubmitted(); QString prettyCmd = currentCmd_->cmdToSend(); prettyCmd.remove( QRegExp("set prompt \032.\n") ); prettyCmd = "(gdb) " + prettyCmd; if (currentCmd_->isUserCommand()) emit userCommandOutput(prettyCmd); else emit internalCommandOutput(prettyCmd); } bool MIDebugger::isReady() const { return currentCmd_ == 0; } void MIDebugger::interrupt() { //TODO:win32 Porting needed int pid = process_->pid(); if (pid != 0) { ::kill(pid, SIGINT); } } MICommand* MIDebugger::currentCommand() const { return currentCmd_; } void MIDebugger::kill() { process_->kill(); } void MIDebugger::readyReadStandardOutput() { process_->setReadChannel(QProcess::StandardOutput); buffer_ += process_->readAll(); for (;;) { /* In MI mode, all messages are exactly one line. See if we have any complete lines in the buffer. */ int i = buffer_.indexOf('\n'); if (i == -1) break; QByteArray reply(buffer_.left(i)); buffer_ = buffer_.mid(i+1); processLine(reply); } } void MIDebugger::readyReadStandardError() { process_->setReadChannel(QProcess::StandardError); emit 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_->markAsCompleted(); + qCDebug(DEBUGGERCOMMON) << "Command successful, times " + << currentCmd_->totalProcessingTime() + << currentCmd_->queueTime() + << currentCmd_->gdbProcessingTime(); currentCmd_->invokeHandler(result); } } else if (result.reason == "error") { qCDebug(DEBUGGERCOMMON) << "Handling error"; + currentCmd_->markAsCompleted(); + qCDebug(DEBUGGERCOMMON) << "Command error, times" + << currentCmd_->totalProcessingTime() + << currentCmd_->queueTime() + << currentCmd_->gdbProcessingTime(); // Some commands want to handle errors themself. if (currentCmd_->handlesError() && currentCmd_->invokeHandler(result)) { qCDebug(DEBUGGERCOMMON) << "Invoked custom handler\n"; // Done, nothing more needed } else emit error(result); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled result code: " << result.reason; } delete currentCmd_; currentCmd_ = nullptr; emit ready(); break; } case MI::Record::Async: { MI::AsyncRecord& async = dynamic_cast(*r); switch (async.subkind) { case MI::AsyncRecord::Exec: { // Prefix '*'; asynchronous state changes of the target if (async.reason == "stopped") { emit programStopped(async); } else if (async.reason == "running") { emit programRunning(); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled exec notification: " << async.reason; } break; } case MI::AsyncRecord::Notify: { // Prefix '='; supplementary information that we should handle (new breakpoint etc.) emit notification(async); break; } case MI::AsyncRecord::Status: { // Prefix '+'; GDB documentation: // On-going status information about progress of a slow operation; may be ignored break; } default: Q_ASSERT(false); } break; } case MI::Record::Stream: { MI::StreamRecord& s = dynamic_cast(*r); if (s.subkind == MI::StreamRecord::Target) { emit applicationOutput(s.message); } else { if (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 937577dacb..b5b101fc9a 100644 --- a/debuggers/common/midebugsession.cpp +++ b/debuggers/common/midebugsession.cpp @@ -1,1297 +1,1298 @@ /* * 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"); if (!m_debugger->start(config, extraArguments)) { return false; } // FIXME: here, we should wait until the debugger is up and waiting for input. // Then, clear s_dbgNotStarted // It's better to do this right away so that the state bit is always correct. setDebuggerStateOff(s_dbgNotStarted); // Initialise debugger. At this stage debugger is sitting wondering what to do, // and to whom. initializeDebugger(); qCDebug(DEBUGGERCOMMON) << "Debugger instance started"; return true; } bool MIDebugSession::startDebugging(ILaunchConfiguration* cfg, IExecutePlugin* iexec) { qCDebug(DEBUGGERCOMMON) << "Starting new debug session"; Q_ASSERT(cfg); Q_ASSERT(iexec); // Ensure debugger is started first if (debuggerStateIsOn(s_appNotStarted)) { emit showMessage(i18n("Running program"), 1000); } if (debuggerStateIsOn(s_dbgNotStarted)) { if (!startDebugger(cfg)) return false; } if (debuggerStateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERCOMMON) << "Tried to run when debugger shutting down"; return false; } // Set up the tty for the inferior bool config_useExternalTerminal = iexec->useTerminal(cfg); QString config_ternimalName = iexec->terminal(cfg); if (!config_ternimalName.isEmpty()) { // the external terminal cmd contains additional arguments, just get the terminal name config_ternimalName = KShell::splitArgs(config_ternimalName).first(); } m_tty.reset(new STTY(config_useExternalTerminal, config_ternimalName)); if (!config_useExternalTerminal) { connect(m_tty.get(), &STTY::OutOutput, this, &MIDebugSession::inferiorTtyStdout); connect(m_tty.get(), &STTY::ErrOutput, this, &MIDebugSession::inferiorTtyStderr); } QString tty(m_tty->getSlave()); if (tty.isEmpty()) { KMessageBox::information(qApp->activeWindow(), m_tty->lastError(), i18n("warning")); m_tty.reset(nullptr); return false; } addCommand(InferiorTtySet, tty); // Only dummy err here, actual erros have been checked already in the job and we don't get here if there were any QString err; QString executable = iexec->executable(cfg, err).toLocalFile(); QStringList arguments = iexec->arguments(cfg, err); // Change the working directory to the correct one QString dir = iexec->workingDirectory(cfg).toLocalFile(); if (dir.isEmpty()) { dir = QFileInfo(executable).absolutePath(); } addCommand(EnvironmentCd, '"' + dir + '"'); // Set the environment variables EnvironmentGroupList l(KSharedConfig::openConfig()); QString envgrp = iexec->environmentGroup(cfg); if (envgrp.isEmpty()) { qCWarning(DEBUGGERCOMMON) << i18n("No environment group specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment group.", cfg->name()); envgrp = l.defaultGroup(); } for (const auto &envvar : l.createEnvironment(envgrp, {})) { addCommand(GdbSet, "environment " + envvar); } // Set the run arguments if (!arguments.isEmpty()) addCommand(ExecArguments, KShell::joinArgs(arguments)); // Do other debugger specific config options and actually start the inferior program if (!execInferior(cfg, executable)) { 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; emit showMessage(i18n("Attaching to process %1", pid), 1000); if (debuggerStateIsOn(s_dbgNotStarted)) { // FIXME: use global launch configuration rather than nullptr if (!startDebugger(nullptr)) { return false; } } setDebuggerStateOn(s_attached); //set current state to running, after attaching we will get *stopped response setDebuggerStateOn(s_appRunning); // Currently, we always start debugger with a name of binary, // we might be connecting to a different binary completely, // so cancel all symbol tables gdb has. // We can't omit application name from gdb invocation // because for libtool binaries, we have no way to guess // real binary name. addCommand(MI::FileExecAndSymbols); addCommand(TargetAttach, QString::number(pid), this, &MIDebugSession::handleTargetAttach, CmdHandlesError); addCommand(new SentinelCommand(breakpointController(), &MIBreakpointController::initSendBreakpoints)); raiseEvent(connected_to_program); emit raiseFramestackViews(); return true; } void MIDebugSession::handleTargetAttach(const MI::ResultRecord& r) { if (r.reason == "error") { KMessageBox::error( qApp->activeWindow(), i18n("Could not attach debugger:
")+ r["msg"].literal(), i18n("Startup error")); stopDebugger(); } } bool MIDebugSession::examineCoreFile(const QUrl &debugee, const QUrl &coreFile) { emit showMessage(i18n("Examining core file %1", coreFile.toLocalFile()), 1000); if (debuggerStateIsOn(s_dbgNotStarted)) { // FIXME: use global launch configuration rather than nullptr if (!startDebugger(nullptr)) { return false; } } // FIXME: support non-local URLs addCommand(FileExecAndSymbols, debugee.toLocalFile()); addCommand(NonMI, "core " + coreFile.toLocalFile(), this, &MIDebugSession::handleCoreFile, CmdHandlesError); raiseEvent(connected_to_program); raiseEvent(program_state_changed); return true; } void MIDebugSession::handleCoreFile(const MI::ResultRecord& r) { if (r.reason != "error") { setDebuggerStateOn(s_programExited|s_core); } else { KMessageBox::information( qApp->activeWindow(), i18n("Failed to load core file" "

Debugger reported the following error:" "

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

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

Debugger reported the following error:" "

%1", result["msg"].literal()), i18n("Debugger error")); // Error most likely means that some change made in GUI // was not communicated to the gdb, so GUI is now not // in sync with gdb. Resync it. // // Another approach is to make each widget reload it content // on errors from commands that it sent, but that's too complex. // Errors are supposed to happen rarely, so full reload on error // is not a big deal. Well, maybe except for memory view, but // it's no auto-reloaded anyway. // // Also, don't reload state on errors appeared during state // reloading! if (!m_debugger->currentCommand()->stateReloading()) raiseEvent(program_state_changed); } void MIDebugSession::setSourceInitFile(bool enable) { m_sourceInitFile = enable; } diff --git a/debuggers/gdb/debugsession.cpp b/debuggers/gdb/debugsession.cpp index 896d27bbf4..03e9e3d7d4 100644 --- a/debuggers/gdb/debugsession.cpp +++ b/debuggers/gdb/debugsession.cpp @@ -1,271 +1,271 @@ /* * 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 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() { //addCommand(new GDBCommand(GDBMI::EnableTimings, "yes")); addCommand(new CliCommand(MI::GdbShow, "version", this, &DebugSession::handleVersion)); // This makes gdb pump a variable out on one line. addCommand(MI::GdbSet, "width 0"); addCommand(MI::GdbSet, "height 0"); addCommand(MI::SignalHandle, "SIG32 pass nostop noprint"); addCommand(MI::SignalHandle, "SIG41 pass nostop noprint"); addCommand(MI::SignalHandle, "SIG42 pass nostop noprint"); addCommand(MI::SignalHandle, "SIG43 pass nostop noprint"); addCommand(MI::EnablePrettyPrinting); addCommand(MI::GdbSet, "charset UTF-8"); addCommand(MI::GdbSet, "print sevenbit-strings off"); QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevgdb/printers/gdbinit"); if (!fileName.isEmpty()) { QFileInfo fileInfo(fileName); QString quotedPrintersPath = fileInfo.dir().path() .replace('\\', "\\\\") .replace('"', "\\\""); addCommand(MI::NonMI, QString("python sys.path.insert(0, \"%0\")").arg(quotedPrintersPath)); addCommand(MI::NonMI, "source " + fileName); } 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) { addCommand(MI::GdbSet, "print static-members on"); } else { addCommand(MI::GdbSet, "print static-members off"); } if (asmDemangle) { addCommand(MI::GdbSet, "print asm-demangle on"); } else { addCommand(MI::GdbSet, "print asm-demangle off"); } 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()) { addCommand(MI::NonMI, "source " + KShell::quoteArg(configGdbScript.toLocalFile())); } // FIXME: have a check box option that controls remote debugging if (runShellScript.isValid()) { // Special for remote debug, the remote inferior is started by this shell script QByteArray tty(m_tty->getSlave().toLatin1()); QByteArray options = QByteArray(">") + tty + QByteArray(" 2>&1 <") + tty; QProcess *proc = new QProcess; QStringList arguments; arguments << "-c" << KShell::quoteArg(runShellScript.toLocalFile()) + ' ' + KShell::quoteArg(executable) + QString::fromLatin1(options); qCDebug(DEBUGGERGDB) << "starting sh" << arguments; proc->start("sh", arguments); //PORTING TODO QProcess::DontCare); } if (runGdbScript.isValid()) { // Special for remote debug, gdb script at run is requested, to connect to remote inferior // Race notice: wait for the remote gdbserver/executable // - but that might be an issue for this script to handle... // Note: script could contain "run" or "continue" // Future: the shell script should be able to pass info (like pid) // to the gdb script... addCommand(new SentinelCommand([this, runGdbScript]() { breakpointController()->initSendBreakpoints(); breakpointController()->setDeleteDuplicateBreakpoints(true); qCDebug(DEBUGGERGDB) << "Running gdb script " << KShell::quoteArg(runGdbScript.toLocalFile()); addCommand(MI::NonMI, "source " + KShell::quoteArg(runGdbScript.toLocalFile()), [this](const MI::ResultRecord&) { breakpointController()->setDeleteDuplicateBreakpoints(false); }, CmdMaybeStartsRunning); raiseEvent(connected_to_program); }, CmdMaybeStartsRunning)); } else { // normal local debugging addCommand(MI::FileExecAndSymbols, KShell::quoteArg(executable), this, &DebugSession::handleFileExecAndSymbols, CmdHandlesError); raiseEvent(connected_to_program); addCommand(new SentinelCommand([this]() { breakpointController()->initSendBreakpoints(); addCommand(MI::ExecRun, QString(), CmdMaybeStartsRunning); }, CmdMaybeStartsRunning)); } return true; } void DebugSession::handleVersion(const QStringList& s) { qCDebug(DEBUGGERGDB) << s.first(); // minimal version is 7.0,0 QRegExp rx("([7-9]+)\\.([0-9]+)(\\.([0-9]+))?"); int idx = rx.indexIn(s.first()); if (idx == -1) { if (!qobject_cast(qApp)) { //for unittest qFatal("You need a graphical application."); } KMessageBox::error( qApp->activeWindow(), i18n("You need gdb 7.0.0 or higher.
" "You are using: %1", s.first()), i18n("gdb error")); stopDebugger(); } } -void DebugSession::handleFileExecAndSymbols(const MI::ResultRecord& r) +void DebugSession::handleFileExecAndSymbols(const ResultRecord& r) { if (r.reason == "error") { KMessageBox::error( qApp->activeWindow(), i18n("Could not start debugger:
")+ r["msg"].literal(), i18n("Startup error")); stopDebugger(); } } diff --git a/debuggers/gdb/disassemblewidget.cpp b/debuggers/gdb/disassemblewidget.cpp index 41068fcf44..9aba320223 100644 --- a/debuggers/gdb/disassemblewidget.cpp +++ b/debuggers/gdb/disassemblewidget.cpp @@ -1,437 +1,538 @@ /* * 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); + + m_disassemblyFlavorAtt = new QAction(i18n("&AT&&T"), this); + m_disassemblyFlavorAtt->setToolTip(i18n("GDB will use the AT&T disassembly flavor (e.g. mov 0xc(%ebp),%eax).")); + m_disassemblyFlavorAtt->setData(DisassemblyFlavorATT); + m_disassemblyFlavorAtt->setCheckable(true); + + m_disassemblyFlavorIntel = new QAction(i18n("&Intel"), this); + m_disassemblyFlavorIntel->setToolTip(i18n("GDB will use the Intel disassembly flavor (e.g. mov eax, DWORD PTR [ebp+0xc]).")); + m_disassemblyFlavorIntel->setData(DisassemblyFlavorIntel); + m_disassemblyFlavorIntel->setCheckable(true); + + m_disassemblyFlavorActionGroup = new QActionGroup(this); + m_disassemblyFlavorActionGroup->setExclusive(true); + m_disassemblyFlavorActionGroup->addAction(m_disassemblyFlavorAtt); + m_disassemblyFlavorActionGroup->addAction(m_disassemblyFlavorIntel); + connect(m_disassemblyFlavorActionGroup, &QActionGroup::triggered, widget, &DisassembleWidget::setDisassemblyFlavor); + } +} + +void DisassembleWindow::setDisassemblyFlavor(DisassemblyFlavor flavor) +{ + switch(flavor) + { + default: + case DisassemblyFlavorUnknown: + m_disassemblyFlavorAtt->setChecked(false); + m_disassemblyFlavorIntel->setChecked(false); + break; + case DisassemblyFlavorATT: + m_disassemblyFlavorAtt->setChecked(true); + m_disassemblyFlavorIntel->setChecked(false); + break; + case DisassemblyFlavorIntel: + m_disassemblyFlavorAtt->setChecked(false); + m_disassemblyFlavorIntel->setChecked(true); + break; } } void DisassembleWindow::contextMenuEvent(QContextMenuEvent *e) { QMenu popup(this); popup.addAction(m_selectAddrAction); popup.addAction(m_jumpToLocation); popup.addAction(m_runUntilCursor); + QMenu* disassemblyFlavorMenu = popup.addMenu(i18n("Disassembly flavor")); + disassemblyFlavorMenu->addAction(m_disassemblyFlavorAtt); + disassemblyFlavorMenu->addAction(m_disassemblyFlavorIntel); popup.exec(e->globalPos()); } /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ DisassembleWidget::DisassembleWidget(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_) { + updateDisassemblyFlavor(); m_registersManager->updateRegisters(); if (!displayCurrent()) disassembleMemoryRegion(); } } } /***************************************************************************/ void DisassembleWidget::slotShowStepInSource(const QUrl&, int, const QString& currentAddress) { update(currentAddress); } void DisassembleWidget::updateExecutionAddressHandler(const ResultRecord& r) { const Value& content = r["asm_insns"]; const Value& pc = content[0]; if( pc.hasField("address") ){ QString addr = pc["address"].literal(); address_ = addr.toULong(&ok,16); disassembleMemoryRegion(addr); } } /***************************************************************************/ void DisassembleWidget::disassembleMemoryRegion(const QString& from, const QString& to) { DebugSession *s = qobject_cast(KDevelop::ICore:: self()->debugController()->currentSession()); if(!s || !s->isRunning()) return; //only get $pc if (from.isEmpty()){ s->addCommand(DataDisassemble, "-s \"$pc\" -e \"$pc+1\" -- 0", this, &DisassembleWidget::updateExecutionAddressHandler); }else{ QString cmd = (to.isEmpty())? QString("-s %1 -e \"%1 + 256\" -- 0").arg(from ): QString("-s %1 -e %2+1 -- 0").arg(from).arg(to); // if both addr set s->addCommand(DataDisassemble, cmd, this, &DisassembleWidget::disassembleMemoryHandler); } } /***************************************************************************/ void DisassembleWidget::disassembleMemoryHandler(const ResultRecord& r) { const Value& content = r["asm_insns"]; QString currentFunction; m_disassembleWindow->clear(); for(int i = 0; i < content.size(); ++i) { const Value& line = content[i]; QString addr, fct, offs, inst; if( line.hasField("address") ) addr = line["address"].literal(); if( line.hasField("func-name") ) fct = line["func-name"].literal(); if( line.hasField("offset") ) offs = line["offset"].literal(); if( line.hasField("inst") ) inst = line["inst"].literal(); //We use offset at the same column where function is. if(currentFunction == fct){ if(!fct.isEmpty()){ fct = QString("+") + offs; } }else { currentFunction = fct; } m_disassembleWindow->addTopLevelItem(new QTreeWidgetItem(m_disassembleWindow, QStringList() << QString() << addr << fct << inst)); if (i == 0) { lower_ = addr.toULong(&ok,16); } else if (i == content.size()-1) { upper_ = addr.toULong(&ok,16); } } displayCurrent(); m_disassembleWindow->resizeColumnToContents(Icon); // make Icon always visible m_disassembleWindow->resizeColumnToContents(Address); // make entire address always visible } void DisassembleWidget::showEvent(QShowEvent*) { slotActivate(true); //it doesn't work for large names of functions // for (int i = 0; i < m_disassembleWindow->model()->columnCount(); ++i) // m_disassembleWindow->resizeColumnToContents(i); } void DisassembleWidget::hideEvent(QHideEvent*) { slotActivate(false); } void DisassembleWidget::slotDeactivate() { slotActivate(false); } void DisassembleWidget::enableControls(bool enabled) { m_disassembleWindow->setEnabled(enabled); } void DisassembleWidget::slotChangeAddress() { if(!m_dlg) return; m_dlg->updateOkState(); if (!m_disassembleWindow->selectedItems().isEmpty()) { m_dlg->setAddress(m_disassembleWindow->selectedItems().first()->text(Address)); } if (m_dlg->exec() == QDialog::Rejected) return; unsigned long addr = m_dlg->address().toULong(&ok,16); if (addr < lower_ || addr > upper_ || !displayCurrent()) disassembleMemoryRegion(m_dlg->address()); } void SelectAddressDialog::setAddress ( const QString& address ) { m_ui.comboBox->setCurrentItem ( address, true ); } void DisassembleWidget::update(const QString &address) { if (!active_) { return; } address_ = address.toULong(&ok, 16); if (!displayCurrent()) { disassembleMemoryRegion(); } m_registersManager->updateRegisters(); } + +void DisassembleWidget::setDisassemblyFlavor(QAction* action) +{ + DebugSession* s = qobject_cast(KDevelop::ICore:: + self()->debugController()->currentSession()); + if(!s || !s->isRunning()) { + return; + } + + DisassemblyFlavor disassemblyFlavor = static_cast(action->data().toInt()); + QString cmd; + switch(disassemblyFlavor) + { + default: + // unknown flavor, do not build a GDB command + break; + case DisassemblyFlavorATT: + cmd = QStringLiteral("disassembly-flavor att"); + break; + case DisassemblyFlavorIntel: + cmd = QStringLiteral("disassembly-flavor intel"); + break; + } + qCDebug(DEBUGGERGDB) << "Disassemble widget set " << cmd; + + if (!cmd.isEmpty()) { + s->addCommand(GdbSet, cmd, this, &DisassembleWidget::setDisassemblyFlavorHandler); + } +} + +void DisassembleWidget::setDisassemblyFlavorHandler(const ResultRecord& r) +{ + if (r.reason == "done" && active_) { + disassembleMemoryRegion(); + } +} + +void DisassembleWidget::updateDisassemblyFlavor() +{ + DebugSession* s = qobject_cast(KDevelop::ICore:: + self()->debugController()->currentSession()); + if(!s || !s->isRunning()) { + return; + } + + s->addCommand(GdbShow, QStringLiteral("disassembly-flavor"), this, &DisassembleWidget::showDisassemblyFlavorHandler); +} + +void DisassembleWidget::showDisassemblyFlavorHandler(const ResultRecord& r) +{ + const Value& value = r["value"]; + qCDebug(DEBUGGERGDB) << "Disassemble widget disassembly flavor" << value.literal(); + + DisassemblyFlavor disassemblyFlavor = DisassemblyFlavorUnknown; + if (value.literal() == "att") { + disassemblyFlavor = DisassemblyFlavorATT; + } else if (value.literal() == "intel") { + disassemblyFlavor = DisassemblyFlavorIntel; + } + m_disassembleWindow->setDisassemblyFlavor(disassemblyFlavor); +} diff --git a/debuggers/gdb/disassemblewidget.h b/debuggers/gdb/disassemblewidget.h index b1fcca636f..1c4a36871d 100644 --- a/debuggers/gdb/disassemblewidget.h +++ b/debuggers/gdb/disassemblewidget.h @@ -1,164 +1,179 @@ /* * GDB Debugger Support * * Copyright 1999 John Birch * Copyright 2007 Hamish Rodda * Copyright 2013 Vlas Puhov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _DISASSEMBLEWIDGET_H_ #define _DISASSEMBLEWIDGET_H_ #include "mi/mi.h" #include #include #include #include "ui_selectaddressdialog.h" /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ class QSplitter; namespace KDevelop { class IDebugSession; } namespace KDevMI { namespace GDB { class RegistersManager; class SelectAddressDialog : public QDialog { Q_OBJECT public: SelectAddressDialog(QWidget *parent = 0); - + QString address() const; void setAddress(const QString& address); bool hasValidAddress() const; void updateOkState(); - + private Q_SLOTS: void validateInput(); void itemSelected(); - + private: Ui::SelectAddressDialog m_ui; }; class DisassembleWidget; +enum DisassemblyFlavor { + DisassemblyFlavorUnknown = -1, + DisassemblyFlavorATT = 0, + DisassemblyFlavorIntel, +}; + class DisassembleWindow : public QTreeWidget { public: DisassembleWindow(QWidget *parent, DisassembleWidget* widget); + void setDisassemblyFlavor(DisassemblyFlavor flavor); + protected: void contextMenuEvent(QContextMenuEvent *e) override; private: QAction* m_selectAddrAction; QAction* m_jumpToLocation; QAction* m_runUntilCursor; + QAction* m_disassemblyFlavorAtt; + QAction* m_disassemblyFlavorIntel; + QActionGroup* m_disassemblyFlavorActionGroup; }; class Breakpoint; class DebugSession; class CppDebuggerPlugin; class DisassembleWidget : public QWidget { Q_OBJECT public: enum Columns { Icon, Address, Function, Instruction, ColumnCount }; DisassembleWidget( CppDebuggerPlugin* plugin, QWidget *parent=0 ); ~DisassembleWidget() override; Q_SIGNALS: void requestRaise(); public Q_SLOTS: void slotActivate(bool activate); void slotDeactivate(); void slotShowStepInSource(const QUrl &fileName, int lineNum, const QString &address); void slotChangeAddress(); ///Disassembles code at @p address and updates registers void update(const QString &address); void jumpToCursor(); void runToCursor(); + void setDisassemblyFlavor(QAction* action); private Q_SLOTS: void currentSessionChanged(KDevelop::IDebugSession* session); protected: void showEvent(QShowEvent*) override; void hideEvent(QHideEvent*) override; void enableControls(bool enabled); private: bool displayCurrent(); - + void updateDisassemblyFlavor(); + /// Disassembles memory region from..to /// if from is empty current execution position is used /// if to is empty, 256 bytes range is taken void disassembleMemoryRegion(const QString& from=QString(), const QString& to=QString() ); /// callbacks for GDBCommands void disassembleMemoryHandler(const MI::ResultRecord& r); void updateExecutionAddressHandler(const MI::ResultRecord& r); + void setDisassemblyFlavorHandler(const MI::ResultRecord& r); + void showDisassemblyFlavorHandler(const MI::ResultRecord& r); //for str to uint conversion. bool ok; bool active_; unsigned long lower_; unsigned long upper_; unsigned long address_; RegistersManager* m_registersManager ; DisassembleWindow * m_disassembleWindow; SelectAddressDialog* m_dlg; KConfigGroup m_config; QSplitter *m_splitter; }; } // end of namespace GDB } // end of namespace KDevMI /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ #endif diff --git a/debuggers/gdb/gdboutputwidget.cpp b/debuggers/gdb/gdboutputwidget.cpp index 942e83c048..651b53b854 100644 --- a/debuggers/gdb/gdboutputwidget.cpp +++ b/debuggers/gdb/gdboutputwidget.cpp @@ -1,451 +1,451 @@ /* * GDB Debugger Support * * Copyright 2003 John Birch * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gdboutputwidget.h" #include "dbgglobal.h" #include "debuggerplugin.h" #include "debuglog.h" #include "debugsession.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevMI::GDB; /***************************************************************************/ GDBOutputWidget::GDBOutputWidget(CppDebuggerPlugin* plugin, QWidget *parent) : QWidget(parent), m_userGDBCmdEditor(0), m_Interrupt(0), m_gdbView(0), showInternalCommands_(false), maxLines_(5000) { setWindowIcon(QIcon::fromTheme("dialog-scripts", windowIcon())); setWindowTitle(i18n("GDB Output")); setWhatsThis(i18n("GDB output

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

")); m_gdbView = new OutputTextEdit(this); m_gdbView->setReadOnly(true); m_userGDBCmdEditor = new KHistoryComboBox (this); QLabel *label = new QLabel(i18n("&GDB cmd:"), this); label->setBuddy(m_userGDBCmdEditor); m_Interrupt = new QToolButton( this ); m_Interrupt->setIcon ( QIcon::fromTheme( "media-playback-pause" ) ); m_Interrupt->setToolTip( i18n ( "Pause execution of the app to enter gdb commands" ) ); QVBoxLayout *topLayout = new QVBoxLayout(this); topLayout->addWidget(m_gdbView); topLayout->setStretchFactor(m_gdbView, 1); topLayout->setMargin(0); QBoxLayout *userGDBCmdEntry = new QHBoxLayout(); userGDBCmdEntry->addWidget(label); userGDBCmdEntry->addWidget(m_userGDBCmdEditor); userGDBCmdEntry->setStretchFactor(m_userGDBCmdEditor, 1); userGDBCmdEntry->addWidget(m_Interrupt); topLayout->addLayout(userGDBCmdEntry); setLayout(topLayout); slotStateChanged(s_none, s_dbgNotStarted); connect(m_userGDBCmdEditor, static_cast(&KHistoryComboBox::returnPressed), this, &GDBOutputWidget::slotGDBCmd); connect(m_Interrupt, &QToolButton::clicked, this, &GDBOutputWidget::breakInto); updateTimer_.setSingleShot(true); connect(&updateTimer_, &QTimer::timeout, this, &GDBOutputWidget::flushPending); connect(KDevelop::ICore::self()->debugController(), &KDevelop::IDebugController::currentSessionChanged, this, &GDBOutputWidget::currentSessionChanged); connect(plugin, &CppDebuggerPlugin::reset, this, &GDBOutputWidget::clear); connect(plugin, &CppDebuggerPlugin::raiseDebuggerConsoleViews, this, &GDBOutputWidget::requestRaise); currentSessionChanged(KDevelop::ICore::self()->debugController()->currentSession()); // TODO Port to KF5 // connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), // this, SLOT(updateColors())); updateColors(); } void GDBOutputWidget::updateColors() { KColorScheme scheme(QPalette::Active); gdbColor_ = scheme.foreground(KColorScheme::LinkText).color(); errorColor_ = scheme.foreground(KColorScheme::NegativeText).color(); } void GDBOutputWidget::currentSessionChanged(KDevelop::IDebugSession* s) { DebugSession *session = qobject_cast(s); if (!session) return; connect(this, &GDBOutputWidget::userGDBCmd, session, &DebugSession::addUserCommand); connect(this, &GDBOutputWidget::breakInto, session, &DebugSession::interruptDebugger); connect(session, &DebugSession::debuggerInternalCommandOutput, this, &GDBOutputWidget::slotInternalCommandStdout); connect(session, &DebugSession::debuggerUserCommandOutput, this, &GDBOutputWidget::slotUserCommandStdout); connect(session, &DebugSession::debuggerStateChanged, this, &GDBOutputWidget::slotStateChanged); slotStateChanged(s_none, session->debuggerState()); } /***************************************************************************/ GDBOutputWidget::~GDBOutputWidget() { delete m_gdbView; delete m_userGDBCmdEditor; } /***************************************************************************/ void GDBOutputWidget::clear() { if (m_gdbView) m_gdbView->clear(); userCommands_.clear(); allCommands_.clear(); } /***************************************************************************/ void GDBOutputWidget::slotInternalCommandStdout(const QString& line) { newStdoutLine(line, true); } void GDBOutputWidget::slotUserCommandStdout(const QString& line) { qCDebug(DEBUGGERGDB) << "User command stdout: " << line; newStdoutLine(line, false); } namespace { QString colorify(QString text, const QColor& color) { // Make sure the newline is at the end of the newly-added // string. This is so that we can always correctly remove // newline inside 'flushPending'. if (!text.endsWith('\n')) text.append('\n'); if (text.endsWith('\n')) { text.remove(text.length()-1, 1); } text = "" + text + "
"; return text; } } void GDBOutputWidget::newStdoutLine(const QString& line, bool internal) { QString s = line.toHtmlEscaped(); if (s.startsWith("(gdb)")) { s = colorify(s, gdbColor_); } else s.replace('\n', "
"); allCommands_.append(s); allCommandsRaw_.append(line); trimList(allCommands_, maxLines_); trimList(allCommandsRaw_, maxLines_); if (!internal) { userCommands_.append(s); userCommandsRaw_.append(line); trimList(userCommands_, maxLines_); trimList(userCommandsRaw_, maxLines_); } if (!internal || showInternalCommands_) showLine(s); } void GDBOutputWidget::showLine(const QString& line) { pendingOutput_ += line; // To improve performance, we update the view after some delay. if (!updateTimer_.isActive()) { updateTimer_.start(100); } } void GDBOutputWidget::trimList(QStringList& l, int max_size) { int length = l.count(); if (length > max_size) { for(int to_delete = length - max_size; to_delete; --to_delete) { l.erase(l.begin()); } } } void GDBOutputWidget::setShowInternalCommands(bool show) { if (show != showInternalCommands_) { showInternalCommands_ = show; // Set of strings to show changes, text edit still has old // set. Refresh. m_gdbView->clear(); QStringList& newList = showInternalCommands_ ? allCommands_ : userCommands_; QStringList::iterator i = newList.begin(), e = newList.end(); for(; i != e; ++i) { // Note that color formatting is already applied to '*i'. showLine(*i); } } } /***************************************************************************/ void GDBOutputWidget::slotReceivedStderr(const char* line) { QString colored = colorify(QString::fromLatin1(line).toHtmlEscaped(), errorColor_); // Errors are shown inside user commands too. allCommands_.append(colored); trimList(allCommands_, maxLines_); userCommands_.append(colored); trimList(userCommands_, maxLines_); allCommandsRaw_.append(line); trimList(allCommandsRaw_, maxLines_); userCommandsRaw_.append(line); trimList(userCommandsRaw_, maxLines_); showLine(colored); } /***************************************************************************/ void GDBOutputWidget::slotGDBCmd() { QString GDBCmd(m_userGDBCmdEditor->currentText()); if (!GDBCmd.isEmpty()) { m_userGDBCmdEditor->addToHistory(GDBCmd); m_userGDBCmdEditor->clearEditText(); emit userGDBCmd(GDBCmd); } } void GDBOutputWidget::flushPending() { m_gdbView->setUpdatesEnabled(false); // QTextEdit adds newline after paragraph automatically. // So, remove trailing newline to avoid double newlines. if (pendingOutput_.endsWith('\n')) pendingOutput_.remove(pendingOutput_.length()-1, 1); Q_ASSERT(!pendingOutput_.endsWith('\n')); QTextDocument *document = m_gdbView->document(); QTextCursor cursor(document); cursor.movePosition(QTextCursor::End); cursor.insertHtml(pendingOutput_); pendingOutput_ = ""; m_gdbView->verticalScrollBar()->setValue(m_gdbView->verticalScrollBar()->maximum()); m_gdbView->setUpdatesEnabled(true); m_gdbView->update(); if (m_cmdEditorHadFocus) { m_userGDBCmdEditor->setFocus(); } } /***************************************************************************/ -void GDBOutputWidget::slotStateChanged(DBGStateFlags oldStatus, DBGStateFlags newStatus) +void GDBOutputWidget::slotStateChanged(KDevMI::DBGStateFlags oldStatus, KDevMI::DBGStateFlags newStatus) { Q_UNUSED(oldStatus) if (newStatus & s_dbgNotStarted) { m_Interrupt->setEnabled(false); m_userGDBCmdEditor->setEnabled(false); return; } else { m_Interrupt->setEnabled(true); } if (newStatus & s_dbgBusy) { if (m_userGDBCmdEditor->isEnabled()) { m_cmdEditorHadFocus = m_userGDBCmdEditor->hasFocus(); } m_userGDBCmdEditor->setEnabled(false); } else { m_userGDBCmdEditor->setEnabled(true); } } /***************************************************************************/ void GDBOutputWidget::focusInEvent(QFocusEvent */*e*/) { m_gdbView->verticalScrollBar()->setValue(m_gdbView->verticalScrollBar()->maximum()); m_userGDBCmdEditor->setFocus(); } void GDBOutputWidget::savePartialProjectSession() { KConfigGroup config(KSharedConfig::openConfig(), "GDB Debugger"); config.writeEntry("showInternalCommands", showInternalCommands_); } void GDBOutputWidget::restorePartialProjectSession() { KConfigGroup config(KSharedConfig::openConfig(), "GDB Debugger"); showInternalCommands_ = config.readEntry("showInternalCommands", false); } void GDBOutputWidget::contextMenuEvent(QContextMenuEvent * e) { QScopedPointer popup(new QMenu(this)); QAction* action = popup->addAction(i18n("Show Internal Commands"), this, SLOT(toggleShowInternalCommands())); action->setCheckable(true); action->setChecked(showInternalCommands_); action->setWhatsThis(i18n( "Controls if commands issued internally by KDevelop " "will be shown or not.
" "This option will affect only future commands, it will not " "add or remove already issued commands from the view.")); popup->addAction(i18n("Copy All"), this, SLOT(copyAll())); popup->exec(e->globalPos()); } void GDBOutputWidget::copyAll() { /* See comments for allCommandRaw_ for explanations of this complex logic, as opposed to calling text(). */ const QStringList& raw = showInternalCommands_ ? allCommandsRaw_ : userCommandsRaw_; QString text; for (int i = 0; i < raw.size(); ++i) text += raw.at(i); // Make sure the text is pastable both with Ctrl-C and with // middle click. QApplication::clipboard()->setText(text, QClipboard::Clipboard); QApplication::clipboard()->setText(text, QClipboard::Selection); } void GDBOutputWidget::toggleShowInternalCommands() { setShowInternalCommands(!showInternalCommands_); } OutputTextEdit::OutputTextEdit(GDBOutputWidget * parent) : QTextEdit(parent) { } void OutputTextEdit::contextMenuEvent(QContextMenuEvent * event) { QMenu* popup = createStandardContextMenu(); QAction* action = popup->addAction(i18n("Show Internal Commands"), parent(), SLOT(toggleShowInternalCommands())); action->setCheckable(true); action->setChecked(static_cast(parent())->showInternalCommands()); action->setWhatsThis(i18n( "Controls if commands issued internally by KDevelop " "will be shown or not.
" "This option will affect only future commands, it will not " "add or remove already issued commands from the view.")); popup->exec(event->globalPos()); } bool GDBOutputWidget::showInternalCommands() const { return showInternalCommands_; } diff --git a/debuggers/gdb/memviewdlg.cpp b/debuggers/gdb/memviewdlg.cpp index 6be5426609..a0b7ae46ba 100644 --- a/debuggers/gdb/memviewdlg.cpp +++ b/debuggers/gdb/memviewdlg.cpp @@ -1,526 +1,531 @@ /*************************************************************************** begin : Tue Oct 5 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org ************************************************************************** * Copyright 2006 Vladimir Prus *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "memviewdlg.h" #include "dbgglobal.h" #include "debugsession.h" #include "mi/micommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevMI::GDB; using KDevMI::MI::CommandType; /** Container for controls that select memory range. The memory range selection is embedded into memory view widget, it's not a standalone dialog. However, we want to have easy way to hide/show all controls, so we group them in this class. */ class MemoryRangeSelector : public QWidget { public: QLineEdit* startAddressLineEdit; QLineEdit* amountLineEdit; QPushButton* okButton; QPushButton* cancelButton; MemoryRangeSelector(QWidget* parent) : QWidget(parent) { QVBoxLayout* l = new QVBoxLayout(this); // Grid layout: labels + address field QGridLayout* gl = new QGridLayout(this); l->addLayout(gl); QLabel* l1 = new QLabel(i18n("Start"), this); gl->addWidget(l1, 0, 1); startAddressLineEdit = new QLineEdit(this); gl->addWidget(startAddressLineEdit, 0, 3); QLabel* l2 = new QLabel(i18n("Amount"), this); gl->addWidget(l2, 2, 1); amountLineEdit = new QLineEdit(this); gl->addWidget(amountLineEdit, 2, 3); l->addSpacing(2); QHBoxLayout* hb = new QHBoxLayout(this); l->addLayout(hb); hb->addStretch(); okButton = new QPushButton(i18n("OK"), this); hb->addWidget(okButton); cancelButton = new QPushButton(i18n("Cancel"), this); hb->addWidget(cancelButton); l->addSpacing(2); setLayout(l); connect(startAddressLineEdit, SIGNAL(returnPressed()), okButton, SLOT(animateClick())); connect(amountLineEdit, SIGNAL(returnPressed()), okButton, SLOT(animateClick())); } }; MemoryView::MemoryView(QWidget* parent) : QWidget(parent), // New memory view can be created only when debugger is active, // so don't set s_appNotStarted here. khexedit2_widget(0), amount_(0), data_(0), debuggerState_(0) { setWindowTitle(i18n("Memory view")); emit captionChanged(windowTitle()); initWidget(); if (isOk()) slotEnableOrDisable(); connect(KDevelop::ICore::self()->debugController(), SIGNAL(currentSessionChanged(KDevelop::IDebugSession*)), SLOT(currentSessionChanged(KDevelop::IDebugSession*))); } void MemoryView::currentSessionChanged(KDevelop::IDebugSession* s) { DebugSession *session = qobject_cast(s); if (!session) return; connect(session, SIGNAL(gdbStateChanged(DBGStateFlags,DBGStateFlags)), SLOT(slotStateChanged(DBGStateFlags,DBGStateFlags))); } void MemoryView::slotStateChanged(DBGStateFlags oldState, DBGStateFlags newState) { Q_UNUSED(oldState); debuggerStateChanged(newState); } void MemoryView::initWidget() { QVBoxLayout *l = new QVBoxLayout(this); + l->setContentsMargins(0, 0, 0, 0); khexedit2_widget = KHE::createBytesEditWidget(this); if (!khexedit2_widget) { QTextEdit* edit = new QTextEdit(this); l->addWidget(edit); edit->setText( "

Not Available

" "

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

"); return; } KHE::BytesEditInterface *bytesEdit = KHE::bytesEditInterface(khexedit2_widget); if (bytesEdit) { bytesEdit->setReadOnly(false); bytesEdit->setOverwriteMode(true); bytesEdit->setOverwriteOnly(true); bytesEdit->setAutoDelete(false); } KHE::ValueColumnInterface *valueColumn = KHE::valueColumnInterface(khexedit2_widget); if (valueColumn) { valueColumn->setCoding(KHE::ValueColumnInterface::HexadecimalCoding); valueColumn->setNoOfGroupedBytes(4); valueColumn->setByteSpacingWidth(2); valueColumn->setGroupSpacingWidth(12); valueColumn->setResizeStyle(KHE::ValueColumnInterface::LockGrouping); } KHE::CharColumnInterface *charColumn = KHE::charColumnInterface(khexedit2_widget); if(charColumn) { charColumn->setShowUnprintable(false); charColumn->setSubstituteChar('*'); } rangeSelector_ = new MemoryRangeSelector(this); l->addWidget(rangeSelector_); connect(rangeSelector_->okButton, SIGNAL(clicked()), this, SLOT(slotChangeMemoryRange())); connect(rangeSelector_->cancelButton, SIGNAL(clicked()), this, SLOT(slotHideRangeDialog())); connect(rangeSelector_->startAddressLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotEnableOrDisable())); connect(rangeSelector_->amountLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotEnableOrDisable())); l->addWidget(khexedit2_widget); } void MemoryView::debuggerStateChanged(DBGStateFlags state) { if (isOk()) { debuggerState_ = state; slotEnableOrDisable(); } } void MemoryView::slotHideRangeDialog() { rangeSelector_->hide(); } void MemoryView::slotChangeMemoryRange() { DebugSession *session = qobject_cast( KDevelop::ICore::self()->debugController()->currentSession()); if (!session) return; - session->addCommand(new ExpressionValueCommand( - rangeSelector_->amountLineEdit->text(), - this, &MemoryView::sizeComputed)); + QString amount = rangeSelector_->amountLineEdit->text(); + if(amount.isEmpty()) + amount = QString("sizeof(%1)").arg(rangeSelector_->startAddressLineEdit->text()); + + session->addCommand(new ExpressionValueCommand(amount, this, &MemoryView::sizeComputed)); } void MemoryView::sizeComputed(const QString& size) { DebugSession *session = qobject_cast( KDevelop::ICore::self()->debugController()->currentSession()); if (!session) return; session->addCommand(new GDBCommand(MI::DataReadMemory, QString("%1 x 1 1 %2") .arg(rangeSelector_->startAddressLineEdit->text()) .arg(size), this, &MemoryView::memoryRead)); } void MemoryView::memoryRead(const MI::ResultRecord& r) { const MI::Value& content = r["memory"][0]["data"]; bool startStringConverted; start_ = r["addr"].literal().toULongLong(&startStringConverted, 16); amount_ = content.size(); startAsString_ = rangeSelector_->startAddressLineEdit->text(); amountAsString_ = rangeSelector_->amountLineEdit->text(); setWindowTitle(i18np("%2 (1 byte)","%2 (%1 bytes)",amount_,startAsString_)); emit captionChanged(windowTitle()); KHE::BytesEditInterface* bytesEditor = KHE::bytesEditInterface(khexedit2_widget); bytesEditor->setData(this->data_, 0); delete[] this->data_; this->data_ = new char[amount_]; for(int i = 0; i < content.size(); ++i) { this->data_[i] = content[i].literal().toInt(0, 16); } bytesEditor->setData(this->data_, amount_); slotHideRangeDialog(); } void MemoryView::memoryEdited(int start, int end) { DebugSession *session = qobject_cast( KDevelop::ICore::self()->debugController()->currentSession()); if (!session) return; for(int i = start; i <= end; ++i) { session->addCommand(new GDBCommand(MI::GdbSet, QString("*(char*)(%1 + %2) = %3") .arg(start_) .arg(i) .arg(QString::number(data_[i])))); } } void MemoryView::contextMenuEvent(QContextMenuEvent *e) { if (!isOk()) return; KHE::BytesEditInterface *bytesEdit = KHE::bytesEditInterface(khexedit2_widget); KHE::ValueColumnInterface *valueColumn = KHE::valueColumnInterface(khexedit2_widget); QMenu menu; bool app_running = !(debuggerState_ & s_appNotStarted); QAction* reload = menu.addAction(i18n("&Reload")); reload->setIcon(QIcon::fromTheme("view-refresh")); reload->setEnabled(app_running && amount_ != 0); QActionGroup *formatGroup = NULL; QActionGroup *groupingGroup = NULL; if (valueColumn) { // make Format menu with action group QMenu *formatMenu = new QMenu(i18n("&Format")); formatGroup = new QActionGroup(formatMenu); QAction *binary = formatGroup->addAction(i18n("&Binary")); binary->setData(KHE::ValueColumnInterface::BinaryCoding); binary->setShortcut(Qt::Key_B); formatMenu->addAction(binary); QAction *octal = formatGroup->addAction(i18n("&Octal")); octal->setData(KHE::ValueColumnInterface::OctalCoding); octal->setShortcut(Qt::Key_O); formatMenu->addAction(octal); QAction *decimal = formatGroup->addAction(i18n("&Decimal")); decimal->setData(KHE::ValueColumnInterface::DecimalCoding); decimal->setShortcut(Qt::Key_D); formatMenu->addAction(decimal); QAction *hex = formatGroup->addAction(i18n("&Hexadecimal")); hex->setData(KHE::ValueColumnInterface::HexadecimalCoding); hex->setShortcut(Qt::Key_H); formatMenu->addAction(hex); foreach(QAction* act, formatGroup->actions()) { act->setCheckable(true); act->setChecked(act->data().toInt() == valueColumn->coding()); act->setShortcutContext(Qt::WidgetWithChildrenShortcut); } menu.addMenu(formatMenu); // make Grouping menu with action group QMenu *groupingMenu = new QMenu(i18n("&Grouping")); groupingGroup = new QActionGroup(groupingMenu); QAction *group0 = groupingGroup->addAction(i18n("&0")); group0->setData(0); group0->setShortcut(Qt::Key_0); groupingMenu->addAction(group0); QAction *group1 = groupingGroup->addAction(i18n("&1")); group1->setData(1); group1->setShortcut(Qt::Key_1); groupingMenu->addAction(group1); QAction *group2 = groupingGroup->addAction(i18n("&2")); group2->setData(2); group2->setShortcut(Qt::Key_2); groupingMenu->addAction(group2); QAction *group4 = groupingGroup->addAction(i18n("&4")); group4->setData(4); group4->setShortcut(Qt::Key_4); groupingMenu->addAction(group4); QAction *group8 = groupingGroup->addAction(i18n("&8")); group8->setData(8); group8->setShortcut(Qt::Key_8); groupingMenu->addAction(group8); QAction *group16 = groupingGroup->addAction(i18n("1&6")); group16->setData(16); group16->setShortcut(Qt::Key_6); groupingMenu->addAction(group16); foreach(QAction* act, groupingGroup->actions()) { act->setCheckable(true); act->setChecked(act->data().toInt() == valueColumn->noOfGroupedBytes()); act->setShortcutContext(Qt::WidgetWithChildrenShortcut); } menu.addMenu(groupingMenu); } QAction* write = menu.addAction(i18n("Write changes")); write->setIcon(QIcon::fromTheme("document-save")); write->setEnabled(app_running && bytesEdit && bytesEdit->isModified()); QAction* range = menu.addAction(i18n("Change memory range")); range->setEnabled(app_running && !rangeSelector_->isVisible()); range->setIcon(QIcon::fromTheme("document-edit")); QAction* close = menu.addAction(i18n("Close this view")); close->setIcon(QIcon::fromTheme("window-close")); QAction* result = menu.exec(e->globalPos()); if (result == reload) { // We use numeric start_ and amount_ stored in this, // not textual startAsString_ and amountAsString_, // because program position might have changes and expressions // are no longer valid. DebugSession *session = qobject_cast( KDevelop::ICore::self()->debugController()->currentSession()); if (session) { session->addCommand(new GDBCommand(MI::DataReadMemory, QString("%1 x 1 1 %2").arg(start_).arg(amount_), this, &MemoryView::memoryRead)); } } if (result && formatGroup && formatGroup == result->actionGroup()) valueColumn->setCoding((KHE::ValueColumnInterface::KCoding)result->data().toInt()); if (result && groupingGroup && groupingGroup == result->actionGroup()) valueColumn->setNoOfGroupedBytes(result->data().toInt()); if (result == write) { memoryEdited(0, amount_); bytesEdit->setModified(false); } if (result == range) { rangeSelector_->startAddressLineEdit->setText(startAsString_); rangeSelector_->amountLineEdit->setText(amountAsString_); rangeSelector_->show(); rangeSelector_->startAddressLineEdit->setFocus(); } if (result == close) delete this; } bool MemoryView::isOk() const { return khexedit2_widget; } void MemoryView::slotEnableOrDisable() { bool app_started = !(debuggerState_ & s_appNotStarted); - bool enabled_ = app_started && - !rangeSelector_->startAddressLineEdit->text().isEmpty() && - !rangeSelector_->amountLineEdit->text().isEmpty(); + bool enabled_ = app_started && !rangeSelector_->startAddressLineEdit->text().isEmpty(); rangeSelector_->okButton->setEnabled(enabled_); } MemoryViewerWidget::MemoryViewerWidget(CppDebuggerPlugin* /*plugin*/, QWidget* parent) : QWidget(parent) { setWindowIcon(QIcon::fromTheme("server-database", windowIcon())); setWindowTitle(i18n("Memory viewer")); QAction * newMemoryViewerAction = new QAction(this); newMemoryViewerAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); newMemoryViewerAction->setText(i18n("New memory viewer")); newMemoryViewerAction->setToolTip(i18nc("@info:tooltip", "Open a new memory viewer.")); newMemoryViewerAction->setIcon(QIcon::fromTheme("window-new")); connect(newMemoryViewerAction, SIGNAL(triggered(bool)), this, SLOT(slotAddMemoryView())); addAction(newMemoryViewerAction); QVBoxLayout *l = new QVBoxLayout(this); + l->setContentsMargins(0, 0, 0, 0); toolBox_ = new QToolBox(this); + toolBox_->setContentsMargins(0, 0, 0, 0); l->addWidget(toolBox_); + setLayout(l); + // Start with one empty memory view. slotAddMemoryView(); } void MemoryViewerWidget::slotAddMemoryView() { MemoryView* widget = new MemoryView(this); toolBox_->addItem(widget, widget->windowTitle()); toolBox_->setCurrentIndex(toolBox_->indexOf(widget)); memoryViews_.push_back(widget); connect(widget, SIGNAL(captionChanged(QString)), this, SLOT(slotChildCaptionChanged(QString))); connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(slotChildDestroyed(QObject*))); } void MemoryViewerWidget::slotChildCaptionChanged(const QString& caption) { const QWidget* s = static_cast(sender()); QWidget* ncs = const_cast(s); QString cap = caption; // Prevent intepreting '&' as accelerator specifier. cap.replace('&', "&&"); toolBox_->setItemText(toolBox_->indexOf(ncs), cap); } void MemoryViewerWidget::slotChildDestroyed(QObject* child) { QList::iterator i, e; for(i = memoryViews_.begin(), e = memoryViews_.end(); i != e; ++i) { if (*i == child) { memoryViews_.erase(i); break; } } }