diff --git a/debuggers/gdb/debugjob.cpp b/debuggers/gdb/debugjob.cpp index 78b5fa6f08..1922989946 100644 --- a/debuggers/gdb/debugjob.cpp +++ b/debuggers/gdb/debugjob.cpp @@ -1,173 +1,179 @@ /* * GDB Debugger Support * * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Andreas Pakulat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "debugjob.h" #include "debuggerplugin.h" #include #include #include #include #include #include #include #include #include "debugsession.h" #include "debug.h" #include +#include #include using namespace GDBDebugger; using namespace KDevelop; DebugJob::DebugJob( GDBDebugger::CppDebuggerPlugin* p, KDevelop::ILaunchConfiguration* launchcfg, IExecutePlugin* execute, QObject* parent) : KDevelop::OutputJob(parent) , m_launchcfg( launchcfg ) , m_execute( execute ) { setCapabilities(Killable); m_session = p->createSession(); connect(m_session, &DebugSession::applicationStandardOutputLines, this, &DebugJob::stderrReceived); connect(m_session, &DebugSession::applicationStandardErrorLines, this, &DebugJob::stdoutReceived); + connect(m_session, &DebugSession::gdbInternalCommandStdout, + this, [this](const QString &output){ + this->stdoutReceived(output.split(QRegularExpression("[\r\n]"), QString::SkipEmptyParts)); + }); + connect(m_session, &DebugSession::finished, this, &DebugJob::done ); if (launchcfg->project()) { setObjectName(i18nc("ProjectName: run configuration name", "%1: %2", launchcfg->project()->name(), launchcfg->name())); } else { setObjectName(launchcfg->name()); } } void DebugJob::start() { KConfigGroup grp = m_launchcfg->config(); KDevelop::EnvironmentGroupList l(KSharedConfig::openConfig()); Q_ASSERT(m_execute); QString err; QString executable = m_execute->executable( m_launchcfg, err ).toLocalFile(); if( !err.isEmpty() ) { setError( -1 ); setErrorText( err ); emitResult(); return; } if(!QFileInfo(executable).isExecutable()){ setError( -1 ); setErrorText(QString("'%1' is not an executable").arg(executable)); emitResult(); return; } QStringList arguments = m_execute->arguments( m_launchcfg, err ); if( !err.isEmpty() ) { setError( -1 ); setErrorText( err ); } if( error() != 0 ) { emitResult(); return; } setStandardToolView(KDevelop::IOutputView::DebugView); setBehaviours(KDevelop::IOutputView::Behaviours(KDevelop::IOutputView::AllowUserClose) | KDevelop::IOutputView::AutoScroll); auto model = new KDevelop::OutputModel; model->setFilteringStrategy(OutputModel::NativeAppErrorFilter); setModel(model); setTitle(m_launchcfg->name()); QString startWith = grp.readEntry(GDBDebugger::startWithEntry, QString("ApplicationOutput")); if (startWith == "GdbConsole") { setVerbosity(Silent); } else if (startWith == "FrameStack") { setVerbosity(Silent); } else { setVerbosity(Verbose); } startOutput(); if (!m_session->startProgram( m_launchcfg, m_execute )) { done(); } } bool DebugJob::doKill() { qCDebug(DEBUGGERGDB); m_session->stopDebugger(); return true; } void DebugJob::stderrReceived(const QStringList& l ) { if (KDevelop::OutputModel* m = model()) { m->appendLines( l ); } } void DebugJob::stdoutReceived(const QStringList& l ) { if (KDevelop::OutputModel* m = model()) { m->appendLines( l ); } } KDevelop::OutputModel* DebugJob::model() { return dynamic_cast( KDevelop::OutputJob::model() ); } void DebugJob::done() { emitResult(); } KillSessionJob::KillSessionJob(DebugSession *session, QObject* parent): KJob(parent), m_session(session) { connect(m_session, &DebugSession::finished, this, &KillSessionJob::sessionFinished); setCapabilities(Killable); } void KillSessionJob::start() { //NOOP } bool KillSessionJob::doKill() { m_session->stopDebugger(); return true; } void KillSessionJob::sessionFinished() { emitResult(); } diff --git a/debuggers/gdb/debugsession.cpp b/debuggers/gdb/debugsession.cpp index ee7b120e1e..2e66c9a518 100644 --- a/debuggers/gdb/debugsession.cpp +++ b/debuggers/gdb/debugsession.cpp @@ -1,1520 +1,1522 @@ /* * GDB Debugger Support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "debugsession.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "breakpointcontroller.h" #include "variablecontroller.h" #include "gdb.h" #include "gdbcommandqueue.h" #include "stty.h" #include "gdbframestackmodel.h" #include "debug.h" using namespace KDevelop; namespace GDBDebugger { DebugSession::DebugSession() : m_breakpointController(nullptr) , m_variableController(nullptr) , m_frameStackModel(nullptr) , m_sessionState(NotStartedState) , m_config(KSharedConfig::openConfig(), "GDB Debugger") , m_testing(false) , commandQueue_(new CommandQueue) , m_tty(0) , state_(s_dbgNotStarted | s_appNotStarted) , state_reload_needed(false) , stateReloadInProgress_(false) , m_hasCrashed(false) { configure(); m_breakpointController = new BreakpointController(this); m_variableController = new VariableController(this); m_frameStackModel = new GdbFrameStackModel(this); m_procLineMaker = new KDevelop::ProcessLineMaker(this); connect(m_procLineMaker, &ProcessLineMaker::receivedStdoutLines, this, &DebugSession::applicationStandardOutputLines); connect(m_procLineMaker, &ProcessLineMaker::receivedStderrLines, this, &DebugSession::applicationStandardErrorLines); setupController(); } // Deleting the session involves shutting down gdb nicely. // When were attached to a process, we must first detach so that the process // can continue running as it was before being attached. gdb is quite slow to // detach from a process, so we must process events within here to get a "clean" // shutdown. DebugSession::~DebugSession() { qCDebug(DEBUGGERGDB); if (!stateIsOn(s_dbgNotStarted)) { stopDebugger(); // This currently isn't working, so comment out until it can be resolved - at the moment it just causes a delay on stopping kdevelop //m_process->waitForFinished(); } delete commandQueue_; } void DebugSession::setTesting(bool testing) { m_testing = testing; } KDevelop::IDebugSession::DebuggerState DebugSession::state() const { return m_sessionState; } BreakpointController* DebugSession::breakpointController() const { return m_breakpointController; } IVariableController* DebugSession::variableController() const { return m_variableController; } IFrameStackModel* DebugSession::frameStackModel() const { return m_frameStackModel; } #define ENUM_NAME(o,e,v) (o::staticMetaObject.enumerator(o::staticMetaObject.indexOfEnumerator(#e)).valueToKey((v))) void DebugSession::setSessionState(DebuggerState state) { qCDebug(DEBUGGERGDB) << "STATE CHANGED" << this << state << ENUM_NAME(IDebugSession, DebuggerState, state); if (state != m_sessionState) { m_sessionState = state; emit stateChanged(state); } } void DebugSession::setupController() { // controller -> procLineMaker connect(this, &DebugSession::ttyStdout, m_procLineMaker, &ProcessLineMaker::slotReceivedStdout); connect(this, &DebugSession::ttyStderr, m_procLineMaker, &ProcessLineMaker::slotReceivedStderr); // connect(statusBarIndicator, SIGNAL(doubleClicked()), // controller, SLOT(explainDebuggerStatus())); // TODO: reimplement / re-enable //connect(this, SIGNAL(addWatchVariable(QString)), controller->variables(), SLOT(slotAddWatchVariable(QString))); //connect(this, SIGNAL(evaluateExpression(QString)), controller->variables(), SLOT(slotEvaluateExpression(QString))); } void DebugSession::_gdbStateChanged(DBGStateFlags oldState, DBGStateFlags newState) { QString message; DebuggerState oldSessionState = state(); DebuggerState newSessionState = oldSessionState; DBGStateFlags changedState = oldState ^ newState; if (newState & s_dbgNotStarted) { if (changedState & s_dbgNotStarted) { message = i18n("Debugger stopped"); emit finished(); } if (oldSessionState != NotStartedState) { newSessionState = EndedState; } } else { if (newState & s_appNotStarted) { if (oldSessionState == NotStartedState || oldSessionState == StartingState) { newSessionState = StartingState; } else { newSessionState = StoppedState; } } else if (newState & s_programExited) { if (changedState & s_programExited) { message = i18n("Process exited"); } newSessionState = StoppedState; } else if (newState & s_appRunning) { if (changedState & s_appRunning) { message = i18n("Application is running"); } newSessionState = ActiveState; } else { if (changedState & s_appRunning) { message = i18n("Application is paused"); } newSessionState = PausedState; } } // And now? :-) qCDebug(DEBUGGERGDB) << "state: " << newState << message; if (!message.isEmpty()) emit showMessage(message, 3000); emit gdbStateChanged(oldState, newState); // must be last, since it can lead to deletion of the DebugSession if (newSessionState != oldSessionState) { setSessionState(newSessionState); } } void DebugSession::examineCoreFile(const QUrl& debugee, const QUrl& coreFile) { if (stateIsOn(s_dbgNotStarted)) startDebugger(0); // TODO support non-local URLs queueCmd(new GDBCommand(GDBMI::FileExecAndSymbols, debugee.toLocalFile())); queueCmd(new GDBCommand(GDBMI::NonMI, "core " + coreFile.toLocalFile(), this, &DebugSession::handleCoreFile, CmdHandlesError)); raiseEvent(connected_to_program); raiseEvent(program_state_changed); } void DebugSession::handleCoreFile(const GDBMI::ResultRecord& r) { if (r.reason != "error") { setStateOn(s_programExited|s_core); } else { KMessageBox::information( qApp->activeWindow(), i18n("Failed to load core file" "

Debugger reported the following error:" "

%1", r["msg"].literal()), i18n("Debugger error")); // How should we proceed at this point? Stop the debugger? } } void DebugSession::attachToProcess(int pid) { qCDebug(DEBUGGERGDB) << pid; if (stateIsOn(s_dbgNotStarted)) startDebugger(0); setStateOn(s_attached); //set current state to running, after attaching we will get *stopped response setStateOn(s_appRunning); // Currently, we always start debugger with a name of binary, // we might be connecting to a different binary completely, // so cancel all symbol tables gdb has. // We can't omit application name from gdb invocation // because for libtool binaries, we have no way to guess // real binary name. queueCmd(new GDBCommand(GDBMI::FileExecAndSymbols)); queueCmd(new GDBCommand(GDBMI::TargetAttach, QString::number(pid), this, &DebugSession::handleTargetAttach, CmdHandlesError)); queueCmd(new SentinelCommand(breakpointController(), &BreakpointController::initSendBreakpoints)); raiseEvent(connected_to_program); emit raiseFramestackViews(); } void DebugSession::run() { if (stateIsOn(s_appNotStarted|s_dbgNotStarted|s_shuttingDown)) return; queueCmd(new GDBCommand(GDBMI::ExecContinue, QString(), CmdMaybeStartsRunning)); } void DebugSession::stepOut() { if (stateIsOn(s_appNotStarted|s_shuttingDown)) return; queueCmd(new GDBCommand(GDBMI::ExecFinish, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); } void DebugSession::restartDebugger() { // We implement restart as kill + slotRun, as opposed as plain "run" // command because kill + slotRun allows any special logic in slotRun // to apply for restart. // // That includes: // - checking for out-of-date project // - special setup for remote debugging. // // Had we used plain 'run' command, restart for remote debugging simply // would not work. slotKill(); run(); } void DebugSession::stopDebugger() { commandQueue_->clear(); qCDebug(DEBUGGERGDB) << "DebugSession::slotStopDebugger() called"; if (stateIsOn(s_shuttingDown) || !m_gdb) return; setStateOn(s_shuttingDown); qCDebug(DEBUGGERGDB) << "DebugSession::slotStopDebugger() executing"; // Get gdb's attention if it's busy. We need gdb to be at the // command line so we can stop it. if (!m_gdb.data()->isReady()) { qCDebug(DEBUGGERGDB) << "gdb busy on shutdown - interruping"; m_gdb.data()->interrupt(); } // If the app is attached then we release it here. This doesn't stop // the app running. if (stateIsOn(s_attached)) { queueCmd(new GDBCommand(GDBMI::TargetDetach)); emit gdbUserCommandStdout("(gdb) detach\n"); } // Now try to stop gdb running. queueCmd(new GDBCommand(GDBMI::GdbExit)); emit gdbUserCommandStdout("(gdb) quit"); // We cannot wait forever, kill gdb after 5 seconds if it's not yet quit QTimer::singleShot(5000, this, SLOT(slotKillGdb())); emit reset(); } // Pausing an app removes any pending run commands so that the app doesn't // start again. If we want to be silent then we remove any pending info // commands as well. void DebugSession::interruptDebugger() { Q_ASSERT(m_gdb); // Explicitly send the interrupt in case something went wrong with the usual // ensureGdbListening logic. m_gdb->interrupt(); queueCmd(new GDBCommand(GDBMI::ExecInterrupt, QString(), CmdInterrupt)); } void DebugSession::runToCursor() { if (KDevelop::IDocument* doc = KDevelop::ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if (cursor.isValid()) runUntil(doc->url(), cursor.line() + 1); } } void DebugSession::jumpToCursor() { if (KDevelop::IDocument* doc = KDevelop::ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if (cursor.isValid()) jumpTo(doc->url(), cursor.line() + 1); } } void DebugSession::stepOver() { if (stateIsOn(s_appNotStarted|s_shuttingDown)) return; queueCmd(new GDBCommand(GDBMI::ExecNext, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); } void DebugSession::stepOverInstruction() { if (stateIsOn(s_appNotStarted|s_shuttingDown)) return; queueCmd(new GDBCommand(GDBMI::ExecNextInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); } void DebugSession::stepInto() { if (stateIsOn(s_appNotStarted|s_shuttingDown)) return; queueCmd(new GDBCommand(GDBMI::ExecStep, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); } void DebugSession::stepIntoInstruction() { if (stateIsOn(s_appNotStarted|s_shuttingDown)) return; queueCmd(new GDBCommand(GDBMI::ExecStepInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun)); } void DebugSession::slotDebuggerAbnormalExit() { KMessageBox::information( KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("GDB exited abnormally" "

This is likely a bug in GDB. " "Examine the gdb output window and then stop the debugger"), i18n("GDB exited abnormally")); // Note: we don't stop the debugger here, becuse that will hide gdb // window and prevent the user from finding the exact reason of the // problem. } bool DebugSession::restartAvaliable() const { if (stateIsOn(s_attached) || stateIsOn(s_core)) { return false; } else { return true; } } void DebugSession::configure() { // KConfigGroup config(KSharedConfig::openConfig(), "GDB Debugger"); // // // A a configure.gdb script will prevent these from uncontrolled growth... // config_configGdbScript_ = config.readEntry("Remote GDB Configure Script", ""); // config_runShellScript_ = config.readEntry("Remote GDB Shell Script", ""); // config_runGdbScript_ = config.readEntry("Remote GDB Run Script", ""); // // // PORTING TODO: where is this in the ui? // config_forceBPSet_ = config.readEntry("Allow Forced Breakpoint Set", true); // // config_dbgTerminal_ = config.readEntry("Separate Terminal For Application IO", false); // // bool old_displayStatic = config_displayStaticMembers_; // config_displayStaticMembers_ = config.readEntry("Display Static Members",false); // // bool old_asmDemangle = config_asmDemangle_; // config_asmDemangle_ = config.readEntry("Display Demangle Names",true); // // bool old_breakOnLoadingLibrary_ = config_breakOnLoadingLibrary_; // config_breakOnLoadingLibrary_ = config.readEntry("Try Setting Breakpoints On Loading Libraries",true); // // // FIXME: should move this into debugger part or variable widget. // int old_outputRadix = config_outputRadix_; // #if 0 // config_outputRadix_ = DomUtil::readIntEntry("Output Radix", 10); // varTree_->setRadix(config_outputRadix_); // #endif // // // if (( old_displayStatic != config_displayStaticMembers_ || // old_asmDemangle != config_asmDemangle_ || // old_breakOnLoadingLibrary_ != config_breakOnLoadingLibrary_ || // old_outputRadix != config_outputRadix_) && // m_gdb) // { // bool restart = false; // if (stateIsOn(s_dbgBusy)) // { // slotPauseApp(); // restart = true; // } // // if (old_displayStatic != config_displayStaticMembers_) // { // if (config_displayStaticMembers_) // queueCmd(new GDBCommand(GDBMI::GdbSet, "print static-members on")); // else // queueCmd(new GDBCommand(GDBMI::GdbSet, "print static-members off")); // } // if (old_asmDemangle != config_asmDemangle_) // { // if (config_asmDemangle_) // queueCmd(new GDBCommand(GDBMI::GdbSet, "print asm-demangle on")); // else // queueCmd(new GDBCommand(GDBMI::GdbSet, "print asm-demangle off")); // } // // // Disabled for MI port. // if (old_outputRadix != config_outputRadix_) // { // queueCmd(new GDBCommand(GDBMI::GdbSet, QString().sprintf("output-radix %d", // config_outputRadix_))); // // // FIXME: should do this in variable widget anyway. // // After changing output radix, need to refresh variables view. // raiseEvent(program_state_changed); // // } // // if (config_configGdbScript_.isValid()) // queueCmd(new GDBCommand(GDBMI::NonMI, "source " + config_configGdbScript_.toLocalFile())); // // // if (restart) // queueCmd(new GDBCommand(GDBMI::ExecContinue)); // } } // ************************************************************************** void DebugSession::addCommand(GDBCommand* cmd) { queueCmd(cmd); } void DebugSession::addCommand(GDBMI::CommandType type, const QString& str) { queueCmd(new GDBCommand(type, str)); } // Fairly obvious that we'll add whatever command you give me to a queue // Not quite so obvious though is that if we are going to run again. then any // information requests become redundent and must be removed. // We also try and run whatever command happens to be at the head of // the queue. void DebugSession::queueCmd(GDBCommand *cmd) { if (stateIsOn(s_dbgNotStarted)) { KMessageBox::information( qApp->activeWindow(), i18n("Gdb command sent when debugger is not running
" "The command was:
%1", cmd->initialString()), i18n("Internal error")); return; } if (stateReloadInProgress_) cmd->setStateReloading(true); commandQueue_->enqueue(cmd); qCDebug(DEBUGGERGDB) << "QUEUE: " << cmd->initialString() << (stateReloadInProgress_ ? "(state reloading)" : ""); bool varCommandWithContext= (cmd->type() >= GDBMI::VarAssign && cmd->type() <= GDBMI::VarUpdate && cmd->type() != GDBMI::VarDelete); bool stackCommandWithContext = (cmd->type() >= GDBMI::StackInfoDepth && cmd->type() <= GDBMI::StackListLocals); if (varCommandWithContext || stackCommandWithContext) { if (cmd->thread() == -1) qCDebug(DEBUGGERGDB) << "\t--thread will be added on execution"; if (cmd->frame() == -1) qCDebug(DEBUGGERGDB) << "\t--frame will be added on execution"; } setStateOn(s_dbgBusy); raiseEvent(debugger_busy); executeCmd(); } void DebugSession::executeCmd() { Q_ASSERT(m_gdb); if (stateIsOn(s_dbgNotListening) && commandQueue_->haveImmediateCommand()) { // We may have to call this even while a command is currently executing, because // Gdb can get into a state where a command such as ExecRun does not send a response // while the inferior is running. ensureGdbListening(); } if (!m_gdb.data()->isReady()) return; GDBCommand* currentCmd = commandQueue_->nextCommand(); if (!currentCmd) return; if (currentCmd->flags() & (CmdMaybeStartsRunning | CmdInterrupt)) { setStateOff(s_automaticContinue); } if (currentCmd->flags() & CmdMaybeStartsRunning) { // GDB can be in a state where it is listening for commands while the program is running. // However, when we send a command such as ExecContinue in this state, GDB may return to // the non-listening state without acknowledging that the ExecContinue command has even // finished, let alone sending a new notification about the program's running state. // So let's be extra cautious about ensuring that we will wake GDB up again if required. setStateOn(s_dbgNotListening); } bool varCommandWithContext= (currentCmd->type() >= GDBMI::VarAssign && currentCmd->type() <= GDBMI::VarUpdate && currentCmd->type() != GDBMI::VarDelete); bool stackCommandWithContext = (currentCmd->type() >= GDBMI::StackInfoDepth && currentCmd->type() <= GDBMI::StackListLocals); if (varCommandWithContext || stackCommandWithContext) { // Most var commands should be executed in the context // of the selected thread and frame. if (currentCmd->thread() == -1) currentCmd->setThread(frameStackModel()->currentThread()); if (currentCmd->frame() == -1) currentCmd->setFrame(frameStackModel()->currentFrame()); } QString commandText = currentCmd->cmdToSend(); bool bad_command = false; QString message; int length = commandText.length(); // No i18n for message since it's mainly for debugging. if (length == 0) { // The command might decide it's no longer necessary to send // it. if (SentinelCommand* sc = dynamic_cast(currentCmd)) { qCDebug(DEBUGGERGDB) << "SEND: sentinel command, not sending"; sc->invokeHandler(); } else { qCDebug(DEBUGGERGDB) << "SEND: command " << currentCmd->initialString() << "changed its mind, not sending"; } delete currentCmd; executeCmd(); return; } else { if (commandText[length-1] != '\n') { bad_command = true; message = "Debugger command does not end with newline"; } } if (bad_command) { KMessageBox::information(qApp->activeWindow(), i18n("Invalid debugger command
%1", message), i18n("Invalid debugger command")); executeCmd(); return; } m_gdb.data()->execute(currentCmd); } // ************************************************************************** void DebugSession::destroyCmds() { commandQueue_->clear(); } void DebugSession::slotProgramStopped(const GDBMI::AsyncRecord& r) { /* By default, reload all state on program stop. */ state_reload_needed = true; setStateOff(s_appRunning); setStateOff(s_dbgNotListening); QString reason; if (r.hasField("reason")) reason = r["reason"].literal(); if (reason == "exited-normally" || reason == "exited") { if (r.hasField("exit-code")) { programNoApp(i18n("Exited with return code: %1", r["exit-code"].literal())); } else { programNoApp(i18n("Exited normally")); } state_reload_needed = false; return; } if (reason == "exited-signalled") { programNoApp(i18n("Exited on signal %1", r["signal-name"].literal())); state_reload_needed = false; return; } if (reason == "watchpoint-scope") { QString number = r["wpnum"].literal(); // FIXME: shuld remove this watchpoint // But first, we should consider if removing all // watchpoinst on program exit is the right thing to // do. queueCmd(new GDBCommand(GDBMI::ExecContinue, QString(), CmdMaybeStartsRunning)); state_reload_needed = false; return; } bool wasInterrupt = false; if (reason == "signal-received") { QString name = r["signal-name"].literal(); QString user_name = r["signal-meaning"].literal(); // SIGINT is a "break into running program". // We do this when the user set/mod/clears a breakpoint but the // application is running. // And the user does this to stop the program also. if (name == "SIGINT" && stateIsOn(s_interruptSent)) { wasInterrupt = true; } else { // Whenever we have a signal raised then tell the user, but don't // end the program as we want to allow the user to look at why the // program has a signal that's caused the prog to stop. // Continuing from SIG FPE/SEGV will cause a "Cannot ..." and // that'll end the program. programFinished(i18n("Program received signal %1 (%2)", name, user_name)); m_hasCrashed = true; } } if (!reason.contains("exited")) { // FIXME: we should immediately update the current thread and // frame in the framestackmodel, so that any user actions // are in that thread. However, the way current framestack model // is implemented, we can't change thread id until we refresh // the entire list of threads -- otherwise we might set a thread // id that is not already in the list, and it will be upset. //Indicates if program state should be reloaded immediately. bool updateState = false; if (r.hasField("frame")) { const GDBMI::Value& frame = r["frame"]; QString file, line, addr; if (frame.hasField("fullname")) file = frame["fullname"].literal();; if (frame.hasField("line")) line = frame["line"].literal(); if (frame.hasField("addr")) addr = frame["addr"].literal(); // gdb counts lines from 1 and we don't setCurrentPosition(QUrl::fromLocalFile(file), line.toInt() - 1, addr); updateState = true; } if (updateState) { reloadProgramState(); } } setStateOff(s_interruptSent); if (!wasInterrupt) setStateOff(s_automaticContinue); } bool DebugSession::hasCrashed() const { return m_hasCrashed; } void DebugSession::processNotification(const GDBMI::AsyncRecord & async) { if (async.reason == "thread-group-started") { setStateOff(s_appNotStarted | s_programExited); } else if (async.reason == "thread-group-exited") { setStateOn(s_programExited); } else if (async.reason == "library-loaded") { // do nothing } else if (async.reason == "breakpoint-created") { breakpointController()->notifyBreakpointCreated(async); } else if (async.reason == "breakpoint-modified") { breakpointController()->notifyBreakpointModified(async); } else if (async.reason == "breakpoint-deleted") { breakpointController()->notifyBreakpointDeleted(async); } else { qCDebug(DEBUGGERGDB) << "Unhandled notification: " << async.reason; } } void DebugSession::reloadProgramState() { raiseEvent(program_state_changed); state_reload_needed = false; } // ************************************************************************** // There is no app anymore. This can be caused by program exiting // an invalid program specified or ... // gdb is still running though, but only the run command (may) make sense // all other commands are disabled. void DebugSession::programNoApp(const QString& msg) { qCDebug(DEBUGGERGDB) << msg; setState(s_appNotStarted|s_programExited|(state_&s_shuttingDown)); destroyCmds(); // The application has existed, but it's possible that // some of application output is still in the pipe. We use // different pipes to communicate with gdb and to get application // output, so "exited" message from gdb might have arrived before // last application output. Get this last bit. // Note: this method can be called when we open an invalid // core file. In that case, tty_ won't be set. if (m_tty){ m_tty->readRemaining(); // Tty is no longer usable, delete it. Without this, QSocketNotifier // will continiously bomd STTY with signals, so we need to either disable // QSocketNotifier, or delete STTY. The latter is simpler, since we can't // reuse it for future debug sessions anyway. m_tty.reset(0); } stopDebugger(); raiseEvent(program_exited); raiseEvent(debugger_exited); emit showMessage(msg, 0); programFinished(msg); } void DebugSession::programFinished(const QString& msg) { QString m = QString("*** %0 ***").arg(msg.trimmed()); emit applicationStandardErrorLines(QStringList(m)); /* Also show message in gdb window, so that users who prefer to look at gdb window know what's up. */ emit gdbUserCommandStdout(m); } bool DebugSession::startDebugger(KDevelop::ILaunchConfiguration* cfg) { qCDebug(DEBUGGERGDB) << "Starting debugger controller"; if(m_gdb) { qWarning() << "m_gdb object still existed"; delete m_gdb.data(); m_gdb.clear(); } GDB* gdb = new GDB(this); m_gdb = gdb; // FIXME: here, we should wait until GDB is up and waiting for input. // Then, clear s_dbgNotStarted // It's better to do this right away so that the state bit is always // correct. connect(gdb, &GDB::applicationOutput, this, [this](const QString& output) { emit applicationStandardOutputLines(output.split(QRegularExpression("[\r\n]"),QString::SkipEmptyParts)); }); connect(gdb, &GDB::userCommandOutput, this, &DebugSession::gdbUserCommandStdout); connect(gdb, &GDB::internalCommandOutput, this, &DebugSession::gdbInternalCommandStdout); + connect(gdb, &GDB::debuggerInternalOutput, this, + &DebugSession::gdbInternalOutput); connect(gdb, &GDB::ready, this, &DebugSession::gdbReady); connect(gdb, &GDB::gdbExited, this, &DebugSession::gdbExited); connect(gdb, &GDB::programStopped, this, &DebugSession::slotProgramStopped); connect(gdb, &GDB::programStopped, this, &DebugSession::programStopped); connect(gdb, &GDB::programRunning, this, &DebugSession::programRunning); connect(gdb, &GDB::notification, this, &DebugSession::processNotification); // Start gdb. Do this after connecting all signals so that initial // GDB output, and important events like "GDB died" are reported. { QStringList extraArguments; if (m_testing) extraArguments << "--nx"; // do not load any .gdbinit files if (cfg) { KConfigGroup config = cfg->config(); m_gdb.data()->start(config, extraArguments); } else { // FIXME: this is hack, I am not sure there's any way presently // to edit this via GUI. KConfigGroup config(KSharedConfig::openConfig(), "GDB Debugger"); m_gdb.data()->start(config, extraArguments); } } setStateOff(s_dbgNotStarted); // Initialise gdb. At this stage gdb is sitting wondering what to do, // and to whom. Organise a few things, then set up the tty for the application, // and the application itself //queueCmd(new GDBCommand(GDBMI::EnableTimings, "yes")); queueCmd(new CliCommand(GDBMI::GdbShow, "version", this, &DebugSession::handleVersion)); // This makes gdb pump a variable out on one line. queueCmd(new GDBCommand(GDBMI::GdbSet, "width 0")); queueCmd(new GDBCommand(GDBMI::GdbSet, "height 0")); queueCmd(new GDBCommand(GDBMI::SignalHandle, "SIG32 pass nostop noprint")); queueCmd(new GDBCommand(GDBMI::SignalHandle, "SIG41 pass nostop noprint")); queueCmd(new GDBCommand(GDBMI::SignalHandle, "SIG42 pass nostop noprint")); queueCmd(new GDBCommand(GDBMI::SignalHandle, "SIG43 pass nostop noprint")); queueCmd(new GDBCommand(GDBMI::EnablePrettyPrinting)); queueCmd(new GDBCommand(GDBMI::GdbSet, "charset UTF-8")); queueCmd(new GDBCommand(GDBMI::GdbSet, "print sevenbit-strings off")); QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevgdb/printers/gdbinit"); if (!fileName.isEmpty()) { QFileInfo fileInfo(fileName); QString quotedPrintersPath = fileInfo.dir().path().replace('\\', "\\\\").replace('"', "\\\""); queueCmd(new GDBCommand(GDBMI::NonMI, QString("python sys.path.insert(0, \"%0\")").arg(quotedPrintersPath))); queueCmd(new GDBCommand(GDBMI::NonMI, "source " + fileName)); } return true; } bool DebugSession::startProgram(KDevelop::ILaunchConfiguration* cfg, IExecutePlugin* iface) { if (stateIsOn( s_appNotStarted ) ) { emit showMessage(i18n("Running program"), 1000); } if (stateIsOn(s_dbgNotStarted)) { if (!startDebugger(cfg)) return false; } if (stateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERGDB) << "Tried to run when debugger shutting down"; return false; } KConfigGroup grp = cfg->config(); KDevelop::EnvironmentGroupList l(KSharedConfig::openConfig()); QString envgrp = iface->environmentGroup( cfg ); if( envgrp.isEmpty() ) { qWarning() << i18n("No environment group specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment group.", cfg->name() ); envgrp = l.defaultGroup(); } if (grp.readEntry("Break on Start", false)) { BreakpointModel* m = KDevelop::ICore::self()->debugController()->breakpointModel(); bool found = false; foreach (KDevelop::Breakpoint *b, m->breakpoints()) { if (b->location() == "main") { found = true; break; } } if (!found) { KDevelop::ICore::self()->debugController()->breakpointModel()->addCodeBreakpoint("main"); } } // Configuration values bool config_displayStaticMembers_ = grp.readEntry( GDBDebugger::staticMembersEntry, false ); bool config_asmDemangle_ = grp.readEntry( GDBDebugger::demangleNamesEntry, true ); QUrl config_configGdbScript_ = grp.readEntry( GDBDebugger::remoteGdbConfigEntry, QUrl() ); QUrl config_runShellScript_ = grp.readEntry( GDBDebugger::remoteGdbShellEntry, QUrl() ); QUrl config_runGdbScript_ = grp.readEntry( GDBDebugger::remoteGdbRunEntry, QUrl() ); Q_ASSERT(iface); bool config_useExternalTerminal = iface->useTerminal( cfg ); QString config_externalTerminal = iface->terminal( cfg ); if (!config_externalTerminal.isEmpty()) { // the external terminal cmd contains additional arguments, just get the terminal name config_externalTerminal = KShell::splitArgs(config_externalTerminal).first(); } m_tty.reset(new STTY(config_useExternalTerminal, config_externalTerminal)); if (!config_useExternalTerminal) { connect( m_tty.data(), &STTY::OutOutput, this, &DebugSession::ttyStdout ); connect( m_tty.data(), &STTY::ErrOutput, this, &DebugSession::ttyStderr ); } QString tty(m_tty->getSlave()); if (tty.isEmpty()) { KMessageBox::information(qApp->activeWindow(), m_tty->lastError(), i18n("Warning")); m_tty.reset(0); return false; } queueCmd(new GDBCommand(GDBMI::InferiorTtySet, tty)); // Only dummy err here, actual erros have been checked already in the job and we don't get here if there were any QString err; QString executable = iface->executable(cfg, err).toLocalFile(); QStringList arguments = iface->arguments(cfg, err); // Change the "Working directory" to the correct one QString dir = iface->workingDirectory(cfg).toLocalFile(); if (dir.isEmpty()) { dir = QFileInfo(executable).absolutePath(); } queueCmd(new GDBCommand(GDBMI::EnvironmentCd, '"' + dir + '"')); // Set the run arguments if (!arguments.isEmpty()) queueCmd( new GDBCommand(GDBMI::ExecArguments, KShell::joinArgs( arguments ))); foreach (const QString& envvar, l.createEnvironment(envgrp, QStringList())) queueCmd(new GDBCommand(GDBMI::GdbSet, "environment " + envvar)); // Needed so that breakpoint widget has a chance to insert breakpoints. // FIXME: a bit hacky, as we're really not ready for new commands. setStateOn(s_dbgBusy); raiseEvent(debugger_ready); if (config_displayStaticMembers_) queueCmd(new GDBCommand(GDBMI::GdbSet, "print static-members on")); else queueCmd(new GDBCommand(GDBMI::GdbSet, "print static-members off")); if (config_asmDemangle_) queueCmd(new GDBCommand(GDBMI::GdbSet, "print asm-demangle on")); else queueCmd(new GDBCommand(GDBMI::GdbSet, "print asm-demangle off")); if (config_configGdbScript_.isValid()) queueCmd(new GDBCommand(GDBMI::NonMI, "source " + KShell::quoteArg(config_configGdbScript_.toLocalFile()))); if (!config_runShellScript_.isEmpty()) { // Special for remote debug... QByteArray tty(m_tty->getSlave().toLatin1()); QByteArray options = QByteArray(">") + tty + QByteArray(" 2>&1 <") + tty; QProcess *proc = new QProcess; QStringList arguments; arguments << "-c" << KShell::quoteArg(config_runShellScript_.toLocalFile()) + ' ' + KShell::quoteArg(executable) + QString::fromLatin1( options ); qCDebug(DEBUGGERGDB) << "starting sh" << arguments; proc->start("sh", arguments); //PORTING TODO QProcess::DontCare); } if (!config_runGdbScript_.isEmpty()) {// gdb script at run is requested // Race notice: wait for the remote gdbserver/executable // - but that might be an issue for this script to handle... // Future: the shell script should be able to pass info (like pid) // to the gdb script... queueCmd(new SentinelCommand([this, config_runGdbScript_]() { breakpointController()->initSendBreakpoints(); breakpointController()->setDeleteDuplicateBreakpoints(true); qCDebug(DEBUGGERGDB) << "Running gdb script " << KShell::quoteArg(config_runGdbScript_.toLocalFile()); queueCmd(new GDBCommand(GDBMI::NonMI, "source " + KShell::quoteArg(config_runGdbScript_.toLocalFile()), [this](const GDBMI::ResultRecord&) {breakpointController()->setDeleteDuplicateBreakpoints(false);}, CmdMaybeStartsRunning)); raiseEvent(connected_to_program); // Note: script could contain "run" or "continue" }, CmdMaybeStartsRunning)); } else { queueCmd(new GDBCommand(GDBMI::FileExecAndSymbols, KShell::quoteArg(executable), this, &DebugSession::handleFileExecAndSymbols, CmdHandlesError)); raiseEvent(connected_to_program); queueCmd(new SentinelCommand([this]() { breakpointController()->initSendBreakpoints(); queueCmd(new GDBCommand(GDBMI::ExecRun, QString(), CmdMaybeStartsRunning)); }, CmdMaybeStartsRunning)); } { QString startWith = grp.readEntry(GDBDebugger::startWithEntry, QString("ApplicationOutput")); if (startWith == "GdbConsole") { emit raiseGdbConsoleViews(); } else if (startWith == "FrameStack") { emit raiseFramestackViews(); } else { //ApplicationOutput is raised in DebugJob (by setting job to Verbose/Silent) } } return true; } // ************************************************************************** // SLOTS // ***** // For most of these slots data can only be sent to gdb when it // isn't busy and it is running. // ************************************************************************** void DebugSession::slotKillGdb() { if (!stateIsOn(s_programExited) && stateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERGDB) << "gdb not shutdown - killing"; m_gdb.data()->kill(); setState(s_dbgNotStarted | s_appNotStarted); raiseEvent(debugger_exited); } } // ************************************************************************** void DebugSession::slotKill() { if (stateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (stateIsOn(s_dbgBusy)) { interruptDebugger(); } // The -exec-abort is not implemented in gdb // queueCmd(new GDBCommand(GDBMI::ExecAbort)); queueCmd(new GDBCommand(GDBMI::NonMI, "kill")); } // ************************************************************************** void DebugSession::runUntil(const QUrl& url, int line) { if (stateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!url.isValid()) queueCmd(new GDBCommand(GDBMI::ExecUntil, QString::number(line), CmdMaybeStartsRunning | CmdTemporaryRun)); else queueCmd(new GDBCommand(GDBMI::ExecUntil, QString("%1:%2").arg(url.toLocalFile()).arg(line), CmdMaybeStartsRunning | CmdTemporaryRun)); } void DebugSession::runUntil(const QString& address) { if (stateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!address.isEmpty()) { queueCmd(new GDBCommand(GDBMI::ExecUntil, QString("*%1").arg(address), CmdMaybeStartsRunning | CmdTemporaryRun)); } } // ************************************************************************** void DebugSession::jumpToMemoryAddress(const QString& address) { if (stateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!address.isEmpty()) { queueCmd(new GDBCommand(GDBMI::NonMI, QString("tbreak *%1").arg(address))); queueCmd(new GDBCommand(GDBMI::NonMI, QString("jump *%1").arg(address))); } } void DebugSession::jumpTo(const QUrl& url, int line) { if (stateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (url.isValid()) { queueCmd(new GDBCommand(GDBMI::NonMI, QString("tbreak %1:%2").arg(url.toLocalFile()).arg(line))); queueCmd(new GDBCommand(GDBMI::NonMI, QString("jump %1:%2").arg(url.toLocalFile()).arg(line))); } } // ************************************************************************** // FIXME: connect to GDB's slot. void DebugSession::defaultErrorHandler(const GDBMI::ResultRecord& result) { QString msg = result["msg"].literal(); if (msg.contains("No such process")) { setState(s_appNotStarted|s_programExited); raiseEvent(program_exited); return; } KMessageBox::information( qApp->activeWindow(), i18n("Debugger error" "

Debugger reported the following error:" "

%1", result["msg"].literal()), i18n("Debugger error")); // Error most likely means that some change made in GUI // was not communicated to the gdb, so GUI is now not // in sync with gdb. Resync it. // // Another approach is to make each widget reload it content // on errors from commands that it sent, but that's too complex. // Errors are supposed to happen rarely, so full reload on error // is not a big deal. Well, maybe except for memory view, but // it's no auto-reloaded anyway. // // Also, don't reload state on errors appeared during state // reloading! if (!m_gdb.data()->currentCommand()->stateReloading()) raiseEvent(program_state_changed); } void DebugSession::gdbReady() { stateReloadInProgress_ = false; executeCmd(); if (m_gdb->isReady()) { /* There is nothing in the command queue and no command is currently executing. */ if (stateIsOn(s_automaticContinue)) { if (!stateIsOn(s_appRunning)) { qCDebug(DEBUGGERGDB) << "Posting automatic continue"; queueCmd(new GDBCommand(GDBMI::ExecContinue, QString(), CmdMaybeStartsRunning)); } setStateOff(s_automaticContinue); return; } if (state_reload_needed && !stateIsOn(s_appRunning)) { qCDebug(DEBUGGERGDB) << "Finishing program stop"; // Set to false right now, so that if 'actOnProgramPauseMI_part2' // sends some commands, we won't call it again when handling replies // from that commands. state_reload_needed = false; reloadProgramState(); } qCDebug(DEBUGGERGDB) << "No more commands"; setStateOff(s_dbgBusy); raiseEvent(debugger_ready); } } void DebugSession::gdbExited() { qCDebug(DEBUGGERGDB); /* Technically speaking, GDB is likely not to kill the application, and we should have some backup mechanism to make sure the application is killed by KDevelop. But even if application stays around, we no longer can control it in any way, so mark it as exited. */ setStateOn(s_appNotStarted); setStateOn(s_dbgNotStarted); setStateOff(s_shuttingDown); } void DebugSession::ensureGdbListening() { Q_ASSERT(m_gdb); m_gdb->interrupt(); setStateOn(s_interruptSent); if (stateIsOn(s_appRunning)) setStateOn(s_automaticContinue); setStateOff(s_dbgNotListening); } // FIXME: I don't fully remember what is the business with // stateReloadInProgress_ and whether we can lift it to the // generic level. void DebugSession::raiseEvent(event_t e) { if (e == program_exited || e == debugger_exited) { stateReloadInProgress_ = false; } if (e == program_state_changed) { stateReloadInProgress_ = true; qCDebug(DEBUGGERGDB) << "State reload in progress\n"; } IDebugSession::raiseEvent(e); if (e == program_state_changed) { stateReloadInProgress_ = false; } } // ************************************************************************** void DebugSession::slotUserGDBCmd(const QString& cmd) { queueCmd(new UserCommand(GDBMI::NonMI, cmd)); // User command can theoreticall modify absolutely everything, // so need to force a reload. // We can do it right now, and don't wait for user command to finish // since commands used to reload all view will be executed after // user command anyway. if (!stateIsOn(s_appNotStarted) && !stateIsOn(s_programExited)) raiseEvent(program_state_changed); } void DebugSession::explainDebuggerStatus() { GDBCommand* currentCmd_ = m_gdb.data()->currentCommand(); QString information = i18np("1 command in queue\n", "%1 commands in queue\n", commandQueue_->count()) + i18ncp("Only the 0 and 1 cases need to be translated", "1 command being processed by gdb\n", "%1 commands being processed by gdb\n", (currentCmd_ ? 1 : 0)) + i18n("Debugger state: %1\n", state_); if (currentCmd_) { QString extra = i18n("Current command class: '%1'\n" "Current command text: '%2'\n" "Current command original text: '%3'\n", typeid(*currentCmd_).name(), currentCmd_->cmdToSend(), currentCmd_->initialString()); information += extra; } KMessageBox::information(qApp->activeWindow(), information, i18n("Debugger status")); } bool DebugSession::stateIsOn(DBGStateFlags state) const { return state_ & state; } DBGStateFlags DebugSession::debuggerState() const { return state_; } void DebugSession::setStateOn(DBGStateFlags stateOn) { DBGStateFlags oldState = state_; debugStateChange(state_, state_ | stateOn); state_ |= stateOn; _gdbStateChanged(oldState, state_); } void DebugSession::setStateOff(DBGStateFlags stateOff) { DBGStateFlags oldState = state_; debugStateChange(state_, state_ & ~stateOff); state_ &= ~stateOff; _gdbStateChanged(oldState, state_); } void DebugSession::setState(DBGStateFlags newState) { DBGStateFlags oldState = state_; debugStateChange(state_, newState); state_ = newState; _gdbStateChanged(oldState, state_); } void DebugSession::debugStateChange(DBGStateFlags oldState, DBGStateFlags newState) { int delta = oldState ^ newState; if (delta) { QString out = "STATE:"; #define STATE_CHECK(name) \ do { \ if (delta & name) { \ out += ((newState & name) ? " +" : " -"); \ out += #name; \ delta &= ~name; \ } \ } while (0) STATE_CHECK(s_dbgNotStarted); STATE_CHECK(s_appNotStarted); STATE_CHECK(s_programExited); STATE_CHECK(s_attached); STATE_CHECK(s_core); STATE_CHECK(s_shuttingDown); STATE_CHECK(s_dbgBusy); STATE_CHECK(s_appRunning); STATE_CHECK(s_dbgNotListening); STATE_CHECK(s_automaticContinue); #undef STATE_CHECK for (unsigned int i = 0; delta != 0 && i < 32; ++i) { if (delta & (1 << i)) { delta &= ~(1 << i); out += ((1 << i) & newState) ? " +" : " -"; out += QString::number(i); } } qCDebug(DEBUGGERGDB) << out; } } void DebugSession::programRunning() { setStateOn(s_appRunning); raiseEvent(program_running); if (commandQueue_->haveImmediateCommand() || (m_gdb->currentCommand() && (m_gdb->currentCommand()->flags() & (CmdImmediately | CmdInterrupt)))) { ensureGdbListening(); } else { setStateOn(s_dbgNotListening); } } void DebugSession::handleVersion(const QStringList& s) { qCDebug(DEBUGGERGDB) << s.first(); // minimal version is 7.0,0 QRegExp rx("([7-9]+)\\.([0-9]+)(\\.([0-9]+))?"); int idx = rx.indexIn(s.first()); if (idx == -1) { if (!qobject_cast(qApp)) { //for unittest qFatal("You need a graphical application."); } KMessageBox::error( qApp->activeWindow(), i18n("You need gdb 7.0.0 or higher.
" "You are using: %1", s.first()), i18n("gdb error")); stopDebugger(); } } void DebugSession::handleFileExecAndSymbols(const GDBMI::ResultRecord& r) { if (r.reason == "error") { KMessageBox::error( qApp->activeWindow(), i18n("Could not start debugger:
")+ r["msg"].literal(), i18n("Startup error")); stopDebugger(); } } void DebugSession::handleTargetAttach(const GDBMI::ResultRecord& r) { if (r.reason == "error") { KMessageBox::error( qApp->activeWindow(), i18n("Could not attach debugger:
")+ r["msg"].literal(), i18n("Startup error")); stopDebugger(); } } } diff --git a/debuggers/gdb/debugsession.h b/debuggers/gdb/debugsession.h index 98aa5d641f..d10d441895 100644 --- a/debuggers/gdb/debugsession.h +++ b/debuggers/gdb/debugsession.h @@ -1,306 +1,307 @@ /* * GDB Debugger Support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef GDB_DEBUGSESSION_H #define GDB_DEBUGSESSION_H #include #include #include #include #include "breakpointcontroller.h" #include "gdbglobal.h" #include "mi/gdbmi.h" class IExecutePlugin; class KToolBar; namespace KTextEditor { class Cursor; } namespace KDevelop { class ProcessLineMaker; class ILaunchConfiguration; } namespace GDBDebugger { class STTY; class CommandQueue; class GDBCommand; class GDB; static const char gdbPathEntry[] = "GDB Path"; static const char debuggerShellEntry[] = "Debugger Shell"; static const char remoteGdbConfigEntry[] = "Remote GDB Config Script"; static const char remoteGdbShellEntry[] = "Remote GDB Shell Script"; static const char remoteGdbRunEntry[] = "Remote GDB Run Script"; static const char staticMembersEntry[] = "Display Static Members"; static const char demangleNamesEntry[] = "Display Demangle Names"; static const char allowForcedBPEntry[] = "Allow Forced Breakpoint Set"; static const char startWithEntry[] = "Start With"; class DebugSession : public KDevelop::IDebugSession { Q_OBJECT public: DebugSession(); ~DebugSession() override; DebuggerState state() const override; bool restartAvaliable() const override; BreakpointController* breakpointController() const override; KDevelop::IVariableController* variableController() const override; KDevelop::IFrameStackModel* frameStackModel() const override; bool hasCrashed() const; using IDebugSession::event; Q_SIGNALS: void applicationStandardOutputLines(const QStringList& lines); void applicationStandardErrorLines(const QStringList& lines); void showMessage(const QString& message, int timeout); void reset(); void programStopped(const GDBMI::AsyncRecord& mi_record); public Q_SLOTS: /** * Start the debugger, and execute the program specified by \a run. */ bool startProgram(KDevelop::ILaunchConfiguration* run, IExecutePlugin* execute); void restartDebugger() override; void stopDebugger() override; void interruptDebugger() override; void run() override; void runToCursor() override; void jumpToCursor() override; void stepOver() override; void stepIntoInstruction() override; void stepInto() override; void stepOverInstruction() override; void stepOut() override; /** * Start the debugger and examine the core file given by \a coreFile. */ void examineCoreFile(const QUrl& debugee, const QUrl& coreFile); /** * Attach to currently running process with the given \a pid. */ void attachToProcess(int pid); protected: /** * Testing mode affects a (very!) limited number of settings in an attempt to create * a cleaner and more reproducible environment for unit tests. */ void setTesting(bool testing); Q_SIGNALS: void raiseGdbConsoleViews(); private Q_SLOTS: void slotDebuggerAbnormalExit(); private: void _gdbStateChanged(DBGStateFlags oldState, DBGStateFlags newState); void setupController(); void setSessionState(KDevelop::IDebugSession::DebuggerState state); public: /** * Run currently executing program to the given \a url and \a line. */ void runUntil(const QUrl& url, int line); /** * Run currently executing program to the given \a address */ void runUntil(const QString& address); /** * Move the execution point of the currently executing program to the given \a url and \a line. */ void jumpTo(const QUrl& url, int line); /** * Move the execution point of the currently executing program to the given \a address. *Note: It can be really very dangerous, so use jumpTo instead. */ void jumpToMemoryAddress(const QString& address); /** Adds a command to the end of queue of commands to be executed by gdb. The command will be actually sent to gdb only when replies from all previous commands are received and full processed. The literal command sent to gdb is obtained by calling cmd->cmdToSend. The call is made immediately before sending the command, so it's possible to use results of prior commands when computing the exact command to send. */ void addCommand(GDBCommand* cmd); /** Same as above, but internally constructs new GDBCommand instance from the string. */ void addCommand(GDBMI::CommandType type, const QString& cmd = QString()); bool stateIsOn(DBGStateFlags state) const; DBGStateFlags debuggerState() const; using QObject::event; private: /** Try to execute next command in the queue. If GDB is not busy with previous command, and there's a command in the queue, sends it. */ void executeCmd(); void ensureGdbListening(); void destroyCmds(); /** Called when there are no pending commands and 'state_reload_needed' is true. Also can be used to immediately reload program state. Issues commands to completely reload all program state shown to the user. */ void reloadProgramState(); void programNoApp(const QString &msg); void programFinished(const QString &msg); void setStateOn(DBGStateFlags stateOn); void setStateOff(DBGStateFlags stateOff); void setState(DBGStateFlags newState); void debugStateChange(DBGStateFlags oldState, DBGStateFlags newState); void raiseEvent(event_t e) override; bool startDebugger(KDevelop::ILaunchConfiguration* cfg); private Q_SLOTS: void gdbReady(); void gdbExited(); void slotProgramStopped(const GDBMI::AsyncRecord& mi_record); /** Default handler for errors. Tries to guess is the error message is telling that target is gone, if so, informs the user. Otherwise, shows a dialog box and reloads view state. */ void defaultErrorHandler(const GDBMI::ResultRecord& result); /**Triggered every time program begins/continues it's execution.*/ void programRunning(); /** Handle MI async notifications. */ void processNotification(const GDBMI::AsyncRecord& n); // All of these slots are entered in the controller's thread, as they use queued connections or are called internally void queueCmd(GDBCommand *cmd); void configure(); // Pops up a dialog box with some hopefully // detailed information about which state debugger // is in, which commands were sent and so on. void explainDebuggerStatus(); void slotKillGdb(); void handleVersion(const QStringList& s); void handleFileExecAndSymbols(const GDBMI::ResultRecord& r); void handleTargetAttach(const GDBMI::ResultRecord& r); void handleCoreFile(const GDBMI::ResultRecord& r); public Q_SLOTS: void slotKill(); void slotUserGDBCmd(const QString&); Q_SIGNALS: void rawGDBMemoryDump (char *buf); void rawGDBRegisters (char *buf); void rawGDBLibraries (char *buf); void ttyStdout (const QByteArray& output); void ttyStderr (const QByteArray& output); void gdbInternalCommandStdout (const QString& output); void gdbUserCommandStdout (const QString& output); + void gdbInternalOutput (const QString& output); void gdbStateChanged(DBGStateFlags oldState, DBGStateFlags newState); void debuggerAbnormalExit(); /** Emitted immediately after breakpoint is hit, before any commands are sent and before current line indicator is shown. */ void breakpointHit(int id); /** Emitted for watchpoint hit, after line indicator is shown. */ void watchpointHit(int id, const QString& oldValue, const QString& newValue); private: friend class GdbTest; BreakpointController* m_breakpointController; KDevelop::IVariableController* m_variableController; KDevelop::IFrameStackModel* m_frameStackModel; KDevelop::ProcessLineMaker *m_procLineMaker; KDevelop::ProcessLineMaker *m_gdbLineMaker; DebuggerState m_sessionState; KConfigGroup m_config; QPointer m_gdb; bool m_testing; CommandQueue* commandQueue_; QScopedPointer m_tty; QString badCore_; // Some state variables DBGStateFlags state_; /**When program stops and all commands from queue are executed and this variable is true, program state shown to the user is updated.*/ bool state_reload_needed; /**True if program has stopped and all stuff like breakpoints is being updated.*/ bool stateReloadInProgress_; /**True if process crashed*/ bool m_hasCrashed; QTime commandExecutionTime; ///Exit code of the last inferior(in format: exit normally, with code "number" e.t.c) QString m_inferiorExitCode; }; } #endif diff --git a/debuggers/gdb/gdb.cpp b/debuggers/gdb/gdb.cpp index 38b1ba665f..67ed85b003 100644 --- a/debuggers/gdb/gdb.cpp +++ b/debuggers/gdb/gdb.cpp @@ -1,412 +1,411 @@ /* * Low level GDB interface. * * Copyright 1999 John Birch * Copyright 2007 Vladimir Prus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gdb.h" #include "debugsession.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include // #define DEBUG_NO_TRY //to get a backtrace to where the exception was thrown Q_LOGGING_CATEGORY(DEBUGGERGDB, "kdevelop.debuggers.gdb") using namespace GDBDebugger; GDB::GDB(QObject* parent) : QObject(parent), process_(0), currentCmd_(0) { } GDB::~GDB() { // prevent Qt warning: QProcess: Destroyed while process is still running. if (process_ && process_->state() == QProcess::Running) { disconnect(process_, static_cast(&KProcess::error), this, &GDB::processErrored); process_->kill(); process_->waitForFinished(10); } } void GDB::start(KConfigGroup& config, const QStringList& extraArguments) { // FIXME: verify that default value leads to something sensible QUrl gdbUrl = config.readEntry(GDBDebugger::gdbPathEntry, QUrl()); if (gdbUrl.isEmpty()) { gdbBinary_ = "gdb"; } else { // FIXME: verify its' a local path. gdbBinary_ = gdbUrl.url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); } process_ = new KProcess(this); process_->setOutputChannelMode( KProcess::SeparateChannels ); connect(process_, &KProcess::readyReadStandardOutput, this, &GDB::readyReadStandardOutput); connect(process_, &KProcess::readyReadStandardError, this, &GDB::readyReadStandardError); connect(process_, static_cast(&KProcess::finished), this, &GDB::processFinished); connect(process_, static_cast(&KProcess::error), this, &GDB::processErrored); QStringList arguments = extraArguments; arguments << "--interpreter=mi2" << "-quiet"; QUrl shell = config.readEntry(GDBDebugger::debuggerShellEntry, QUrl()); if( !shell.isEmpty() ) { qCDebug(DEBUGGERGDB) << "have shell" << shell; QString shell_without_args = shell.toLocalFile().split(QChar(' ')).first(); QFileInfo info( shell_without_args ); /*if( info.isRelative() ) { shell_without_args = build_dir + "/" + shell_without_args; info.setFile( shell_without_args ); }*/ if( !info.exists() ) { KMessageBox::information( qApp->activeWindow(), i18n("Could not locate the debugging shell '%1'.", shell_without_args ), i18n("Debugging Shell Not Found") ); // FIXME: throw, or set some error message. return; } arguments.insert(0, gdbBinary_); arguments.insert(0, shell.toLocalFile()); process_->setShellCommand( KShell::joinArgs( arguments ) ); } else { process_->setProgram( gdbBinary_, arguments ); } process_->start(); qCDebug(DEBUGGERGDB) << "STARTING GDB\n"; emit userCommandOutput(shell.toLocalFile() + ' ' + gdbBinary_ + " --interpreter=mi2 -quiet\n" ); } void GDB::execute(GDBCommand* command) { currentCmd_ = command; QString commandText = currentCmd_->cmdToSend(); qCDebug(DEBUGGERGDB) << "SEND:" << commandText.trimmed(); QByteArray commandUtf8 = commandText.toUtf8(); process_->write(commandUtf8, commandUtf8.length()); QString prettyCmd = currentCmd_->cmdToSend(); prettyCmd.remove( QRegExp("set prompt \032.\n") ); prettyCmd = "(gdb) " + prettyCmd; if (currentCmd_->isUserCommand()) emit userCommandOutput(prettyCmd); else emit internalCommandOutput(prettyCmd); } bool GDB::isReady() const { return currentCmd_ == 0; } void GDB::interrupt() { //TODO:win32 Porting needed int pid = process_->pid(); if (pid != 0) { ::kill(pid, SIGINT); } } GDBCommand* GDB::currentCommand() const { return currentCmd_; } void GDB::kill() { process_->kill(); } void GDB::readyReadStandardOutput() { process_->setReadChannel(QProcess::StandardOutput); buffer_ += process_->readAll(); for (;;) { /* In MI mode, all messages are exactly one line. See if we have any complete lines in the buffer. */ int i = buffer_.indexOf('\n'); if (i == -1) break; QByteArray reply(buffer_.left(i)); buffer_ = buffer_.mid(i+1); processLine(reply); } } void GDB::readyReadStandardError() { process_->setReadChannel(QProcess::StandardOutput); - emit internalCommandOutput(QString::fromUtf8(process_->readAll())); + emit debuggerInternalOutput(QString::fromUtf8(process_->readAll())); } void GDB::processLine(const QByteArray& line) { qCDebug(DEBUGGERGDB) << "GDB output: " << line; FileSymbol file; file.contents = line; std::unique_ptr r(mi_parser_.parse(&file)); if (!r) { // FIXME: Issue an error! qCDebug(DEBUGGERGDB) << "Invalid MI message:" << line; // We don't consider the current command done. // So, if a command results in unparseable reply, // we'll just wait for the "right" reply, which might // never come. However, marking the command as // done in this case is even more risky. // It's probably possible to get here if we're debugging // natively without PTY, though this is uncommon case. return; } #ifndef DEBUG_NO_TRY try { #endif switch(r->kind) { case GDBMI::Record::Result: { GDBMI::ResultRecord& result = static_cast(*r); emit internalCommandOutput(QString::fromUtf8(line) + '\n'); // GDB doc: "running" and "exit" are status codes equivalent to "done" if (result.reason == "done" || result.reason == "running" || result.reason == "exit") { if (!currentCmd_) { qCDebug(DEBUGGERGDB) << "Received a result without a pending command"; } else { Q_ASSERT(currentCmd_->token() == result.token); currentCmd_->invokeHandler(result); } } else if (result.reason == "error") { qCDebug(DEBUGGERGDB) << "Handling error"; // Some commands want to handle errors themself. if (currentCmd_->handlesError() && currentCmd_->invokeHandler(result)) { qCDebug(DEBUGGERGDB) << "Invoked custom handler\n"; // Done, nothing more needed } else emit error(result); } else { qCDebug(DEBUGGERGDB) << "Unhandled result code: " << result.reason; } delete currentCmd_; currentCmd_ = nullptr; emit ready(); break; } case GDBMI::Record::Async: { GDBMI::AsyncRecord& async = dynamic_cast(*r); switch (async.subkind) { case GDBMI::AsyncRecord::Exec: { // Prefix '*'; asynchronous state changes of the target if (async.reason == "stopped") { emit programStopped(async); } else if (async.reason == "running") { emit programRunning(); } else { qCDebug(DEBUGGERGDB) << "Unhandled exec notification: " << async.reason; } break; } case GDBMI::AsyncRecord::Notify: { // Prefix '='; supplementary information that we should handle (new breakpoint etc.) emit notification(async); break; } case GDBMI::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 GDBMI::Record::Stream: { GDBMI::StreamRecord& s = dynamic_cast(*r); if (s.subkind == GDBMI::StreamRecord::Target) { emit applicationOutput(s.message); - } else { + } else if (s.subkind == GDBMI::StreamRecord::Console) { if (currentCmd_ && currentCmd_->isUserCommand()) emit userCommandOutput(s.message); - else if (s.subkind == GDBMI::StreamRecord::Console) { - emit applicationOutput(s.message); - } else { + else emit internalCommandOutput(s.message); - } if (currentCmd_) currentCmd_->newOutput(s.message); + } else { + emit debuggerInternalOutput(s.message); } emit streamRecord(s); break; } case GDBMI::Record::Prompt: break; } #ifndef DEBUG_NO_TRY } catch(const std::exception& e) { KMessageBox::detailedSorry( qApp->activeWindow(), i18nc("Internal debugger error", "

The debugger component encountered internal error while " "processing reply from gdb. Please submit a bug report."), i18n("The exception is: %1\n" "The MI response is: %2", e.what(), QString::fromLatin1(line)), i18n("Internal debugger error")); } #endif } // ************************************************************************** void GDB::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); Q_UNUSED(exitStatus); qCDebug(DEBUGGERGDB) << "GDB FINISHED\n"; /* FIXME: return the status? */ emit gdbExited(); /* FIXME: revive. Need to arrange for controller to delete us. bool abnormal = exitCode != 0 || exitStatus != QProcess::NormalExit; deleteLater(); delete tty_; tty_ = 0; if (abnormal) emit debuggerAbnormalExit(); raiseEvent(debugger_exited); destroyCmds(); setState(s_dbgNotStarted|s_appNotStarted|s_programExited); emit showMessage(i18n("Process exited"), 3000); emit gdbUserCommandStdout("(gdb) Process exited\n"); */ } void GDB::processErrored(QProcess::ProcessError error) { qCDebug(DEBUGGERGDB) << "GDB ERRORED" << error; if( error == QProcess::FailedToStart ) { KMessageBox::information( qApp->activeWindow(), i18n("Could not start debugger." "

Could not run '%1'. " "Make sure that the path name is specified correctly.", gdbBinary_), i18n("Could not start debugger")); /* FIXME: make sure the controller gets rids of GDB instance emit debuggerAbnormalExit(); raiseEvent(debugger_exited); */ /* Used to be before, GDB controller might want to do the same. destroyCmds(); setState(s_dbgNotStarted|s_appNotStarted|s_programExited); emit showMessage(i18n("Process didn't start"), 3000); */ emit userCommandOutput("(gdb) didn't start\n"); } else if (error == QProcess::Crashed) { KMessageBox::error( qApp->activeWindow(), i18n("Gdb crashed." "

Because of that the debug session has to be ended.
" "Try to reproduce the crash with plain gdb and report a bug.
"), i18n("Gdb crashed")); } } diff --git a/debuggers/gdb/gdb.h b/debuggers/gdb/gdb.h index 087f51e708..41fdef2741 100644 --- a/debuggers/gdb/gdb.h +++ b/debuggers/gdb/gdb.h @@ -1,144 +1,147 @@ /* * Low level GDB interface. * * Copyright 2007 Vladimir Prus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef GDB_H_d5c9cb274cbad688fe7a507a84f6633b #define GDB_H_d5c9cb274cbad688fe7a507a84f6633b #include "mi/gdbmi.h" #include "mi/miparser.h" #include "gdbcommand.h" #include #include #include class KConfigGroup; namespace GDBDebugger { class GDB : public QObject { Q_OBJECT public: explicit GDB(QObject* parent = 0); ~GDB() override; /** Starts GDB. This should be done after connecting to all signals the client is interested in. */ void start(KConfigGroup& config, const QStringList& extraArguments = {}); /** Executes a command. This method may be called at most once each time 'ready' is emitted. When the GDB instance is just constructed, one should wait for 'ready' as well. The ownership of 'command' is transferred to GDB. */ void execute(GDBCommand* command); /** Returns true if 'execute' can be called immediately. */ bool isReady() const; /** FIXME: temporary, to be eliminated. */ GDBCommand* currentCommand() const; /** Arrange to gdb to stop doing whatever it's doing, and start waiting for a command. FIXME: probably should make sure that 'ready' is emitted, or something. */ void interrupt(); /** Kills GDB. */ void kill(); Q_SIGNALS: /** Emitted when debugger becomes ready -- i.e. when isReady call will return true. */ void ready(); /** Emitted when GDB itself exits. This could happen because it just crashed due to internal bug, or we killed it explicitly. */ void gdbExited(); /** Emitted when GDB reports stop, with 'r' being the data provided by GDB. */ void programStopped(const GDBMI::AsyncRecord& r); /** Emitted when GDB believes that the program is running. */ void programRunning(); /** Emitted for each MI stream record found. Presently only used to recognize some CLI messages that mean that the program has died. FIXME: connect to parseCliLine */ void streamRecord(const GDBMI::StreamRecord& s); /** Reports an async notification record. */ void notification(const GDBMI::AsyncRecord& n); /** Emitted for error that is not handled by the command being executed. */ void error(const GDBMI::ResultRecord& s); /** Reports output from the running application. Generally output will only be available when using remote GDB targets. When running locally, the output will either appear on GDB stdout, and ignored, or routed via pty. */ void applicationOutput(const QString& s); /** Reports output of a command explicitly typed by the user, or output from .gdbinit commands. */ void userCommandOutput(const QString& s); /** Reports output of a command issued internally - by KDevelop. At the moment, stderr output from - GDB and the 'log' MI channel will be also routed here. */ + by KDevelop. */ void internalCommandOutput(const QString& s); + /** Reports debugger interal output, including stderr output from debugger + and the 'log' MI channel */ + void debuggerInternalOutput(const QString& s); + private Q_SLOTS: void readyReadStandardOutput(); void readyReadStandardError(); void processFinished(int exitCode, QProcess::ExitStatus exitStatus); void processErrored(QProcess::ProcessError); private: void processLine(const QByteArray& line); private: QString gdbBinary_; KProcess* process_; GDBCommand* currentCmd_; MIParser mi_parser_; /** The unprocessed output from gdb. Output is processed as soon as we see newline. */ QByteArray buffer_; }; } #endif diff --git a/debuggers/gdb/gdbframestackmodel.cpp b/debuggers/gdb/gdbframestackmodel.cpp index 78778ca133..dbb16bb9ac 100644 --- a/debuggers/gdb/gdbframestackmodel.cpp +++ b/debuggers/gdb/gdbframestackmodel.cpp @@ -1,141 +1,144 @@ /* * GDB-specific implementation of thread and frame model. * * Copyright 2009 Vladimir Prus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gdbframestackmodel.h" #include "gdbcommand.h" #include +#include + using namespace KDevelop; QString getFunctionOrAddress(const GDBMI::Value &frame) { if (frame.hasField("func")) return frame["func"].literal(); else return frame["addr"].literal(); } QPair getSource(const GDBMI::Value &frame) { QPair ret(QString(), -1); if (frame.hasField("fullname")) ret=qMakePair(frame["fullname"].literal(), frame["line"].toInt()-1); else if (frame.hasField("file")) ret=qMakePair(frame["file"].literal(), frame["line"].toInt()-1); else if (frame.hasField("from")) ret.first=frame["from"].literal(); return ret; } void GdbFrameStackModel::fetchThreads() { session()->addCommand( new GDBCommand(GDBMI::ThreadInfo, "", this, &GdbFrameStackModel::handleThreadInfo)); } void GdbFrameStackModel::handleThreadInfo(const GDBMI::ResultRecord& r) { const GDBMI::Value& threads = r["threads"]; - // Traverse GDB threads in backward order -- since GDB - // reports them in backward order. We want UI to - // show thread IDs in the natural order. - // FIXME: make the code independent of whatever craziness - // gdb might have tomorrow. - - QList threadsList; - int gidx = threads.size()-1; - for (; gidx >= 0; --gidx) { - KDevelop::FrameStackModel::ThreadItem i; - const GDBMI::Value & threadMI = threads[gidx]; - i.nr = threadMI["id"].toInt(); + QList threadsList; + for (int i = 0; i!= threads.size(); ++i) { + const auto &threadMI = threads[i]; + FrameStackModel::ThreadItem threadItem; + threadItem.nr = threadMI["id"].toInt(); if (threadMI["state"].literal() == "stopped") { - i.name = getFunctionOrAddress(threads[gidx]["frame"]); + threadItem.name = getFunctionOrAddress(threadMI["frame"]); } else { - i.name = i18n("(running)"); + i18n("(running)"); } - threadsList << i; + threadsList << threadItem; } + // Sort the list by id, some old version of GDB + // reports them in backward order. We want UI to + // show thread IDs in the natural order. + std::sort(threadsList.begin(), threadsList.end(), + [](const FrameStackModel::ThreadItem &a, const FrameStackModel::ThreadItem &b){ + return a.nr < b.nr; + }); + setThreads(threadsList); if (r.hasField("current-thread-id")) { int currentThreadId = r["current-thread-id"].toInt(); setCurrentThread(currentThreadId); if (session()->hasCrashed()) { setCrashedThreadIndex(currentThreadId); } } } struct FrameListHandler : public GDBCommandHandler { FrameListHandler(GdbFrameStackModel* model, int thread, int to) : model(model), m_thread(thread) , m_to(to) {} void handle(const GDBMI::ResultRecord &r) override { const GDBMI::Value& stack = r["stack"]; int first = stack[0]["level"].toInt(); QList frames; for (int i = 0; i< stack.size(); ++i) { const GDBMI::Value& frame = stack[i]; KDevelop::FrameStackModel::FrameItem f; f.nr = frame["level"].toInt(); f.name = getFunctionOrAddress(frame); QPair loc = getSource(frame); f.file = QUrl::fromLocalFile(loc.first); f.line = loc.second; frames << f; } bool hasMore = false; if (!frames.isEmpty()) { if (frames.last().nr == m_to+1) { frames.takeLast(); hasMore = true; } } if (first == 0) { model->setFrames(m_thread, frames); } else { model->insertFrames(m_thread, frames); } model->setHasMoreFrames(m_thread, hasMore); } private: GdbFrameStackModel* model; int m_thread; int m_to; }; void GdbFrameStackModel::fetchFrames(int threadNumber, int from, int to) { //to+1 so we know if there are more QString arg = QString("%1 %2").arg(from).arg(to+1); GDBCommand *c = new GDBCommand(GDBMI::StackListFrames, arg, new FrameListHandler(this, threadNumber, to)); c->setThread(threadNumber); session()->addCommand(c); } diff --git a/debuggers/gdb/gdboutputwidget.cpp b/debuggers/gdb/gdboutputwidget.cpp index db08e9e54d..c4ecd216a5 100644 --- a/debuggers/gdb/gdboutputwidget.cpp +++ b/debuggers/gdb/gdboutputwidget.cpp @@ -1,460 +1,463 @@ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gdbglobal.h" #include "debuggerplugin.h" #include "debugsession.h" #include "debug.h" namespace GDBDebugger { /***************************************************************************/ 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::raiseGdbConsoleViews, 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::slotUserGDBCmd); connect(this, &GDBOutputWidget::breakInto, session, &DebugSession::interruptDebugger); connect(session, &DebugSession::gdbInternalCommandStdout, this, &GDBOutputWidget::slotInternalCommandStdout); connect(session, &DebugSession::gdbUserCommandStdout, this, &GDBOutputWidget::slotUserCommandStdout); + // debugger internal output, treat it as an internal command output + connect(session, &DebugSession::gdbInternalOutput, + this, &GDBOutputWidget::slotInternalCommandStdout); connect(session, &DebugSession::gdbStateChanged, 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) { 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/unittests/test_gdb.cpp b/debuggers/gdb/unittests/test_gdb.cpp index 597be0661a..c39ca4eb7b 100644 --- a/debuggers/gdb/unittests/test_gdb.cpp +++ b/debuggers/gdb/unittests/test_gdb.cpp @@ -1,2033 +1,2036 @@ /* Copyright 2009 Niko Sams Copyright 2013 Vlas Puhov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_gdb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gdbcommand.h" #include "debugsession.h" #include "gdbframestackmodel.h" #include #include #include using KDevelop::AutoTestShell; namespace GDBDebugger { QUrl findExecutable(const QString& name) { QFileInfo info(qApp->applicationDirPath() + "/unittests/" + name); Q_ASSERT(info.exists()); Q_ASSERT(info.isExecutable()); return QUrl::fromLocalFile(info.canonicalFilePath()); } QString findSourceFile(const QString& name) { QFileInfo info(QFileInfo(__FILE__).dir().absoluteFilePath(name)); Q_ASSERT(info.exists()); return info.canonicalFilePath(); } static bool isAttachForbidden(const char * file, int line) { // if on linux, ensure we can actually attach QFile canRun("/proc/sys/kernel/yama/ptrace_scope"); if (canRun.exists()) { if (!canRun.open(QIODevice::ReadOnly)) { QTest::qFail("Something is wrong: /proc/sys/kernel/yama/ptrace_scope exists but cannot be read", file, line); return true; } if (canRun.read(1).toInt() != 0) { QTest::qSkip("ptrace attaching not allowed, skipping test. To enable it, set /proc/sys/kernel/yama/ptrace_scope to 0.", file, line); return true; } } return false; } #define SKIP_IF_ATTACH_FORBIDDEN() \ do { \ if (isAttachForbidden(__FILE__, __LINE__)) \ return; \ } while(0) void GdbTest::initTestCase() { AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); m_iface = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IExecutePlugin", "kdevexecute")->extension(); Q_ASSERT(m_iface); } void GdbTest::cleanupTestCase() { KDevelop::TestCore::shutdown(); } void GdbTest::init() { //remove all breakpoints - so we can set our own in the test KConfigGroup breakpoints = KSharedConfig::openConfig()->group("breakpoints"); breakpoints.writeEntry("number", 0); breakpoints.sync(); KDevelop::BreakpointModel* m = KDevelop::ICore::self()->debugController()->breakpointModel(); m->removeRows(0, m->rowCount()); KDevelop::VariableCollection *vc = KDevelop::ICore::self()->debugController()->variableCollection(); for (int i=0; i < vc->watches()->childCount(); ++i) { delete vc->watches()->child(i); } vc->watches()->clear(); } class TestLaunchConfiguration : public KDevelop::ILaunchConfiguration { public: TestLaunchConfiguration(const QUrl& executable = findExecutable("debugee"), const QUrl& workingDirectory = QUrl()) { qDebug() << "FIND" << executable; c = new KConfig(); c->deleteGroup("launch"); cfg = c->group("launch"); cfg.writeEntry("isExecutable", true); cfg.writeEntry("Executable", executable); cfg.writeEntry("Working Directory", workingDirectory); } ~TestLaunchConfiguration() override { delete c; } const KConfigGroup config() const override { return cfg; } KConfigGroup config() override { return cfg; }; QString name() const override { return QString("Test-Launch"); } KDevelop::IProject* project() const override { return 0; } KDevelop::LaunchConfigurationType* type() const override { return 0; } private: KConfigGroup cfg; KConfig *c; }; class TestFrameStackModel : public KDevelop::GdbFrameStackModel { public: TestFrameStackModel(DebugSession* session) : GdbFrameStackModel(session), fetchFramesCalled(0), fetchThreadsCalled(0) {} int fetchFramesCalled; int fetchThreadsCalled; void fetchFrames(int threadNumber, int from, int to) override { fetchFramesCalled++; GdbFrameStackModel::fetchFrames(threadNumber, from, to); } void fetchThreads() override { fetchThreadsCalled++; GdbFrameStackModel::fetchThreads(); } }; class TestDebugSession : public DebugSession { Q_OBJECT public: TestDebugSession() : DebugSession() { setTesting(true); m_frameStackModel = new TestFrameStackModel(this); KDevelop::ICore::self()->debugController()->addSession(this); } QUrl url() { return currentUrl(); } int line() { return currentLine(); } TestFrameStackModel* frameStackModel() const override { return m_frameStackModel; } private: TestFrameStackModel* m_frameStackModel; }; class TestWaiter { public: TestWaiter(DebugSession * session_, const char * condition_, const char * file_, int line_) : session(session_) , condition(condition_) , file(file_) , line(line_) { stopWatch.start(); } bool waitUnless(bool ok) { if (ok) { qDebug() << "Condition " << condition << " reached in " << file << ':' << line; return false; } if (stopWatch.elapsed() > 5000) { QTest::qFail(qPrintable(QString("Timeout before reaching condition %0").arg(condition)), file, line); return false; } QTest::qWait(100); if (!session) { QTest::qFail(qPrintable(QString("Session ended without reaching condition %0").arg(condition)), file, line); return false; } return true; } private: QTime stopWatch; QPointer session; const char * condition; const char * file; int line; }; #define WAIT_FOR_STATE(session, state) \ do { if (!waitForState((session), (state), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR_STATE_AND_IDLE(session, state) \ do { if (!waitForState((session), (state), __FILE__, __LINE__, true)) return; } while (0) #define WAIT_FOR(session, condition) \ do { \ TestWaiter w((session), #condition, __FILE__, __LINE__); \ while (w.waitUnless((condition))) /* nothing */ ; \ } while(0) #define COMPARE_DATA(index, expected) \ compareData((index), (expected), __FILE__, __LINE__) void compareData(QModelIndex index, QString expected, const char *file, int line) { QString s = index.model()->data(index, Qt::DisplayRole).toString(); if (s != expected) { QFAIL(qPrintable(QString("'%0' didn't match expected '%1' in %2:%3").arg(s).arg(expected).arg(file).arg(line))); } } static const QString debugeeFileName = findSourceFile("debugee.cpp"); KDevelop::BreakpointModel* breakpoints() { return KDevelop::ICore::self()->debugController()->breakpointModel(); } void GdbTest::testStdOut() { TestDebugSession *session = new TestDebugSession; QSignalSpy outputSpy(session, SIGNAL(applicationStandardOutputLines(QStringList))); TestLaunchConfiguration cfg; session->startProgram(&cfg, m_iface); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); { QCOMPARE(outputSpy.count(), 1); QList arguments = outputSpy.takeFirst(); QCOMPARE(arguments.count(), 1); QCOMPARE(arguments.first().toStringList(), QStringList() << "Hello, world!" << "Hello"); } } void GdbTest::testBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); } void GdbTest::testDisableBreakpoint() { //Description: We must stop only on the third breakpoint int firstBreakLine=28; int secondBreakLine=23; int thirdBreakLine=24; int fourthBreakLine=31; TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b; b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), firstBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //this is needed to emulate debug from GUI. If we are in edit mode, the debugSession doesn't exist. KDevelop::ICore::self()->debugController()->breakpointModel()->blockSignals(true); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), secondBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //all disabled breakpoints were added KDevelop::Breakpoint * thirdBreak = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), thirdBreakLine); KDevelop::ICore::self()->debugController()->breakpointModel()->blockSignals(false); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), thirdBreak->line()); //disable existing breakpoint thirdBreak->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //add another disabled breakpoint b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), fourthBreakLine); QTest::qWait(300); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); QTest::qWait(300); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testChangeLocationBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 27); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); QTest::qWait(100); b->setLine(28); QTest::qWait(100); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 28); QTest::qWait(500); breakpoints()->setData(breakpoints()->index(0, KDevelop::Breakpoint::LocationColumn), QString(debugeeFileName+":30")); QCOMPARE(b->line(), 29); QTest::qWait(100); QCOMPARE(b->line(), 29); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testDeleteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; QCOMPARE(breakpoints()->rowCount(), 0); //add breakpoint before startProgram breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QCOMPARE(breakpoints()->rowCount(), 1); breakpoints()->removeRow(0); QCOMPARE(breakpoints()->rowCount(), 0); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPendingBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("test_gdb.cpp")), 10); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::PendingState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testUpdateBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; + // breakpoint 1: line 29 KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(breakpoints()->rowCount(), 1); session->startProgram(&cfg, m_iface); + // breakpoint 2: line 28 //insert custom command as user might do it using GDB console session->addCommand(new UserCommand(GDBMI::NonMI, "break "+debugeeFileName+":28")); - WAIT_FOR_STATE(session, DebugSession::PausedState); - QTest::qWait(100); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 28 session->stepInto(); - WAIT_FOR_STATE(session, DebugSession::PausedState); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop after step QCOMPARE(breakpoints()->rowCount(), 2); b = breakpoints()->breakpoint(1); QCOMPARE(b->url(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(b->line(), 27); session->run(); + WAIT_FOR_STATE(session, DebugSession::PausedState); // stop at line 29 + session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testIgnoreHitsBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); b1->setIgnoreHits(1); KDevelop::Breakpoint * b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); session->startProgram(&cfg, m_iface); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b2->hitCount() == 1); b2->setIgnoreHits(1); session->run(); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b1->hitCount() == 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 39); b->setCondition("x[0] == 'H'"); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 23); b->setCondition("i==2"); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startProgram(&cfg, m_iface); WAIT_FOR(session, session->state() == DebugSession::PausedState && session->line() == 24); b->setCondition("i == 0"); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 39); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnWriteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addWatchpoint("i"); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnWriteWithConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); KDevelop::Breakpoint *b = breakpoints()->addWatchpoint("i"); b->setCondition("i==2"); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnReadBreakpoint() { /* test disabled because of gdb bug: http://sourceware.org/bugzilla/show_bug.cgi?id=10136 TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b = breakpoints()->addReadWatchpoint("foo::i"); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); */ } void GdbTest::testBreakOnReadBreakpoint2() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addReadWatchpoint("i"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnAccessBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addAccessWatchpoint("i"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QTest::qWait(500); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 25); b->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointWhileRunningMultiple() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 24); KDevelop::Breakpoint *b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QTest::qWait(500); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 25); b1->setDeleted(); b2->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointFunctionName() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testManualBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); breakpoints()->removeRows(0, 1); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 0); session->addCommand(GDBMI::NonMI, "break debugee.cpp:23"); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 1); Breakpoint* b = breakpoints()->breakpoint(0); QCOMPARE(b->line(), 22); session->addCommand(GDBMI::NonMI, "disable 2"); session->addCommand(GDBMI::NonMI, "condition 2 i == 1"); session->addCommand(GDBMI::NonMI, "ignore 2 1"); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->enabled(), false); QCOMPARE(b->condition(), QString("i == 1")); QCOMPARE(b->ignoreHits(), 1); session->addCommand(GDBMI::NonMI, "delete 2"); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testShowStepInSource() { TestDebugSession *session = new TestDebugSession; QSignalSpy showStepInSourceSpy(session, SIGNAL(showStepInSource(QUrl,int,QString))); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); { QCOMPARE(showStepInSourceSpy.count(), 3); QList arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 29); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 22); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 23); } } void GdbTest::testStack() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); QCOMPARE(stackModel->rowCount(tIdx), 2); QCOMPARE(stackModel->columnCount(tIdx), 3); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "foo"); COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":23"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "main"); COMPARE_DATA(tIdx.child(1, 2), debugeeFileName+":29"); session->stepOut(); WAIT_FOR_STATE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":30"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackFetchMore() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeerecursion")); QString fileName = findSourceFile("debugeerecursion.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->frameStackModel()->fetchFramesCalled, 1); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); QCOMPARE(stackModel->rowCount(tIdx), 21); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "foo"); COMPARE_DATA(tIdx.child(0, 2), fileName+":26"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "foo"); COMPARE_DATA(tIdx.child(1, 2), fileName+":24"); COMPARE_DATA(tIdx.child(2, 0), "2"); COMPARE_DATA(tIdx.child(2, 1), "foo"); COMPARE_DATA(tIdx.child(2, 2), fileName+":24"); COMPARE_DATA(tIdx.child(19, 0), "19"); COMPARE_DATA(tIdx.child(20, 0), "20"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 2); QCOMPARE(stackModel->rowCount(tIdx), 41); COMPARE_DATA(tIdx.child(20, 0), "20"); COMPARE_DATA(tIdx.child(21, 0), "21"); COMPARE_DATA(tIdx.child(22, 0), "22"); COMPARE_DATA(tIdx.child(39, 0), "39"); COMPARE_DATA(tIdx.child(40, 0), "40"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 3); QCOMPARE(stackModel->rowCount(tIdx), 121); COMPARE_DATA(tIdx.child(40, 0), "40"); COMPARE_DATA(tIdx.child(41, 0), "41"); COMPARE_DATA(tIdx.child(42, 0), "42"); COMPARE_DATA(tIdx.child(119, 0), "119"); COMPARE_DATA(tIdx.child(120, 0), "120"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); COMPARE_DATA(tIdx.child(120, 0), "120"); COMPARE_DATA(tIdx.child(121, 0), "121"); COMPARE_DATA(tIdx.child(122, 0), "122"); COMPARE_DATA(tIdx.child(300, 0), "300"); COMPARE_DATA(tIdx.child(300, 1), "main"); COMPARE_DATA(tIdx.child(300, 2), fileName+":30"); stackModel->fetchMoreFrames(); //nothing to fetch, we are at the end QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackDeactivateAndActive() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); session->stepOut(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(200); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), debugeeFileName+":30"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackSwitchThread() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeethreads")); QString fileName = findSourceFile("debugeethreads.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->rowCount(), 4); QModelIndex tIdx = stackModel->index(0,0); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), fileName+":39"); tIdx = stackModel->index(1,0); QVERIFY(stackModel->data(tIdx).toString().startsWith("#2 at ")); stackModel->setCurrentThread(2); QTest::qWait(200); int rows = stackModel->rowCount(tIdx); QVERIFY(rows > 3); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile("debugeeslow.cpp"); KProcess debugeeProcess; debugeeProcess << "nice" << findExecutable("debugeeslow").toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); QTest::qWait(100); TestDebugSession *session = new TestDebugSession; session->attachToProcess(debugeeProcess.pid()); WAIT_FOR_STATE(session, DebugSession::PausedState); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 34); QTest::qWait(100); session->run(); QTest::qWait(2000); WAIT_FOR_STATE(session, DebugSession::PausedState); if (session->line() < 34 || session->line() < 35) { QCOMPARE(session->line(), 34); } session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testManualAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile("debugeeslow.cpp"); KProcess debugeeProcess; debugeeProcess << "nice" << findExecutable("debugeeslow").toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; cfg.config().writeEntry(GDBDebugger::remoteGdbRunEntry, QUrl::fromLocalFile(findSourceFile("gdb_script_empty"))); QVERIFY(session->startProgram(&cfg, m_iface)); session->addCommand(GDBMI::NonMI, QString("attach %0").arg(debugeeProcess.pid())); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); QTest::qWait(2000); // give the slow inferior some extra time to run WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testCoreFile() { QFile f("core"); if (f.exists()) f.remove(); KProcess debugeeProcess; debugeeProcess.setOutputChannelMode(KProcess::MergedChannels); debugeeProcess << "bash" << "-c" << "ulimit -c unlimited; " + findExecutable("debugeecrash").toLocalFile(); debugeeProcess.start(); debugeeProcess.waitForFinished(); qDebug() << debugeeProcess.readAll(); if (!QFile::exists("core")) { QSKIP("no core dump found, check your system configuration (see /proc/sys/kernel/core_pattern).", SkipSingle); } TestDebugSession *session = new TestDebugSession; session->examineCoreFile(findExecutable("debugeecrash"), QUrl::fromLocalFile(QDir::currentPath()+"/core")); TestFrameStackModel *stackModel = session->frameStackModel(); WAIT_FOR_STATE(session, DebugSession::StoppedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } KDevelop::VariableCollection *variableCollection() { return KDevelop::ICore::self()->debugController()->variableCollection(); } void GdbTest::testVariablesLocals() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QCOMPARE(variableCollection()->rowCount(), 2); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "0"); session->run(); QTest::qWait(1000); WAIT_FOR_STATE(session, DebugSession::PausedState); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesLocalsStruct() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QModelIndex i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); int structIndex = 0; for(int j=0; j<3; ++j) { if (variableCollection()->index(j, 0, i).data().toString() == "ts") { structIndex = j; } } COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(structIndex, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatches() { TestDebugSession *session = new TestDebugSession; KDevelop::ICore::self()->debugController()->variableCollection()->variableWidgetShown(); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("ts"); QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatchesQuotes() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; const QString testString("test"); const QString quotedTestString("\"" + testString + "\""); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(quotedTestString); //just a constant string QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), quotedTestString); COMPARE_DATA(variableCollection()->index(0, 1, i), "[" + QString::number(testString.length() + 1) + "]"); QModelIndex testStr = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, testStr), "..."); variableCollection()->expanded(testStr); QTest::qWait(100); int len = testString.length(); for (int ind = 0; ind < len; ind++) { COMPARE_DATA(variableCollection()->index(ind, 0, testStr), QString::number(ind)); QChar c = testString.at(ind); QString value = QString::number(c.toLatin1()) + " '" + c + "'"; COMPARE_DATA(variableCollection()->index(ind, 1, testStr), value); } COMPARE_DATA(variableCollection()->index(len, 0, testStr), QString::number(len)); COMPARE_DATA(variableCollection()->index(len, 1, testStr), "0 '\\000'"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatchesTwoSessions() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("ts"); QTest::qWait(300); QModelIndex ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); variableCollection()->expanded(ts); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope QCOMPARE(variableCollection()->watches()->childCount(), 1); KDevelop::Variable* v = dynamic_cast(variableCollection()->watches()->child(0)); QVERIFY(v); QVERIFY(!v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(!v->inScope()); //start a second debug session session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(300); QCOMPARE(variableCollection()->watches()->childCount(), 1); ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); v = dynamic_cast(variableCollection()->watches()->child(0)); QVERIFY(v); QVERIFY(v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(v->inScope()); QCOMPARE(v->data(1, Qt::DisplayRole).toString(), QString::number(0)); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope v = dynamic_cast(variableCollection()->watches()->child(0)); QVERIFY(!v->inScope()); QVERIFY(!dynamic_cast(v->child(0))->inScope()); } void GdbTest::testVariablesStopDebugger() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stopDebugger(); QTest::qWait(300); } void GdbTest::testVariablesStartSecondSession() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesSwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); QTest::qWait(200); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); COMPARE_DATA(variableCollection()->index(2, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(2, 1, i), "1"); COMPARE_DATA(variableCollection()->index(3, 0, i), "argv"); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesQuicklySwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); QTest::qWait(300); stackModel->setCurrentFrame(0); QTest::qWait(1); stackModel->setCurrentFrame(1); QTest::qWait(1); stackModel->setCurrentFrame(0); QTest::qWait(1); stackModel->setCurrentFrame(1); QTest::qWait(500); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); QStringList locs; for (int j = 0; j < variableCollection()->rowCount(i); ++j) { locs << variableCollection()->index(j, 0, i).data().toString(); } QVERIFY(locs.contains("argc")); QVERIFY(locs.contains("argv")); QVERIFY(locs.contains("x")); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testSegfaultDebugee() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg(findExecutable("debugeecrash")); QString fileName = findSourceFile("debugeecrash.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 23); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testSwitchFrameGdbConsole() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->currentFrame(), 0); stackModel->setCurrentFrame(1); QCOMPARE(stackModel->currentFrame(), 1); QTest::qWait(500); QCOMPARE(stackModel->currentFrame(), 1); session->slotUserGDBCmd("print x"); QTest::qWait(500); //currentFrame must not reset to 0; Bug 222882 QCOMPARE(stackModel->currentFrame(), 1); } //Bug 201771 void GdbTest::testInsertAndRemoveBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeeslow")); QString fileName = findSourceFile("debugeeslow.cpp"); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); b->setDeleted(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //Bug 274390 void GdbTest::testCommandOrderFastStepping() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeeqt")); breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startProgram(&cfg, m_iface)); for(int i=0; i<20; i++) { session->stepInto(); } WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPickupManuallyInsertedBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startProgram(&cfg, m_iface)); session->addCommand(GDBMI::NonMI, "break debugee.cpp:32"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); //wait for breakpoints update QCOMPARE(breakpoints()->breakpoints().count(), 2); QCOMPARE(breakpoints()->rowCount(), 2); KDevelop::Breakpoint *b = breakpoints()->breakpoint(1); QVERIFY(b); QCOMPARE(b->line(), 31); //we start with 0, gdb with 1 QCOMPARE(b->url().fileName(), QString("debugee.cpp")); } //Bug 270970 void GdbTest::testPickupManuallyInsertedBreakpointOnlyOnce() { TestDebugSession *session = new TestDebugSession; //inject here, so it behaves similar like a command from .gdbinit QTemporaryFile configScript; configScript.open(); configScript.write(QString("file %0\n").arg(findExecutable("debugee").toLocalFile()).toLocal8Bit()); configScript.write("break debugee.cpp:32\n"); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(GDBDebugger::remoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile("debugee.cpp"), 31); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPickupCatchThrowOnlyOnce() { QTemporaryFile configScript; configScript.open(); configScript.write("catch throw\n"); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(GDBDebugger::remoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); for (int i = 0; i < 2; ++i) { TestDebugSession* session = new TestDebugSession; QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::EndedState); } QCOMPARE(breakpoints()->rowCount(), 1); //one from kdevelop, one from runScript } void GdbTest::testRunGdbScript() { TestDebugSession *session = new TestDebugSession; QTemporaryFile runScript; runScript.open(); runScript.write("file " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); runScript.write("break main\n"); runScript.write("run\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(GDBDebugger::remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testRemoteDebug() { const QString gdbserverExecutable = QStandardPaths::findExecutable("gdbserver"); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } TestDebugSession *session = new TestDebugSession; QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(GDBDebugger::remoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); grp.writeEntry(GDBDebugger::remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testRemoteDebugInsertBreakpoint() { const QString gdbserverExecutable = QStandardPaths::findExecutable("gdbserver"); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } TestDebugSession *session = new TestDebugSession; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 35); QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 " + findExecutable("debugee").toLocalFile().toUtf8() + "\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file " + findExecutable("debugee").toLocalFile().toUtf8() + '\n'); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(GDBDebugger::remoteGdbShellEntry, QUrl::fromLocalFile(shellScript.fileName()+"-copy")); grp.writeEntry(GDBDebugger::remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testRemoteDebugInsertBreakpointPickupOnlyOnce() { const QString gdbserverExecutable = QStandardPaths::findExecutable("gdbserver"); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } TestDebugSession *session = new TestDebugSession; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 35); QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 "+findExecutable("debugee").toLocalFile().toLatin1()+"\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file "+findExecutable("debugee").toLocalFile().toLatin1()+"\n"); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(GDBDebugger::remoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); grp.writeEntry(GDBDebugger::remoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //************************** second session session = new TestDebugSession; QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testBreakpointWithSpaceInPath() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeespace")); KConfigGroup grp = cfg.config(); QString fileName = findSourceFile("debugee space.cpp"); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 20); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 20); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakpointDisabledOnStart() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28) ->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 31); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 31); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testCatchpoint() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg(findExecutable("debugeeexception")); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp")), 29); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); TestFrameStackModel* fsModel = session->frameStackModel(); QCOMPARE(fsModel->currentFrame(), 0); QCOMPARE(session->line(), 29); session->addCommand(new GDBCommand(GDBMI::NonMI, "catch throw")); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); const QList frames = fsModel->frames(fsModel->currentThread()); QVERIFY(frames.size() >= 2); // frame 0 is somewhere inside libstdc++ QCOMPARE(frames[1].file, QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp"))); QCOMPARE(frames[1].line, 22); QCOMPARE(breakpoints()->rowCount(),2); QVERIFY(!breakpoints()->breakpoint(0)->location().isEmpty()); QVERIFY(!breakpoints()->breakpoint(1)->location().isEmpty()); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //TODO: figure out why do we need this test? And do we need it at all?? void GdbTest::testThreadAndFrameInfo() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeethreads")); QString fileName = findSourceFile("debugeethreads.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startProgram(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QSignalSpy outputSpy(session, SIGNAL(gdbUserCommandStdout(QString))); session->addCommand( new UserCommand(GDBMI::ThreadInfo,"")); session->addCommand(new UserCommand(GDBMI::StackListLocals, QLatin1String("0"))); QTest::qWait(1000); QCOMPARE(outputSpy.count(), 2); QVERIFY(outputSpy.last().at(0).toString().contains(QLatin1String("--thread 1"))); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::parseBug304730() { FileSymbol file; file.contents = QByteArray("^done,bkpt={" "number=\"1\",type=\"breakpoint\",disp=\"keep\",enabled=\"y\",addr=\"\",times=\"0\"," "original-location=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp:231\"}," "{number=\"1.1\",enabled=\"y\",addr=\"0x081d84aa\"," "func=\"PatchMatch, 2u> >" "::Propagation(ForwardPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}," "{number=\"1.2\",enabled=\"y\",addr=\"0x081d8ae2\"," "func=\"PatchMatch, 2u> >" "::Propagation(BackwardPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}," "{number=\"1.3\",enabled=\"y\",addr=\"0x081d911a\"," "func=\"PatchMatch, 2u> >" "::Propagation(AllowedPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}"); MIParser parser; std::unique_ptr record(parser.parse(&file)); QVERIFY(record.get() != nullptr); } void GdbTest::testMultipleLocationsBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("debugeemultilocbreakpoint")); breakpoints()->addCodeBreakpoint("aPlusB"); //TODO check if the additional location breakpoint is added session->startProgram(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 19); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBug301287() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add("argc"); QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //start second debug session (same cfg) session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(300); i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testMultipleBreakpoint() { TestDebugSession *session = new TestDebugSession; //there'll be about 3-4 breakpoints, but we treat it like one. TestLaunchConfiguration c(findExecutable("debugeemultiplebreakpoint")); KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint("debugeemultiplebreakpoint.cpp:52"); session->startProgram(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testRegularExpressionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable("debugeemultilocbreakpoint")); breakpoints()->addCodeBreakpoint("main"); session->startProgram(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); session->addCommand(new GDBCommand(GDBMI::NonMI, "rbreak .*aPl.*B")); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 3); session->addCommand(new GDBCommand(GDBMI::BreakDelete, "")); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testChangeBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable("debugeeslow")); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint("debugeeslow.cpp:25"); session->startProgram(&c, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QVERIFY(session->currentLine() >= 24 && session->currentLine() <= 26 ); session->run(); WAIT_FOR_STATE(session, DebugSession::ActiveState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //to make one loop QTest::qWait(2000); WAIT_FOR_STATE(session, DebugSession::ActiveState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testDebugInExternalTerminal() { TestLaunchConfiguration cfg; foreach (const QString & console, QStringList() << "konsole" << "xterm" << "xfce4-terminal" << "gnome-terminal") { TestDebugSession* session = 0; if (QStandardPaths::findExecutable(console).isEmpty()) { continue; } session = new TestDebugSession(); cfg.config().writeEntry("External Terminal"/*ExecutePlugin::terminalEntry*/, console); cfg.config().writeEntry("Use External Terminal"/*ExecutePlugin::useTerminalEntry*/, true); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); session->startProgram(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } } // see: https://bugs.kde.org/show_bug.cgi?id=339231 void GdbTest::testPathWithSpace() { #ifdef HAVE_PATH_WITH_SPACES_TEST TestDebugSession* session = new TestDebugSession; auto debugee = findExecutable("path with space/spacedebugee"); TestLaunchConfiguration c(debugee, KIO::upUrl(debugee)); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint("spacedebugee.cpp:30"); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startProgram(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); #endif } bool GdbTest::waitForState(GDBDebugger::DebugSession *session, DebugSession::DebuggerState state, const char *file, int line, bool waitForIdle) { QPointer s(session); //session can get deleted in DebugController QTime stopWatch; stopWatch.start(); while (s.data()->state() != state || (waitForIdle && s->stateIsOn(s_dbgBusy))) { if (stopWatch.elapsed() > 5000) { qWarning() << "current state" << s.data()->state() << "waiting for" << state; QTest::qFail(qPrintable(QString("Timeout before reaching state %0").arg(state)), file, line); return false; } QTest::qWait(20); if (!s) { if (state == DebugSession::EndedState) break; QTest::qFail(qPrintable(QString("Session ended before reaching state %0").arg(state)), file, line); return false; } } if (!waitForIdle && state != DebugSession::EndedState) { // legacy behavior for tests that implicitly may require waiting for idle, // but which were written before waitForIdle was added QTest::qWait(100); } qDebug() << "Reached state " << state << " in " << file << ':' << line; return true; } } QTEST_MAIN(GDBDebugger::GdbTest) #include "test_gdb.moc" #include "moc_test_gdb.cpp"