diff options
Diffstat (limited to 'languages/cpp/debugger/debuggerpart.cpp')
-rw-r--r-- | languages/cpp/debugger/debuggerpart.cpp | 1272 |
1 files changed, 1272 insertions, 0 deletions
diff --git a/languages/cpp/debugger/debuggerpart.cpp b/languages/cpp/debugger/debuggerpart.cpp new file mode 100644 index 00000000..c8c3c1a1 --- /dev/null +++ b/languages/cpp/debugger/debuggerpart.cpp @@ -0,0 +1,1272 @@ +/*************************************************************************** + * Copyright (C) 1999-2001 by John Birch * + * [email protected] * + * Copyright (C) 2001 by Bernd Gehrmann * + * [email protected] * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "debuggerpart.h" +#include "label_with_double_click.h" + +#include <qdir.h> +#include <qvbox.h> +#include <qwhatsthis.h> +#include <qpopupmenu.h> +#include <qtooltip.h> + +#include <kaction.h> +#include <kdebug.h> +#include <kfiledialog.h> +#include <kdevgenericfactory.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kmainwindow.h> +#include <kstatusbar.h> +#include <kparts/part.h> +#include <ktexteditor/viewcursorinterface.h> +#include <kmessagebox.h> +#include <kapplication.h> +#include <dcopclient.h> +#include <qtimer.h> +#include <kstringhandler.h> +#include <kdockwidget.h> + +#include "kdevcore.h" +#include "kdevproject.h" +#include "kdevmainwindow.h" +#include "kdevappfrontend.h" +#include "kdevpartcontroller.h" +#include "kdevdebugger.h" +#include "domutil.h" +#include "variablewidget.h" +#include "gdbbreakpointwidget.h" +#include "framestackwidget.h" +#include "disassemblewidget.h" +#include "processwidget.h" +#include "gdbcontroller.h" +#include "breakpoint.h" +#include "dbgpsdlg.h" +#include "dbgtoolbar.h" +#include "memviewdlg.h" +#include "gdbparser.h" +#include "gdboutputwidget.h" +#include "debuggerconfigwidget.h" +#include "processlinemaker.h" + +#include <iostream> + +#include <kdevplugininfo.h> +#include <debugger.h> + + + + + +namespace GDBDebugger +{ + +static const KDevPluginInfo data("kdevdebugger"); + +typedef KDevGenericFactory<DebuggerPart> DebuggerFactory; +K_EXPORT_COMPONENT_FACTORY( libkdevdebugger, DebuggerFactory( data ) ) + +DebuggerPart::DebuggerPart( QObject *parent, const char *name, const QStringList & ) : + KDevPlugin( &data, parent, name ? name : "DebuggerPart" ), + controller(0), previousDebuggerState_(s_dbgNotStarted), + justRestarted_(false), needRebuild_(true), + running_(false) +{ + setObjId("DebuggerInterface"); + setInstance(DebuggerFactory::instance()); + + setXMLFile("kdevdebugger.rc"); + + m_debugger = new Debugger( partController() ); + + statusBarIndicator = new LabelWithDoubleClick( + " ", mainWindow()->statusBar()); + statusBarIndicator->setFixedWidth(15); + statusBarIndicator->setAlignment(Qt::AlignCenter); + mainWindow()->statusBar()->addWidget(statusBarIndicator, 0, true); + statusBarIndicator->show(); + + // Setup widgets and dbgcontroller + + controller = new GDBController(*projectDom()); + + + gdbBreakpointWidget = new GDBBreakpointWidget( controller, + 0, "gdbBreakpointWidget" ); + gdbBreakpointWidget->setCaption(i18n("Breakpoint List")); + QWhatsThis::add + (gdbBreakpointWidget, i18n("<b>Breakpoint list</b><p>" + "Displays a list of breakpoints with " + "their current status. Clicking on a " + "breakpoint item allows you to change " + "the breakpoint and will take you " + "to the source in the editor window.")); + gdbBreakpointWidget->setIcon( SmallIcon("stop") ); + mainWindow()->embedOutputView(gdbBreakpointWidget, i18n("Breakpoints"), i18n("Debugger breakpoints")); + + variableWidget = new VariableWidget( controller, + gdbBreakpointWidget, + 0, "variablewidget"); + mainWindow()->embedSelectView(variableWidget, i18n("Variables"), + i18n("Debugger variable-view")); + mainWindow()->setViewAvailable(variableWidget, false); + + framestackWidget = new FramestackWidget( controller, 0, "framestackWidget" ); + framestackWidget->setEnabled(false); + framestackWidget->setCaption(i18n("Frame Stack")); + QWhatsThis::add + (framestackWidget, i18n("<b>Frame stack</b><p>" + "Often referred to as the \"call stack\", " + "this is a list showing what function is " + "currently active and who called each " + "function to get to this point in your " + "program. By clicking on an item you " + "can see the values in any of the " + "previous calling functions.")); + framestackWidget->setIcon( SmallIcon("table") ); + mainWindow()->embedOutputView(framestackWidget, i18n("Frame Stack"), i18n("Debugger function call stack")); + mainWindow()->setViewAvailable(framestackWidget, false); + + disassembleWidget = new DisassembleWidget( controller, 0, "disassembleWidget" ); + disassembleWidget->setEnabled(false); + disassembleWidget->setCaption(i18n("Machine Code Display")); + QWhatsThis::add + (disassembleWidget, i18n("<b>Machine code display</b><p>" + "A machine code view into your running " + "executable with the current instruction " + "highlighted. You can step instruction by " + "instruction using the debuggers toolbar " + "buttons of \"step over\" instruction and " + "\"step into\" instruction.")); + disassembleWidget->setIcon( SmallIcon("gear") ); + mainWindow()->embedOutputView(disassembleWidget, i18n("Disassemble"), + i18n("Debugger disassemble view")); + mainWindow()->setViewAvailable(disassembleWidget, false); + + gdbOutputWidget = new GDBOutputWidget( 0, "gdbOutputWidget" ); + gdbOutputWidget->setEnabled(false); + gdbOutputWidget->setIcon( SmallIcon("inline_image") ); + gdbOutputWidget->setCaption(i18n("GDB Output")); + QWhatsThis::add + (gdbOutputWidget, i18n("<b>GDB output</b><p>" + "Shows all gdb commands being executed. " + "You can also issue any other gdb command while debugging.")); + mainWindow()->embedOutputView(gdbOutputWidget, i18n("GDB"), + i18n("GDB output")); + mainWindow()->setViewAvailable(gdbOutputWidget, false); + + // gdbBreakpointWidget -> this + connect( gdbBreakpointWidget, SIGNAL(refreshBPState(const Breakpoint&)), + this, SLOT(slotRefreshBPState(const Breakpoint&))); + connect( gdbBreakpointWidget, SIGNAL(publishBPState(const Breakpoint&)), + this, SLOT(slotRefreshBPState(const Breakpoint&))); + connect( gdbBreakpointWidget, SIGNAL(gotoSourcePosition(const QString&, int)), + this, SLOT(slotGotoSource(const QString&, int)) ); + + + viewerWidget = new ViewerWidget( controller, 0, "viewerWidget"); + mainWindow()->embedSelectView(viewerWidget, + i18n("Debug views"), + i18n("Special debugger views")); + mainWindow()->setViewAvailable(viewerWidget, false); + connect(viewerWidget, SIGNAL(setViewShown(bool)), + this, SLOT(slotShowView(bool))); + + // Now setup the actions + KAction *action; + +// action = new KAction(i18n("&Start"), "1rightarrow", CTRL+SHIFT+Key_F9, + action = new KAction(i18n("&Start"), "dbgrun", Key_F9, + this, SLOT(slotRun()), + actionCollection(), "debug_run"); + action->setToolTip( i18n("Start in debugger") ); + action->setWhatsThis( i18n("<b>Start in debugger</b><p>" + "Starts the debugger with the project's main " + "executable. You may set some breakpoints " + "before this, or you can interrupt the program " + "while it is running, in order to get information " + "about variables, frame stack, and so on.") ); + + action = new KAction(i18n("&Restart"), "dbgrestart", 0, + this, SLOT(slotRestart()), + actionCollection(), "debug_restart"); + action->setToolTip( i18n("Restart program") ); + action->setWhatsThis( i18n("<b>Restarts application</b><p>" + "Restarts applications from the beginning." + ) ); + action->setEnabled(false); + + + action = new KAction(i18n("Sto&p"), "stop", 0, + this, SLOT(slotStop()), + actionCollection(), "debug_stop"); + action->setToolTip( i18n("Stop debugger") ); + action->setWhatsThis(i18n("<b>Stop debugger</b><p>Kills the executable and exits the debugger.")); + + action = new KAction(i18n("Interrupt"), "player_pause", 0, + this, SLOT(slotPause()), + actionCollection(), "debug_pause"); + action->setToolTip( i18n("Interrupt application") ); + action->setWhatsThis(i18n("<b>Interrupt application</b><p>Interrupts the debugged process or current GDB command.")); + + action = new KAction(i18n("Run to &Cursor"), "dbgrunto", 0, + this, SLOT(slotRunToCursor()), + actionCollection(), "debug_runtocursor"); + action->setToolTip( i18n("Run to cursor") ); + action->setWhatsThis(i18n("<b>Run to cursor</b><p>Continues execution until the cursor position is reached.")); + + + action = new KAction(i18n("Set E&xecution Position to Cursor"), "dbgjumpto", 0, + this, SLOT(slotJumpToCursor()), + actionCollection(), "debug_jumptocursor"); + action->setToolTip( i18n("Jump to cursor") ); + action->setWhatsThis(i18n("<b>Set Execution Position </b><p>Set the execution pointer to the current cursor position.")); + + + action = new KAction(i18n("Step &Over"), "dbgnext", Key_F10, + this, SLOT(slotStepOver()), + actionCollection(), "debug_stepover"); + action->setToolTip( i18n("Step over the next line") ); + action->setWhatsThis( i18n("<b>Step over</b><p>" + "Executes one line of source in the current source file. " + "If the source line is a call to a function the whole " + "function is executed and the app will stop at the line " + "following the function call.") ); + + + action = new KAction(i18n("Step over Ins&truction"), "dbgnextinst", 0, + this, SLOT(slotStepOverInstruction()), + actionCollection(), "debug_stepoverinst"); + action->setToolTip( i18n("Step over instruction") ); + action->setWhatsThis(i18n("<b>Step over instruction</b><p>Steps over the next assembly instruction.")); + + + action = new KAction(i18n("Step &Into"), "dbgstep", Key_F11, + this, SLOT(slotStepInto()), + actionCollection(), "debug_stepinto"); + action->setToolTip( i18n("Step into the next statement") ); + action->setWhatsThis( i18n("<b>Step into</b><p>" + "Executes exactly one line of source. If the source line " + "is a call to a function then execution will stop after " + "the function has been entered.") ); + + + action = new KAction(i18n("Step into I&nstruction"), "dbgstepinst", 0, + this, SLOT(slotStepIntoInstruction()), + actionCollection(), "debug_stepintoinst"); + action->setToolTip( i18n("Step into instruction") ); + action->setWhatsThis(i18n("<b>Step into instruction</b><p>Steps into the next assembly instruction.")); + + + action = new KAction(i18n("Step O&ut"), "dbgstepout", Key_F12, + this, SLOT(slotStepOut()), + actionCollection(), "debug_stepout"); + action->setToolTip( i18n("Steps out of the current function") ); + action->setWhatsThis( i18n("<b>Step out</b><p>" + "Executes the application until the currently executing " + "function is completed. The debugger will then display " + "the line after the original call to that function. If " + "program execution is in the outermost frame (i.e. in " + "main()) then this operation has no effect.") ); + + + action = new KAction(i18n("Viewers"), "dbgmemview", 0, + this, SLOT(slotMemoryView()), + actionCollection(), "debug_memview"); + action->setToolTip( i18n("Debugger viewers") ); + action->setWhatsThis(i18n("<b>Debugger viewers</b><p>Various information about application being executed. There are 4 views available:<br>" + "<b>Memory</b><br>" + "<b>Disassemble</b><br>" + "<b>Registers</b><br>" + "<b>Libraries</b>")); + + + action = new KAction(i18n("Examine Core File..."), "core", 0, + this, SLOT(slotExamineCore()), + actionCollection(), "debug_core"); + action->setToolTip( i18n("Examine core file") ); + action->setWhatsThis( i18n("<b>Examine core file</b><p>" + "This loads a core file, which is typically created " + "after the application has crashed, e.g. with a " + "segmentation fault. The core file contains an " + "image of the program memory at the time it crashed, " + "allowing you to do a post-mortem analysis.") ); + + + action = new KAction(i18n("Attach to Process"), "connect_creating", 0, + this, SLOT(slotAttachProcess()), + actionCollection(), "debug_attach"); + action->setToolTip( i18n("Attach to process") ); + action->setWhatsThis(i18n("<b>Attach to process</b><p>Attaches the debugger to a running process.")); + + action = new KAction(i18n("Toggle Breakpoint"), 0, 0, + this, SLOT(toggleBreakpoint()), + actionCollection(), "debug_toggle_breakpoint"); + action->setToolTip(i18n("Toggle breakpoint")); + action->setWhatsThis(i18n("<b>Toggle breakpoint</b><p>Toggles the breakpoint at the current line in editor.")); + + connect( mainWindow()->main()->guiFactory(), SIGNAL(clientAdded(KXMLGUIClient*)), + this, SLOT(guiClientAdded(KXMLGUIClient*)) ); + + connect( core(), SIGNAL(projectConfigWidget(KDialogBase*)), + this, SLOT(projectConfigWidget(KDialogBase*)) ); + + connect( partController(), SIGNAL(loadedFile(const KURL &)), + gdbBreakpointWidget, SLOT(slotRefreshBP(const KURL &)) ); + connect( debugger(), SIGNAL(toggledBreakpoint(const QString &, int)), + gdbBreakpointWidget, SLOT(slotToggleBreakpoint(const QString &, int)) ); + connect( debugger(), SIGNAL(editedBreakpoint(const QString &, int)), + gdbBreakpointWidget, SLOT(slotEditBreakpoint(const QString &, int)) ); + connect( debugger(), SIGNAL(toggledBreakpointEnabled(const QString &, int)), + gdbBreakpointWidget, SLOT(slotToggleBreakpointEnabled(const QString &, int)) ); + + connect( core(), SIGNAL(contextMenu(QPopupMenu *, const Context *)), + this, SLOT(contextMenu(QPopupMenu *, const Context *)) ); + + connect( core(), SIGNAL(stopButtonClicked(KDevPlugin*)), + this, SLOT(slotStop(KDevPlugin*)) ); + connect( core(), SIGNAL(projectClosed()), + this, SLOT(projectClosed()) ); + + connect( partController(), SIGNAL(activePartChanged(KParts::Part*)), + this, SLOT(slotActivePartChanged(KParts::Part*)) ); + + procLineMaker = new ProcessLineMaker(); + + connect( procLineMaker, SIGNAL(receivedStdoutLine(const QCString&)), + appFrontend(), SLOT(insertStdoutLine(const QCString&)) ); + connect( procLineMaker, SIGNAL(receivedStderrLine(const QCString&)), + appFrontend(), SLOT(insertStderrLine(const QCString&)) ); + + connect( procLineMaker, SIGNAL(receivedPartialStdoutLine(const QCString&)), + appFrontend(), SLOT(addPartialStdoutLine(const QCString&))); + connect( procLineMaker, SIGNAL(receivedPartialStderrLine(const QCString&)), + appFrontend(), SLOT(addPartialStderrLine(const QCString&))); + + // The output from tracepoints goes to "application" window, because + // we don't have any better alternative, and using yet another window + // is undesirable. Besides, this makes tracepoint look even more similar + // to printf debugging. + connect( gdbBreakpointWidget, SIGNAL(tracingOutput(const char*)), + procLineMaker, SLOT(slotReceivedStdout(const char*))); + + + connect(partController(), SIGNAL(savedFile(const KURL &)), + this, SLOT(slotFileSaved())); + + if (project()) + connect(project(), SIGNAL(projectCompiled()), + this, SLOT(slotProjectCompiled())); + + setupController(); + QTimer::singleShot(0, this, SLOT(setupDcop())); +} + +void DebuggerPart::setupDcop() +{ + QCStringList objects = kapp->dcopClient()->registeredApplications(); + for (QCStringList::Iterator it = objects.begin(); it != objects.end(); ++it) + if ((*it).find("drkonqi-") == 0) + slotDCOPApplicationRegistered(*it); + + connect(kapp->dcopClient(), SIGNAL(applicationRegistered(const QCString&)), SLOT(slotDCOPApplicationRegistered(const QCString&))); + kapp->dcopClient()->setNotifications(true); +} + +void DebuggerPart::slotDCOPApplicationRegistered(const QCString& appId) +{ + if (appId.find("drkonqi-") == 0) { + QByteArray answer; + QCString replyType; + + kapp->dcopClient()->call(appId, "krashinfo", "appName()", QByteArray(), replyType, answer, true, 5000); + + QDataStream d(answer, IO_ReadOnly); + QCString appName; + d >> appName; + + if (appName.length() && project() && project()->mainProgram().endsWith(appName)) { + kapp->dcopClient()->send(appId, "krashinfo", "registerDebuggingApplication(QString)", i18n("Debug in &KDevelop")); + connectDCOPSignal(appId, "krashinfo", "acceptDebuggingApplication()", "slotDebugExternalProcess()", true); + } + } +} + +ASYNC DebuggerPart::slotDebugExternalProcess() +{ + QByteArray answer; + QCString replyType; + + kapp->dcopClient()->call(kapp->dcopClient()->senderId(), "krashinfo", "pid()", QByteArray(), replyType, answer, true, 5000); + + QDataStream d(answer, IO_ReadOnly); + int pid; + d >> pid; + + if (attachProcess(pid) && m_drkonqi.isEmpty()) { + m_drkonqi = kapp->dcopClient()->senderId(); + QTimer::singleShot(15000, this, SLOT(slotCloseDrKonqi())); + mainWindow()->raiseView(framestackWidget); + } + + mainWindow()->main()->raise(); +} + +ASYNC DebuggerPart::slotDebugCommandLine(const QString& /*command*/) +{ + KMessageBox::information(0, "Asked to debug command line"); +} + +void DebuggerPart::slotCloseDrKonqi() +{ + kapp->dcopClient()->send(m_drkonqi, "MainApplication-Interface", "quit()", QByteArray()); + m_drkonqi = ""; +} + +DebuggerPart::~DebuggerPart() +{ + kapp->dcopClient()->setNotifications(false); + + if (variableWidget) + mainWindow()->removeView(variableWidget); + if (gdbBreakpointWidget) + mainWindow()->removeView(gdbBreakpointWidget); + if (framestackWidget) + mainWindow()->removeView(framestackWidget); + if (disassembleWidget) + mainWindow()->removeView(disassembleWidget); + if(gdbOutputWidget) + mainWindow()->removeView(gdbOutputWidget); + + delete variableWidget; + delete gdbBreakpointWidget; + delete framestackWidget; + delete disassembleWidget; + delete gdbOutputWidget; + delete controller; + delete floatingToolBar; + delete statusBarIndicator; + delete procLineMaker; + + GDBParser::destroy(); +} + + +void DebuggerPart::guiClientAdded( KXMLGUIClient* client ) +{ + // Can't change state until after XMLGUI has been loaded... + // Anyone know of a better way of doing this? + if( client == this ) + stateChanged( QString("stopped") ); +} + +void DebuggerPart::contextMenu(QPopupMenu *popup, const Context *context) +{ + if (!context->hasType( Context::EditorContext )) + return; + + const EditorContext *econtext = static_cast<const EditorContext*>(context); + m_contextIdent = econtext->currentWord(); + + bool running = !(previousDebuggerState_ & s_dbgNotStarted); + + // If debugger is running, we insert items at the top. + // The reason is user has explicitly run the debugger, so he's + // surely debugging, not editing code or something. So, first + // menu items should be about debugging, not some copy/paste/cut + // things. + if (!running) + popup->insertSeparator(); + + int index = running ? 0 : -1; + if (running) + { + // Too bad we can't add QAction to popup menu in Qt3. + KAction* act = actionCollection()->action("debug_runtocursor"); + Q_ASSERT(act); + if (act) + { + int id = popup->insertItem( act->iconSet(), i18n("Run to &Cursor"), + this, SLOT(slotRunToCursor()), + 0, -1, index); + + popup->setWhatsThis(id, act->whatsThis()); + index += running; + } + } + if (econtext->url().isLocalFile()) + { + int id = popup->insertItem( i18n("Toggle Breakpoint"), + this, SLOT(toggleBreakpoint()), + 0, -1, index); + index += running; + popup->setWhatsThis(id, i18n("<b>Toggle breakpoint</b><p>Toggles breakpoint at the current line.")); + } + if (!m_contextIdent.isEmpty()) + { + QString squeezed = KStringHandler::csqueeze(m_contextIdent, 30); + int id = popup->insertItem( i18n("Evaluate: %1").arg(squeezed), + this, SLOT(contextEvaluate()), + 0, -1, index); + index += running; + popup->setWhatsThis(id, i18n("<b>Evaluate expression</b><p>Shows the value of the expression under the cursor.")); + int id2 = popup->insertItem( i18n("Watch: %1").arg(squeezed), + this, SLOT(contextWatch()), + 0, -1, index); + index += running; + popup->setWhatsThis(id2, i18n("<b>Watch expression</b><p>Adds an expression under the cursor to the Variables/Watch list.")); + } + if (running) + popup->insertSeparator(index); +} + + +void DebuggerPart::toggleBreakpoint() +{ + KParts::ReadWritePart *rwpart + = dynamic_cast<KParts::ReadWritePart*>(partController()->activePart()); + KTextEditor::ViewCursorInterface *cursorIface + = dynamic_cast<KTextEditor::ViewCursorInterface*>(partController()->activeWidget()); + + if (!rwpart || !cursorIface) + return; + + uint line, col; + cursorIface->cursorPositionReal(&line, &col); + + gdbBreakpointWidget->slotToggleBreakpoint(rwpart->url().path(), line); +} + + +void DebuggerPart::contextWatch() +{ + variableWidget->slotAddWatchVariable(m_contextIdent); +} + +void DebuggerPart::contextEvaluate() +{ + variableWidget->slotEvaluateExpression(m_contextIdent); +} + +void DebuggerPart::projectConfigWidget(KDialogBase *dlg) +{ + QVBox *vbox = dlg->addVBoxPage(i18n("Debugger"), i18n("Debugger"), BarIcon( info()->icon(), KIcon::SizeMedium) ); + DebuggerConfigWidget *w = new DebuggerConfigWidget(this, vbox, "debugger config widget"); + connect( dlg, SIGNAL(okClicked()), w, SLOT(accept()) ); + connect( dlg, SIGNAL(finished()), controller, SLOT(configure()) ); +} + + +void DebuggerPart::setupController() +{ + VariableTree *variableTree = variableWidget->varTree(); + + // variableTree -> gdbBreakpointWidget + connect( variableTree, SIGNAL(toggleWatchpoint(const QString &)), + gdbBreakpointWidget, SLOT(slotToggleWatchpoint(const QString &))); + + // gdbOutputWidget -> controller + connect( gdbOutputWidget, SIGNAL(userGDBCmd(const QString &)), + controller, SLOT(slotUserGDBCmd(const QString&))); + connect( gdbOutputWidget, SIGNAL(breakInto()), + controller, SLOT(slotBreakInto())); + + connect( controller, SIGNAL(breakpointHit(int)), + gdbBreakpointWidget, SLOT(slotBreakpointHit(int))); + + // controller -> disassembleWidget + connect( controller, SIGNAL(showStepInSource(const QString&, int, const QString&)), + disassembleWidget, SLOT(slotShowStepInSource(const QString&, int, const QString&))); + + // controller -> this + connect( controller, SIGNAL(dbgStatus(const QString&, int)), + this, SLOT(slotStatus(const QString&, int))); + connect( controller, SIGNAL(showStepInSource(const QString&, int, const QString&)), + this, SLOT(slotShowStep(const QString&, int))); + connect( controller, SIGNAL(debuggerAbnormalExit()), + this, SLOT(slotDebuggerAbnormalExit())); + + connect(controller, SIGNAL(event(GDBController::event_t)), + this, SLOT(slotEvent(GDBController::event_t))); + + // controller -> procLineMaker + connect( controller, SIGNAL(ttyStdout(const char*)), + procLineMaker, SLOT(slotReceivedStdout(const char*))); + connect( controller, SIGNAL(ttyStderr(const char*)), + procLineMaker, SLOT(slotReceivedStderr(const char*))); + + // controller -> gdbOutputWidget + connect( controller, SIGNAL(gdbInternalCommandStdout(const char*)), + gdbOutputWidget, SLOT(slotInternalCommandStdout(const char*)) ); + connect( controller, SIGNAL(gdbUserCommandStdout(const char*)), + gdbOutputWidget, SLOT(slotUserCommandStdout(const char*)) ); + + connect( controller, SIGNAL(gdbStderr(const char*)), + gdbOutputWidget, SLOT(slotReceivedStderr(const char*)) ); + connect( controller, SIGNAL(dbgStatus(const QString&, int)), + gdbOutputWidget, SLOT(slotDbgStatus(const QString&, int))); + + // controller -> viewerWidget + connect( controller, SIGNAL(dbgStatus(const QString&, int)), + viewerWidget, SLOT(slotDebuggerState(const QString&, int))); + + + connect(statusBarIndicator, SIGNAL(doubleClicked()), + controller, SLOT(explainDebuggerStatus())); + +} + + +bool DebuggerPart::startDebugger() +{ + QString build_dir; // Currently selected build directory + DomUtil::PairList run_envvars; // List with the environment variables + QString run_directory; // Directory from where the program should be run + QString program; // Absolute path to application + QString run_arguments; // Command line arguments to be passed to the application + + if (project()) { + build_dir = project()->buildDirectory(); + run_envvars = project()->runEnvironmentVars(); + run_directory = project()->runDirectory(); + program = project()->mainProgram(); + run_arguments = project()->debugArguments(); + } + + QString shell = DomUtil::readEntry(*projectDom(), "/kdevdebugger/general/dbgshell"); + if( !shell.isEmpty() ) + { + shell = shell.simplifyWhiteSpace(); + QString shell_without_args = QStringList::split(QChar(' '), shell ).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( + mainWindow()->main(), + i18n("Could not locate the debugging shell '%1'.").arg( shell_without_args ), + i18n("Debugging Shell Not Found"), "gdb_error" ); + return false; + } + } + + if (controller->start(shell, run_envvars, run_directory, + program, run_arguments)) + { + core()->running(this, true); + + stateChanged( QString("active") ); + + KActionCollection *ac = actionCollection(); + ac->action("debug_run")->setText( i18n("&Continue") ); + + ac->action("debug_run")->setToolTip( + i18n("Continues the application execution") ); + ac->action("debug_run")->setWhatsThis( + i18n("Continue application execution\n\n" + "Continues the execution of your application in the " + "debugger. This only takes effect when the application " + "has been halted by the debugger (i.e. a breakpoint has " + "been activated or the interrupt was pressed).") ); + + mainWindow()->setViewAvailable(framestackWidget, true); + mainWindow()->setViewAvailable(disassembleWidget, true); + mainWindow()->setViewAvailable(gdbOutputWidget, true); + mainWindow()->setViewAvailable(variableWidget, true); + + framestackWidget->setEnabled(true); + disassembleWidget->setEnabled(true); + + gdbOutputWidget->setEnabled(true); + + + if (DomUtil::readBoolEntry(*projectDom(), "/kdevdebugger/general/floatingtoolbar", false)) + { +#ifndef QT_MAC + floatingToolBar = new DbgToolBar(this, mainWindow()->main()); + floatingToolBar->show(); +#endif + } + + running_ = true; + return true; + } + else + { + return false; + } +} + +void DebuggerPart::slotStopDebugger() +{ + running_ = false; + controller->slotStopDebugger(); + debugger()->clearExecutionPoint(); + + delete floatingToolBar; + floatingToolBar = 0; + + gdbBreakpointWidget->reset(); + disassembleWidget->clear(); + gdbOutputWidget->clear(); + disassembleWidget->slotActivate(false); + +// variableWidget->setEnabled(false); + framestackWidget->setEnabled(false); + disassembleWidget->setEnabled(false); + gdbOutputWidget->setEnabled(false); + + + mainWindow()->setViewAvailable(variableWidget, false); + mainWindow()->setViewAvailable(framestackWidget, false); + mainWindow()->setViewAvailable(disassembleWidget, false); + mainWindow()->setViewAvailable(gdbOutputWidget, false); + + KActionCollection *ac = actionCollection(); + ac->action("debug_run")->setText( i18n("&Start") ); +// ac->action("debug_run")->setIcon( "1rightarrow" ); + ac->action("debug_run")->setToolTip( i18n("Runs the program in the debugger") ); + ac->action("debug_run")->setWhatsThis( i18n("Start in debugger\n\n" + "Starts the debugger with the project's main " + "executable. You may set some breakpoints " + "before this, or you can interrupt the program " + "while it is running, in order to get information " + "about variables, frame stack, and so on.") ); + + stateChanged( QString("stopped") ); + + core()->running(this, false); +} + +void DebuggerPart::slotShowView(bool show) +{ + const QWidget* s = static_cast<const QWidget*>(sender()); + QWidget* ncs = const_cast<QWidget*>(s); + mainWindow()->setViewAvailable(ncs, show); + if (show) + mainWindow()->raiseView(ncs); +} + +void DebuggerPart::slotDebuggerAbnormalExit() +{ + mainWindow()->raiseView(gdbOutputWidget); + + KMessageBox::information( + mainWindow()->main(), + i18n("<b>GDB exited abnormally</b>" + "<p>This is likely a bug in GDB. " + "Examine the gdb output window and then stop the debugger"), + i18n("GDB exited abnormally"), "gdb_error"); + + // 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. +} + +void DebuggerPart::slotFileSaved() +{ + needRebuild_ = true; +} + +void DebuggerPart::slotProjectCompiled() +{ + needRebuild_ = false; +} + +void DebuggerPart::projectClosed() +{ + slotStopDebugger(); +} + +void DebuggerPart::slotRun() +{ + if( controller->stateIsOn( s_dbgNotStarted ) || + controller->stateIsOn( s_appNotStarted ) ) + { + if (running_ && controller->stateIsOn(s_dbgNotStarted)) + { + // User has already run the debugger, but it's not running. + // Most likely, the debugger has crashed, and the debuggerpart + // was left in 'running' state so that the user can examine + // gdb output or something. But now, need to fully shut down + // previous debug session. + slotStopDebugger(); + } + + // We're either starting gdb for the first time, + // or starting the application under gdb. In both + // cases, might need to rebuild the application. + + // Note that this logic somewhat duplicates the + // isDirty method present in a number of project plugins. + // But there, it's a private method we can't conveniently + // access. Besides, the custom makefiles project manager won't + // care about a file unless it's explicitly added, so it can + // miss dependencies. + + needRebuild_ |= haveModifiedFiles(); + + bool rebuild = false; + if (needRebuild_ && project()) + { + // We don't add "Don't ask again" checkbox to the + // message because it's not clear if one cooked + // decision will be right for all cases when we're starting + // debugging with modified code, and because it's not clear + // how user can reset this "don't ask again" setting. + int r = KMessageBox::questionYesNoCancel( + 0, + "<b>" + i18n("Rebuild the project?") + "</b>" + + i18n("<p>The project is out of date. Rebuild it?"), + i18n("Rebuild the project?")); + if (r == KMessageBox::Cancel) + { + return; + } + if (r == KMessageBox::Yes) + { + rebuild = true; + } + else + { + // If the user said don't rebuild, try to avoid + // asking the same question again. + // Note that this only affects 'were any files changed' + // check, if a file is changed but not saved we'll + // still ask the user again. That's bad, but I don't know + // a better solution -- it's hard to check that + // the file has the same content as it had when the user + // last answered 'no, don't rebuild'. + needRebuild_ = false; + } + + if (rebuild) + { + disconnect(SIGNAL(buildProject())); + // The KDevProject has no method to build the project, + // so try connecting to a slot has is present to all + // existing project managers. + // Note: this assumes that 'slotBuild' will save + // modified files. + + if (connect(this, SIGNAL(buildProject()), + project(), SLOT(slotBuild()))) + { + connect(project(), SIGNAL(projectCompiled()), + this, SLOT(slotRun_part2())); + + emit buildProject(); + rebuild = true; + } + } + } + if (!rebuild) + { + slotRun_part2(); + } + return; + } + else + { + // When continuing the program, don't try to rebuild -- user + // has explicitly asked to "continue". + mainWindow()->statusBar()->message(i18n("Continuing program"), 1000); + } + controller->slotRun(); +} + +void DebuggerPart::slotRun_part2() +{ + needRebuild_ = false; + + disconnect(project(), SIGNAL(projectCompiled()), + this, SLOT(slotRun_part2())); + + if (controller->stateIsOn( s_dbgNotStarted )) + { + mainWindow()->statusBar()->message(i18n("Debugging program"), 1000); + if ( DomUtil::readBoolEntry( *projectDom(), "/kdevdebugger/general/raiseGDBOnStart", false ) ) + { + mainWindow()->raiseView( gdbOutputWidget ); + }else + { + mainWindow()->raiseView( framestackWidget ); + } + appFrontend()->clearView(); + startDebugger(); + } + else if (controller->stateIsOn( s_appNotStarted ) ) + { + KActionCollection *ac = actionCollection(); + ac->action("debug_run")->setText( i18n("&Continue") ); + ac->action("debug_run")->setToolTip( i18n("Continues the application execution") ); + ac->action("debug_run")->setWhatsThis( i18n("Continue application execution\n\n" + "Continues the execution of your application in the " + "debugger. This only takes effect when the application " + "has been halted by the debugger (i.e. a breakpoint has " + "been activated or the interrupt was pressed).") ); + + mainWindow()->statusBar()->message(i18n("Running program"), 1000); + + appFrontend()->clearView(); + } + + controller->slotRun(); +} + + +void DebuggerPart::slotRestart() +{ + // 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. + controller->slotKill(); + slotRun(); +} + +void DebuggerPart::slotExamineCore() +{ + mainWindow()->statusBar()->message(i18n("Choose a core file to examine..."), 1000); + + QString dirName = project()? project()->projectDirectory() : QDir::homeDirPath(); + QString coreFile = KFileDialog::getOpenFileName(dirName); + if (coreFile.isNull()) + return; + + mainWindow()->statusBar()->message(i18n("Examining core file %1").arg(coreFile), 1000); + + startDebugger(); + controller->slotCoreFile(coreFile); +} + + +void DebuggerPart::slotAttachProcess() +{ + mainWindow()->statusBar()->message(i18n("Choose a process to attach to..."), 1000); + + Dbg_PS_Dialog dlg; + if (!dlg.exec() || !dlg.pidSelected()) + return; + + int pid = dlg.pidSelected(); + attachProcess(pid); +} + +bool DebuggerPart::attachProcess(int pid) +{ + mainWindow()->statusBar()->message(i18n("Attaching to process %1").arg(pid), 1000); + + bool ret = startDebugger(); + controller->slotAttachTo(pid); + return ret; +} + + +void DebuggerPart::slotStop(KDevPlugin* which) +{ + if( which != 0 && which != this ) + return; + +// if( !controller->stateIsOn( s_dbgNotStarted ) && !controller->stateIsOn( s_shuttingDown ) ) + slotStopDebugger(); +} + + +void DebuggerPart::slotPause() +{ + controller->slotBreakInto(); +} + + +void DebuggerPart::slotRunToCursor() +{ + KParts::ReadWritePart *rwpart + = dynamic_cast<KParts::ReadWritePart*>(partController()->activePart()); + KTextEditor::ViewCursorInterface *cursorIface + = dynamic_cast<KTextEditor::ViewCursorInterface*>(partController()->activeWidget()); + + if (!rwpart || !rwpart->url().isLocalFile() || !cursorIface) + return; + + uint line, col; + cursorIface->cursorPosition(&line, &col); + + controller->slotRunUntil(rwpart->url().path(), ++line); +} + +void DebuggerPart::slotJumpToCursor() +{ + KParts::ReadWritePart *rwpart + = dynamic_cast<KParts::ReadWritePart*>(partController()->activePart()); + KTextEditor::ViewCursorInterface *cursorIface + = dynamic_cast<KTextEditor::ViewCursorInterface*>(partController()->activeWidget()); + + if (!rwpart || !rwpart->url().isLocalFile() || !cursorIface) + return; + + uint line, col; + cursorIface->cursorPositionReal(&line, &col); + + controller->slotJumpTo(rwpart->url().path(), ++line); +} + +void DebuggerPart::slotStepOver() +{ + controller->slotStepOver(); +} + + +void DebuggerPart::slotStepOverInstruction() +{ + controller->slotStepOverIns(); +} + + +void DebuggerPart::slotStepIntoInstruction() +{ + controller->slotStepIntoIns(); +} + + +void DebuggerPart::slotStepInto() +{ + controller->slotStepInto(); +} + + +void DebuggerPart::slotStepOut() +{ + controller->slotStepOutOff(); +} + + +void DebuggerPart::slotMemoryView() +{ + viewerWidget->slotAddMemoryView(); +} + +void DebuggerPart::slotRefreshBPState( const Breakpoint& BP) +{ + if (BP.hasFileAndLine()) + { + const FilePosBreakpoint& bp = dynamic_cast<const FilePosBreakpoint&>(BP); + if (bp.isActionDie()) + { + debugger()->setBreakpoint(bp.fileName(), bp.lineNum()-1, -1, true, false); + } + else if (bp.isActionClear()) + { + // Do nothing. This is always a result of breakpoint deletion, + // either via click on gutter, or via breakpoints window. + // We should not add marker for a breakpoint that's being deleted, + // because if user removes marker, and we re-add it here until + // we see 'actionDie' this can confuse the code. + // And no need to clear the marker, since we'll soon see 'actionDie' + // and clear it for good. + } + else + debugger()->setBreakpoint(bp.fileName(), bp.lineNum()-1, + 1/*bp->id()*/, bp.isEnabled(), bp.isPending() ); + } +} + +void DebuggerPart::slotStatus(const QString &msg, int state) +{ + QString stateIndicator, stateIndicatorFull; + + if (state & s_dbgNotStarted) + { + stateIndicator = " "; + stateIndicatorFull = "Debugger not started"; + stateChanged( QString("stopped") ); + } + else if (state & s_dbgBusy) + { + stateIndicator = "R"; + stateIndicatorFull = "Debugger is busy"; + stateChanged( QString("active") ); + } + else if (state & s_programExited) + { + stateIndicator = "E"; + stateIndicatorFull = "Application has exited"; + stateChanged( QString("stopped") ); + } + else + { + stateIndicator = "P"; + stateIndicatorFull = "Application is paused"; + stateChanged( QString("paused") ); + // On the first stop, show the variables view. + // We do it on first stop, and not at debugger start, because + // a program might just successfully run till completion. If we show + // the var views on start and hide on stop, this will look like flicker. + // On the other hand, if application is paused, it's very + // likely that the user wants to see variables. + if (justRestarted_) + { + justRestarted_ = false; + mainWindow()->setViewAvailable(variableWidget, true); + mainWindow()->raiseView(variableWidget); + } + } + + if (state & s_appNotStarted) + { + KActionCollection *ac = actionCollection(); + ac->action("debug_run")->setText( i18n("To start something","Start") ); + ac->action("debug_run")->setToolTip( i18n("Restart the program in the debugger") ); + ac->action("debug_run")->setWhatsThis( i18n("Restart in debugger\n\n" + "Restarts the program in the debugger") ); + } + + + bool program_running = !(state & s_appNotStarted); + bool attached_or_core = (state & s_attached) || (state & s_core); + + // If program is started, enable the 'restart' comand. + actionCollection()->action("debug_restart")->setEnabled( + program_running && !attached_or_core); + + + // As soon as debugger clears 's_appNotStarted' flag, we + // set 'justRestarted' variable. + // The other approach would be to set justRestarted in slotRun, slotCore + // and slotAttach. + // Note that setting this var in startDebugger is not OK, because the + // initial state of debugger is exactly the same as state after pause, + // so we'll always show varaibles view. + if ((previousDebuggerState_ & s_appNotStarted) && + !(state & s_appNotStarted)) + { + justRestarted_ = true; + } + if (state & s_appNotStarted) + { + justRestarted_ = false; + } + + // And now? :-) + kdDebug(9012) << "Debugger state: " << stateIndicator << ": " << endl; + kdDebug(9012) << " " << msg << endl; + + statusBarIndicator->setText(stateIndicator); + QToolTip::add(statusBarIndicator, stateIndicatorFull); + if (!msg.isEmpty()) + mainWindow()->statusBar()->message(msg, 3000); + + + previousDebuggerState_ = state; +} + +void DebuggerPart::slotEvent(GDBController::event_t e) +{ + if (e == GDBController::program_running || + e == GDBController::program_exited || + e == GDBController::debugger_exited) + { + debugger()->clearExecutionPoint(); + } +} + + +void DebuggerPart::slotShowStep(const QString &fileName, int lineNum) +{ + if ( ! fileName.isEmpty() ) + { + // Debugger counts lines from 1 + debugger()->gotoExecutionPoint(KURL( fileName ), lineNum-1); + } + else + { + debugger()->clearExecutionPoint(); + } +} + + +void DebuggerPart::slotGotoSource(const QString &fileName, int lineNum) +{ + if ( ! fileName.isEmpty() ) + partController()->editDocument(KURL( fileName ), lineNum); +} + + +void DebuggerPart::slotActivePartChanged( KParts::Part* part ) +{ + KAction* action = actionCollection()->action("debug_toggle_breakpoint"); + if(!action) + return; + + if(!part) + { + action->setEnabled(false); + return; + } + KTextEditor::ViewCursorInterface *iface + = dynamic_cast<KTextEditor::ViewCursorInterface*>(part->widget()); + action->setEnabled( iface != 0 ); +} + +void DebuggerPart::restorePartialProjectSession(const QDomElement* el) +{ + gdbBreakpointWidget->restorePartialProjectSession(el); + gdbOutputWidget->restorePartialProjectSession(el); +} + +void DebuggerPart::savePartialProjectSession(QDomElement* el) +{ + gdbBreakpointWidget->savePartialProjectSession(el); + gdbOutputWidget->savePartialProjectSession(el); +} + +bool DebuggerPart::haveModifiedFiles() +{ + bool have_modified = false; + KURL::List const& filelist = partController()->openURLs(); + KURL::List::ConstIterator it = filelist.begin(); + while ( it != filelist.end() ) + { + if (partController()->documentState(*it) != Clean) + have_modified = true; + + ++it; + } + + return have_modified; +} + +} + +KDevAppFrontend * GDBDebugger::DebuggerPart::appFrontend( ) +{ + return extension<KDevAppFrontend>("KDevelop/AppFrontend"); +} + +KDevDebugger * GDBDebugger::DebuggerPart::debugger() +{ + return m_debugger; +} + +#include "debuggerpart.moc" |