You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1861 lines
60 KiB
1861 lines
60 KiB
// *************************************************************************
|
|
// gdbcontroller.cpp - description
|
|
// -------------------
|
|
// begin : Sun Aug 8 1999
|
|
// copyright : (C) 1999 by John Birch
|
|
// email : jbb@kdevelop.org
|
|
// **************************************************************************
|
|
//
|
|
// **************************************************************************
|
|
// * *
|
|
// * This program is free software; you can redistribute it and/or modify *
|
|
// * it under the terms of the GNU General Public License as published by *
|
|
// * the Free Software Foundation; either version 2 of the License, or *
|
|
// * (at your option) any later version. *
|
|
// * *
|
|
// **************************************************************************
|
|
|
|
#include "gdbcontroller.h"
|
|
|
|
#include "breakpoint.h"
|
|
#include "gdbcommand.h"
|
|
#include "stty.h"
|
|
#include "domutil.h"
|
|
#include "settings.h"
|
|
#include "mi/miparser.h"
|
|
|
|
#include <tdeapplication.h>
|
|
#include <tdeconfig.h>
|
|
#include <kdebug.h>
|
|
#include <tdeglobal.h>
|
|
#include <tdelocale.h>
|
|
#include <tdemessagebox.h>
|
|
#include <kprocess.h>
|
|
#include <twin.h>
|
|
|
|
#include <tqdatetime.h>
|
|
#include <tqfileinfo.h>
|
|
#include <tqregexp.h>
|
|
#include <tqstring.h>
|
|
#include <tqdir.h>
|
|
#include <tqvaluevector.h>
|
|
#include <tqeventloop.h>
|
|
|
|
#include <iostream>
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <typeinfo>
|
|
using namespace std;
|
|
|
|
// **************************************************************************
|
|
//
|
|
// Does all the communication between gdb and the tdevelop's debugger code.
|
|
// Significatant classes being used here are
|
|
//
|
|
// GDBParser - parses the "variable" data using the vartree and varitems
|
|
// VarTree - where the variable data will end up
|
|
// FrameStack - tracks the program frames and allows the user to switch between
|
|
// and therefore view the calling funtions and their data
|
|
// Breakpoint - Where and what to do with breakpoints.
|
|
// STTY - the tty that the _application_ will run on.
|
|
//
|
|
// Significant variables
|
|
// state_ - be very careful setting this. The controller is totally
|
|
// dependent on this reflecting the correct state. For instance,
|
|
// if the app is busy but we don't think so, then we lose control
|
|
// of the app. The only way to get out of these situations is to
|
|
// delete (stop) the controller.
|
|
// currentFrame_
|
|
// - Holds the frame number where and locals/variable information will
|
|
// go to
|
|
//
|
|
// Certain commands need to be "wrapped", so that the output gdb produces is
|
|
// of the form "\032data_id gdb output \032data_id"
|
|
// Then a very simple parse can extract this gdb output and hand it off
|
|
// to its' respective parser.
|
|
// To do this we set the prompt to be \032data_id before the command and then
|
|
// reset to \032i to indicate the "idle".
|
|
//
|
|
// Note that the following does not work because in certain situations
|
|
// gdb can get an error in performing the command and therefore will not
|
|
// output the final echo. Hence the data will be thrown away.
|
|
// (certain "info locals" will generate this error.
|
|
//
|
|
// queueCmd(new GDBCommand(TQString().sprintf("define printlocal\n"
|
|
// "echo \32%c\ninfo locals\necho \32%c\n"
|
|
// "end",
|
|
// LOCALS, LOCALS)));
|
|
// (although replacing echo with "set prompt" appropriately could work Hmmmm.)
|
|
//
|
|
// Shared libraries and breakpoints
|
|
// ================================
|
|
// Shared libraries and breakpoints have a problem that has a reasonable solution.
|
|
// The problem is that gdb will not accept breakpoints in source that is in a
|
|
// shared library that has _not_ _yet_ been opened but will be opened via a
|
|
// dlopen.
|
|
//
|
|
// The solution is to get gdb to tell us when a shared library has been opened.
|
|
// This means that when the user sets a breakpoint, we flag this breakpoint as
|
|
// pending, try to set the breakpoint and if gdb says it succeeded then flag it
|
|
// as active. If gdb is not successful then we leave the breakpoint as pending.
|
|
//
|
|
// This is known as "lazy breakpoints"
|
|
//
|
|
// If the user has selected a file that is really outside the program and tried to
|
|
// set a breakpoint then this breakpoint will always be pending. I can't do
|
|
// anything about that, because it _might_ be in a shared library. If not they
|
|
// are either fools or just misguided...
|
|
//
|
|
// Now that the breakpoint is pending, we need gdb to tell us when a shared
|
|
// library has been loaded. We use "set stop-on 1". This breaks on _any_
|
|
// library event, and we just try to set the pending breakpoints. Once we're
|
|
// done, we then "continue"
|
|
//
|
|
// Now here's the problem with all this. If the user "step"s over code that
|
|
// contains a library dlopen then it'll just keep running, because we receive a
|
|
// break and hence end up doing a continue. In this situation, I do _not_
|
|
// do a continue but leave it stopped with the status line reflecting the
|
|
// stopped state. The frame stack is in the dl routine that caused the stop.
|
|
//
|
|
// There isn't any way around this, but I could allievate the problem somewhat
|
|
// by only doing a "set stop-on 1" when we have pending breakpoints.
|
|
//
|
|
// **************************************************************************
|
|
|
|
namespace GDBDebugger
|
|
{
|
|
|
|
// This is here so we can check for startup /shutdown problems
|
|
int debug_controllerExists = false;
|
|
|
|
|
|
GDBController::GDBController(TQDomDocument &projectDom)
|
|
: DbgController(),
|
|
currentFrame_(0),
|
|
viewedThread_(-1),
|
|
holdingZone_(),
|
|
currentCmd_(0),
|
|
tty_(0),
|
|
badCore_(TQString()),
|
|
state_(s_dbgNotStarted|s_appNotStarted),
|
|
programHasExited_(false),
|
|
dom(projectDom),
|
|
config_breakOnLoadingLibrary_(true),
|
|
config_forceBPSet_(true),
|
|
config_displayStaticMembers_(false),
|
|
config_asmDemangle_(true),
|
|
config_dbgTerminal_(false),
|
|
config_gdbPath_(),
|
|
config_outputRadix_(10),
|
|
state_reload_needed(false),
|
|
stateReloadInProgress_(false)
|
|
{
|
|
configure();
|
|
cmdList_.setAutoDelete(true);
|
|
|
|
Q_ASSERT(! debug_controllerExists);
|
|
debug_controllerExists = true;
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
// Deleting the controller 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.
|
|
GDBController::~GDBController()
|
|
{
|
|
debug_controllerExists = false;
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::configure()
|
|
{
|
|
// A a configure.gdb script will prevent these from uncontrolled growth...
|
|
config_configGdbScript_ = DomUtil::readEntry(dom, "/kdevdebugger/general/configGdbScript").latin1();
|
|
config_runShellScript_ = DomUtil::readEntry(dom, "/kdevdebugger/general/runShellScript").latin1();
|
|
config_runGdbScript_ = DomUtil::readEntry(dom, "/kdevdebugger/general/runGdbScript").latin1();
|
|
|
|
// add macros for reading TQStrings? or in configGdbScript?
|
|
config_forceBPSet_ = DomUtil::readBoolEntry(dom, "/kdevdebugger/general/allowforcedbpset", true);
|
|
config_dbgTerminal_ = DomUtil::readBoolEntry(dom, "/kdevdebugger/general/separatetty", false);
|
|
config_gdbPath_ = DomUtil::readEntry(dom, "/kdevdebugger/general/gdbpath");
|
|
|
|
bool old_displayStatic = config_displayStaticMembers_;
|
|
config_displayStaticMembers_ = DomUtil::readBoolEntry(dom, "/kdevdebugger/display/staticmembers",false);
|
|
|
|
bool old_asmDemangle = config_asmDemangle_;
|
|
config_asmDemangle_ = DomUtil::readBoolEntry(dom, "/kdevdebugger/display/demanglenames",true);
|
|
|
|
bool old_breakOnLoadingLibrary_ = config_breakOnLoadingLibrary_;
|
|
config_breakOnLoadingLibrary_ = DomUtil::readBoolEntry(dom, "/kdevdebugger/general/breakonloadinglibs",true);
|
|
|
|
// FIXME: should move this into debugger part or variable widget.
|
|
int old_outputRadix = config_outputRadix_;
|
|
#if 0
|
|
config_outputRadix_ = DomUtil::readIntEntry(dom, "/kdevdebugger/display/outputradix", 10);
|
|
varTree_->setRadix(config_outputRadix_);
|
|
#endif
|
|
|
|
|
|
if (( old_displayStatic != config_displayStaticMembers_ ||
|
|
old_asmDemangle != config_asmDemangle_ ||
|
|
old_breakOnLoadingLibrary_ != config_breakOnLoadingLibrary_ ||
|
|
old_outputRadix != config_outputRadix_) &&
|
|
dbgProcess_)
|
|
{
|
|
bool restart = false;
|
|
if (stateIsOn(s_dbgBusy))
|
|
{
|
|
pauseApp();
|
|
restart = true;
|
|
}
|
|
|
|
if (old_displayStatic != config_displayStaticMembers_)
|
|
{
|
|
if (config_displayStaticMembers_)
|
|
queueCmd(new GDBCommand("set print static-members on"));
|
|
else
|
|
queueCmd(new GDBCommand("set print static-members off"));
|
|
}
|
|
if (old_asmDemangle != config_asmDemangle_)
|
|
{
|
|
if (config_asmDemangle_)
|
|
queueCmd(new GDBCommand("set print asm-demangle on"));
|
|
else
|
|
queueCmd(new GDBCommand("set print asm-demangle off"));
|
|
}
|
|
|
|
// Disabled for MI port.
|
|
if (old_outputRadix != config_outputRadix_)
|
|
{
|
|
queueCmd(new GDBCommand(TQCString().sprintf("set 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_.isEmpty())
|
|
queueCmd(new GDBCommand("source " + config_configGdbScript_));
|
|
|
|
if (restart)
|
|
queueCmd(new GDBCommand("-exec-continue"));
|
|
}
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::addCommand(GDBCommand* cmd)
|
|
{
|
|
queueCmd(cmd);
|
|
}
|
|
|
|
void GDBController::addCommand(const TQString& str)
|
|
{
|
|
queueCmd(new GDBCommand(str));
|
|
}
|
|
|
|
void GDBController::addCommandToFront(GDBCommand* cmd)
|
|
{
|
|
queueCmd(cmd, queue_at_front);
|
|
}
|
|
|
|
void GDBController::addCommandBeforeRun(GDBCommand* cmd)
|
|
{
|
|
queueCmd(cmd, queue_before_run);
|
|
}
|
|
|
|
int GDBController::currentThread() const
|
|
{
|
|
return viewedThread_;
|
|
}
|
|
|
|
int GDBController::currentFrame() const
|
|
{
|
|
return currentFrame_;
|
|
}
|
|
|
|
// Fairly obvious that we'll add whatever command you give me to a queue
|
|
// If you tell me to, I'll put it at the head of the queue so it'll run ASAP
|
|
// 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 GDBController::queueCmd(GDBCommand *cmd, enum queue_where queue_where)
|
|
{
|
|
if (stateIsOn(s_dbgNotStarted))
|
|
{
|
|
KMessageBox::information(
|
|
0,
|
|
i18n("<b>Gdb command sent when debugger is not running</b><br>"
|
|
"The command was:<br> %1").arg(cmd->initialString()),
|
|
i18n("Internal error"), "gdb_error");
|
|
return;
|
|
}
|
|
|
|
if (stateReloadInProgress_)
|
|
stateReloadingCommands_.insert(cmd);
|
|
|
|
if (queue_where == queue_at_front)
|
|
cmdList_.insert(0, cmd);
|
|
else if (queue_where == queue_at_end)
|
|
cmdList_.append (cmd);
|
|
else if (queue_where == queue_before_run)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < cmdList_.count(); ++i)
|
|
if (cmdList_.at(i)->isRun())
|
|
break;
|
|
|
|
cmdList_.insert(i, cmd);
|
|
}
|
|
|
|
kdDebug(9012) << "QUEUE: " << cmd->initialString()
|
|
<< (stateReloadInProgress_ ? " (state reloading)\n" : "\n");
|
|
|
|
setStateOn(s_dbgBusy);
|
|
emit dbgStatus("", state_);
|
|
raiseEvent(debugger_busy);
|
|
|
|
executeCmd();
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
// If the appliction can accept a command and we've got one waiting
|
|
// then send it.
|
|
// Commands can be just request for data (or change gdbs state in someway)
|
|
// or they can be "run" commands. If a command is sent to gdb our internal
|
|
// state will get updated.
|
|
void GDBController::executeCmd()
|
|
{
|
|
if (stateIsOn(s_dbgNotStarted|s_waitForWrite|s_shuttingDown) || !dbgProcess_)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!currentCmd_)
|
|
{
|
|
if (cmdList_.isEmpty())
|
|
return;
|
|
|
|
currentCmd_ = cmdList_.take(0);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
TQString commandText = currentCmd_->cmdToSend();
|
|
bool bad_command = false;
|
|
TQString message;
|
|
|
|
unsigned 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<SentinelCommand*>(currentCmd_))
|
|
{
|
|
kdDebug(9012) << "SEND: sentinel command, not sending\n";
|
|
sc->invokeHandler();
|
|
}
|
|
else
|
|
{
|
|
kdDebug(9012) << "SEND: command " << currentCmd_->initialString()
|
|
<< " changed its mind, not sending\n";
|
|
}
|
|
|
|
destroyCurrentCommand();
|
|
executeCmd();
|
|
commandDone();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (commandText[length-1] != '\n')
|
|
{
|
|
bad_command = true;
|
|
message = "Debugger command does not end with newline";
|
|
}
|
|
}
|
|
if (bad_command)
|
|
{
|
|
KMessageBox::information(0, i18n("<b>Invalid debugger command</b><br>")
|
|
+ message,
|
|
i18n("Invalid debugger command"), "gdb_error");
|
|
return;
|
|
}
|
|
|
|
kdDebug(9012) << "SEND: " << commandText;
|
|
|
|
dbgProcess_->writeStdin(commandText.local8Bit(),
|
|
commandText.length());
|
|
setStateOn(s_waitForWrite);
|
|
|
|
TQString prettyCmd = currentCmd_->cmdToSend();
|
|
prettyCmd.replace( TQRegExp("set prompt \032.\n"), "" );
|
|
prettyCmd = "(gdb) " + prettyCmd;
|
|
|
|
if (currentCmd_->isUserCommand())
|
|
emit gdbUserCommandStdout( prettyCmd.latin1() );
|
|
else
|
|
emit gdbInternalCommandStdout( prettyCmd.latin1() );
|
|
|
|
emit dbgStatus ("", state_);
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::destroyCmds()
|
|
{
|
|
if (currentCmd_)
|
|
{
|
|
destroyCurrentCommand();
|
|
}
|
|
|
|
while (!cmdList_.isEmpty())
|
|
delete cmdList_.take(0);
|
|
}
|
|
|
|
// 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 GDBController::pauseApp()
|
|
{
|
|
setStateOn(s_explicitBreakInto);
|
|
|
|
/* FIXME: need to decide if we really
|
|
need this, and the consistenly mark
|
|
info commands as such.
|
|
int i = cmdList_.count();
|
|
while (i)
|
|
{
|
|
i--;
|
|
DbgCommand *cmd = cmdList_.at(i);
|
|
if (cmd->isAnInfoCmd())
|
|
delete cmdList_.take(i);
|
|
}
|
|
*/
|
|
|
|
if (dbgProcess_)
|
|
dbgProcess_->kill(SIGINT);
|
|
}
|
|
|
|
void GDBController::actOnProgramPauseMI(const GDBMI::ResultRecord& r)
|
|
{
|
|
// Is this stop on shared library load? Gdb smartly does not
|
|
// print any 'reason' field in this case.
|
|
bool shared_library_load = false;
|
|
if (currentCmd_)
|
|
{
|
|
const TQValueVector<TQString>& lines = currentCmd_->allStreamOutput();
|
|
for(unsigned int i = 0; i < lines.count(); ++i)
|
|
{
|
|
if (lines[i].startsWith("Stopped due to shared library event"))
|
|
{
|
|
shared_library_load = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (shared_library_load)
|
|
{
|
|
raiseEvent(shared_library_loaded);
|
|
queueCmd(new GDBCommand("-exec-continue"));
|
|
return;
|
|
}
|
|
|
|
if (!r.hasField("reason"))
|
|
{
|
|
// FIXME: throw an exception, and add the gdb reply in the
|
|
// caller. Show message box in the caller, not here.
|
|
// FIXME: remove this 'bla-bla-bla'.
|
|
KMessageBox::detailedSorry(
|
|
0,
|
|
i18n("<b>Invalid gdb reply</b>"
|
|
"<p>The 'stopped' packet does not include the 'reason' field'."),
|
|
i18n("The gdb reply is: bla-bla-bla"),
|
|
i18n("Invalid gdb reply"));
|
|
return;
|
|
}
|
|
|
|
TQString reason = r["reason"].literal();
|
|
if (reason == "exited-normally" || reason == "exited")
|
|
{
|
|
programNoApp("Exited normally", false);
|
|
programHasExited_ = true;
|
|
state_reload_needed = false;
|
|
return;
|
|
}
|
|
|
|
if (reason == "exited-signalled")
|
|
{
|
|
programNoApp(i18n("Exited on signal %1")
|
|
.arg(r["signal-name"].literal()), false);
|
|
// FIXME: figure out why this variable is needed.
|
|
programHasExited_ = true;
|
|
state_reload_needed = false;
|
|
return;
|
|
}
|
|
|
|
if (reason == "watchpoint-scope")
|
|
{
|
|
TQString 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("-exec-continue"));
|
|
|
|
state_reload_needed = false;
|
|
return;
|
|
}
|
|
|
|
if (reason == "signal-received")
|
|
{
|
|
TQString name = r["signal-name"].literal();
|
|
TQString 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.
|
|
bool suppress_reporting = false;
|
|
if (name == "SIGINT" && stateIsOn(s_explicitBreakInto))
|
|
{
|
|
suppress_reporting = true;
|
|
// TODO: check that we do something reasonable on
|
|
// implicit break into program (for setting breakpoints,
|
|
// or whatever).
|
|
|
|
setStateOff(s_explicitBreakInto);
|
|
emit dbgStatus("Application interrupted", state_);
|
|
// Will show the source line in the code
|
|
// handling non-special stop kinds, below.
|
|
}
|
|
|
|
if (!suppress_reporting)
|
|
{
|
|
// 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.
|
|
KMessageBox::information(0,
|
|
i18n("Program received signal %1 (%2)")
|
|
.arg(name).arg(user_name),
|
|
i18n("Received signal"));
|
|
}
|
|
}
|
|
|
|
if (reason == "breakpoint-hit")
|
|
{
|
|
int id = r["bkptno"].literal().toInt();
|
|
emit breakpointHit(id);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
void GDBController::reloadProgramState()
|
|
{
|
|
const GDBMI::ResultRecord& r = *last_stop_result;
|
|
|
|
// In gdb 6.3, the *stopped reply does not include full
|
|
// name of the source file. Need to send extra command.
|
|
// Don't send it unless there was 'line' field in last *stopped response.
|
|
// The command has a bug that makes it always returns some file/line,
|
|
// even if we're not in one.
|
|
//
|
|
// FIXME: For gdb 6.4, should not send extra commands.
|
|
// That's for later, so that I verify that this three-command
|
|
// approach works fine.
|
|
if (r.hasField("frame") && r["frame"].hasField("line"))
|
|
queueCmd(new GDBCommand(
|
|
"-file-list-exec-source-file",
|
|
this,
|
|
&GDBController::handleMiFileListExecSourceFile));
|
|
else
|
|
{
|
|
maybeAnnounceWatchpointHit();
|
|
}
|
|
|
|
emit dbgStatus ("", state_);
|
|
|
|
// We're always at frame zero when the program stops
|
|
// and we must reset the active flag
|
|
if (r.hasField("thread-id"))
|
|
viewedThread_ = r["thread-id"].literal().toInt();
|
|
else
|
|
viewedThread_ = -1;
|
|
currentFrame_ = 0;
|
|
|
|
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 GDBController::programNoApp(const TQString &msg, bool msgBox)
|
|
{
|
|
setState(s_appNotStarted|s_programExited|(state_&s_shuttingDown));
|
|
|
|
destroyCmds();
|
|
|
|
// We're always at frame zero when the program stops
|
|
// and we must reset the active flag
|
|
viewedThread_ = -1;
|
|
currentFrame_ = 0;
|
|
|
|
// 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 (tty_)
|
|
tty_->readRemaining();
|
|
|
|
// Tty is no longer usable, delete it. Without this, TQSocketNotifier
|
|
// will continiously bomd STTY with signals, so we need to either disable
|
|
// TQSocketNotifier, or delete STTY. The latter is simpler, since we can't
|
|
// reuse it for future debug sessions anyway.
|
|
|
|
delete tty_;
|
|
tty_ = 0;
|
|
|
|
raiseEvent(program_exited);
|
|
|
|
if (msgBox)
|
|
KMessageBox::information(0, i18n("gdb message:\n")+msg,"Warning", "gdb_error");
|
|
|
|
emit dbgStatus (msg, state_);
|
|
/* Also show message in gdb window, so that users who
|
|
prefer to look at gdb window know what's up. */
|
|
emit gdbUserCommandStdout(msg.ascii());
|
|
}
|
|
|
|
void GDBController::parseCliLine(const TQString& line)
|
|
{
|
|
if (line.startsWith("The program no longer exists")
|
|
|| line.startsWith("Program exited")
|
|
|| line.startsWith("Program terminated"))
|
|
{
|
|
programNoApp(line, false);
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
if (strncmp(buf, "No symbol", 9) == 0 || // watch point failed
|
|
strncmp(buf, "Single", 6) == 0 || // Single stepping
|
|
strncmp(buf, "No source file named", 20) == 0 || // breakpoint not set
|
|
strncmp(buf, "[Switching to Thread", 20) == 0 || //
|
|
strncmp(buf, "[Thread debugging using", 23) == 0 ||
|
|
strncmp(buf, "Current language:", 17) == 0 ||
|
|
strncmp(buf, "Error while mapping shared library sections:", 44) == 0 ||
|
|
strncmp(buf, "Error while reading shared library symbols:", 43) == 0 ||
|
|
*buf == ':' )
|
|
{
|
|
// We don't change state, because this falls out when a run command
|
|
// starts rather than when a run command stops.
|
|
// Or.... it falls out with other messages that _are_ handled.
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
|
|
/// @todo - Only do this at start up
|
|
if (
|
|
strstr(buf, "not in executable format:") ||
|
|
strstr(buf, "No such file or directory.") || // does this fall out?
|
|
strstr(buf, i18n("No such file or directory.").local8Bit())|| // from system via gdb
|
|
strstr(buf, "is not a core dump:") ||
|
|
strncmp(buf, "ptrace: No such process.", 24)==0 ||
|
|
strncmp(buf, "ptrace: Operation not permitted.", 32)==0 ||
|
|
strncmp(buf, "No executable file specified.", 29)==0)
|
|
{
|
|
programNoApp(TQString(buf), true);
|
|
kdDebug(9012) << "Bad file <" << buf << ">" << endl;
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void GDBController::handleMiFileListExecSourceFile(const GDBMI::ResultRecord& r)
|
|
{
|
|
if (r.reason != "done")
|
|
{
|
|
return;
|
|
|
|
// FIXME: throw an exception here. Move reporting
|
|
// to the caller, who knows the gdb output.
|
|
#if 0
|
|
KMessageBox::information(
|
|
0,
|
|
i18n("Invalid gdb reply\n"
|
|
"Command was: %1\n"
|
|
"Response is: %2\n"
|
|
"Invalid response kind: \"%3\"")
|
|
.arg(currentCmd_->rawDbgCommand())
|
|
.arg(buf)
|
|
.arg(r.reason),
|
|
i18n("Invalid gdb reply"), "gdb_error");
|
|
#endif
|
|
}
|
|
|
|
TQString fullname = "";
|
|
if (r.hasField("fullname"))
|
|
fullname = r["fullname"].literal();
|
|
|
|
showStepInSource(fullname,
|
|
r["line"].literal().toInt(),
|
|
(*last_stop_result)["frame"]["addr"].literal());
|
|
|
|
/* Watchpoint hit is announced only after we've highlighted
|
|
the current line. */
|
|
maybeAnnounceWatchpointHit();
|
|
|
|
last_stop_result.reset();
|
|
}
|
|
|
|
void GDBController::maybeAnnounceWatchpointHit()
|
|
{
|
|
/* For some cases, for example catchpoints,
|
|
gdb does not report any reason at all. */
|
|
if ((*last_stop_result).hasField("reason"))
|
|
{
|
|
TQString last_stop_reason = (*last_stop_result)["reason"].literal();
|
|
|
|
if (last_stop_reason == "watchpoint-trigger")
|
|
{
|
|
emit watchpointHit((*last_stop_result)["wpt"]["number"]
|
|
.literal().toInt(),
|
|
(*last_stop_result)["value"]["old"].literal(),
|
|
(*last_stop_result)["value"]["new"].literal());
|
|
}
|
|
else if (last_stop_reason == "read-watchpoint-trigger")
|
|
{
|
|
emit dbgStatus ("Read watchpoint triggered", state_);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GDBController::handleMiFrameSwitch(const GDBMI::ResultRecord& r)
|
|
{
|
|
raiseEvent(thread_or_frame_changed);
|
|
|
|
const GDBMI::Value& frame = r["frame"];
|
|
|
|
TQString file;
|
|
if (frame.hasField("fullname"))
|
|
file = frame["fullname"].literal();
|
|
else if (frame.hasField("file"))
|
|
file = frame["file"].literal();
|
|
|
|
int line = -1;
|
|
if (frame.hasField("line"))
|
|
line = frame["line"].literal().toInt();
|
|
|
|
showStepInSource(file,
|
|
line,
|
|
frame["addr"].literal());
|
|
}
|
|
|
|
// **************************************************************************
|
|
// SLOTS
|
|
// *****
|
|
// For most of these slots data can only be sent to gdb when it
|
|
// isn't busy and it is running.
|
|
|
|
// **************************************************************************
|
|
|
|
bool GDBController::start(const TQString& shell, const DomUtil::PairList& run_envvars, const TQString& run_directory, const TQString &application, const TQString& run_arguments)
|
|
{
|
|
kdDebug(9012) << "Starting debugger controller\n";
|
|
badCore_ = TQString();
|
|
|
|
Q_ASSERT (!dbgProcess_ && !tty_);
|
|
|
|
dbgProcess_ = new TDEProcess;
|
|
|
|
connect( dbgProcess_, TQT_SIGNAL(receivedStdout(TDEProcess *, char *, int)),
|
|
this, TQT_SLOT(slotDbgStdout(TDEProcess *, char *, int)) );
|
|
|
|
connect( dbgProcess_, TQT_SIGNAL(receivedStderr(TDEProcess *, char *, int)),
|
|
this, TQT_SLOT(slotDbgStderr(TDEProcess *, char *, int)) );
|
|
|
|
connect( dbgProcess_, TQT_SIGNAL(wroteStdin(TDEProcess *)),
|
|
this, TQT_SLOT(slotDbgWroteStdin(TDEProcess *)) );
|
|
|
|
connect( dbgProcess_, TQT_SIGNAL(processExited(TDEProcess*)),
|
|
this, TQT_SLOT(slotDbgProcessExited(TDEProcess*)) );
|
|
|
|
application_ = application;
|
|
|
|
TQString gdb = "gdb";
|
|
// Prepend path to gdb, if needed. Using TQDir,
|
|
// path can either end with slash, or not.
|
|
if (!config_gdbPath_.isEmpty())
|
|
{
|
|
gdb = config_gdbPath_;
|
|
}
|
|
|
|
if (!shell.isEmpty())
|
|
{
|
|
*dbgProcess_ << "/bin/sh" << "-c" << shell + " " + gdb +
|
|
+ " " + application + " --interpreter=mi2 -quiet";
|
|
emit gdbUserCommandStdout(
|
|
TQString( "/bin/sh -c " + shell + " " + gdb
|
|
+ " " + application
|
|
+ " --interpreter=mi2 -quiet\n" ).latin1());
|
|
}
|
|
else
|
|
{
|
|
*dbgProcess_ << gdb << application
|
|
<< "-interpreter=mi2" << "-quiet";
|
|
emit gdbUserCommandStdout(
|
|
TQString( gdb + " " + application +
|
|
" --interpreter=mi2 -quiet\n" ).latin1());
|
|
}
|
|
|
|
if (!dbgProcess_->start( TDEProcess::NotifyOnExit,
|
|
TDEProcess::Communication(TDEProcess::All)))
|
|
{
|
|
KMessageBox::information(
|
|
0,
|
|
i18n("<b>Could not start debugger.</b>"
|
|
"<p>Could not run '%1'. "
|
|
"Make sure that the path name is specified correctly."
|
|
).arg(dbgProcess_->args()[0].data()),
|
|
i18n("Could not start debugger"), "gdb_error");
|
|
|
|
return false;
|
|
}
|
|
|
|
setStateOff(s_dbgNotStarted);
|
|
emit dbgStatus ("", state_);
|
|
|
|
saw_gdb_prompt_ = false;
|
|
|
|
// 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
|
|
|
|
// The following two are not necessary in MI, and the first one
|
|
// just breaks MI completely.
|
|
// queueCmd(new GDBCommand("set edit off", NOTRUNCMD, NOTINFOCMD, 0));
|
|
// queueCmd(new GDBCommand("set confirm off", NOTRUNCMD, NOTINFOCMD));
|
|
|
|
if (config_displayStaticMembers_)
|
|
queueCmd(new GDBCommand("set print static-members on"));
|
|
else
|
|
queueCmd(new GDBCommand("set print static-members off"));
|
|
|
|
// This makes gdb pump a variable out on one line.
|
|
queueCmd(new GDBCommand("set width 0"));
|
|
queueCmd(new GDBCommand("set height 0"));
|
|
|
|
queueCmd(new GDBCommand("handle SIG32 pass nostop noprint"));
|
|
queueCmd(new GDBCommand("handle SIG41 pass nostop noprint"));
|
|
queueCmd(new GDBCommand("handle SIG42 pass nostop noprint"));
|
|
queueCmd(new GDBCommand("handle SIG43 pass nostop noprint"));
|
|
|
|
// Print some nicer names in disassembly output. Although for an assembler
|
|
// person this may actually be wrong and the mangled name could be better.
|
|
if (config_asmDemangle_)
|
|
queueCmd(new GDBCommand("set print asm-demangle on"));
|
|
else
|
|
queueCmd(new GDBCommand("set print asm-demangle off"));
|
|
|
|
// make sure output radix is always set to users view.
|
|
queueCmd(new GDBCommand(TQCString().sprintf("set output-radix %d", config_outputRadix_)));
|
|
|
|
// Change the "Working directory" to the correct one
|
|
TQCString tmp( "cd " + TQFile::encodeName( run_directory ));
|
|
queueCmd(new GDBCommand(tmp));
|
|
|
|
// Set the run arguments
|
|
if (!run_arguments.isEmpty())
|
|
queueCmd(
|
|
new GDBCommand(TQCString("set args ") + run_arguments.local8Bit()));
|
|
|
|
// Get the run environment variables pairs into the environstr string
|
|
// in the form of: "ENV_VARIABLE=ENV_VALUE" and send to gdb using the
|
|
// "set enviroment" command
|
|
// Note that we quote the variable value due to the possibility of
|
|
// embedded spaces
|
|
TQString environstr;
|
|
DomUtil::PairList::ConstIterator it;
|
|
for (it = run_envvars.begin(); it != run_envvars.end(); ++it)
|
|
{
|
|
environstr = "set environment ";
|
|
environstr += (*it).first;
|
|
environstr += "=";
|
|
environstr += (*it).second;
|
|
queueCmd(new GDBCommand(environstr.latin1()));
|
|
}
|
|
|
|
queueCmd(new GDBCommand(
|
|
"-list-features", this,
|
|
&GDBController::handleListFeatures, true /* handles error */));
|
|
|
|
|
|
queueCmd(new SentinelCommand(this, &GDBController::startDone));
|
|
|
|
// Now gdb has been started and the application has been loaded,
|
|
// BUT the app hasn't been started yet! A run command is about to be issued
|
|
// by whoever is controlling us. Or we might be asked to load a core, or
|
|
// attach to a running process.
|
|
|
|
return true;
|
|
}
|
|
|
|
void GDBController::startDone()
|
|
{
|
|
// 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);
|
|
raiseEvent(connected_to_program);
|
|
}
|
|
|
|
void GDBController::handleListFeatures(const GDBMI::ResultRecord& r)
|
|
{
|
|
mi_pending_breakpoints_ = false;
|
|
if (r.reason == "done")
|
|
{
|
|
const GDBMI::Value& features = r["features"];
|
|
for (unsigned i = 0; i < features.size(); ++i)
|
|
if (features[i].literal() == "pending-breakpoints")
|
|
{
|
|
mi_pending_breakpoints_ = true;
|
|
}
|
|
}
|
|
|
|
if (!mi_pending_breakpoints_)
|
|
{
|
|
// This version of GDB does not support pending breakpoints in MI,
|
|
// so use a workaround.
|
|
// The below command makes GDB notify us about shared library
|
|
// events, and on each stop we'll try to set breakpoint again.
|
|
addCommandToFront(new GDBCommand("set stop-on-solib-events 1"));
|
|
}
|
|
}
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotStopDebugger()
|
|
{
|
|
kdDebug(9012) << "GDBController::slotStopDebugger() called" << endl;
|
|
if (stateIsOn(s_shuttingDown) || !dbgProcess_)
|
|
return;
|
|
|
|
setStateOn(s_shuttingDown);
|
|
kdDebug(9012) << "GDBController::slotStopDebugger() executing" << endl;
|
|
|
|
TQTime start;
|
|
TQTime now;
|
|
|
|
// Get gdb's attention if it's busy. We need gdb to be at the
|
|
// command line so we can stop it.
|
|
if (stateIsOn(s_dbgBusy))
|
|
{
|
|
kdDebug(9012) << "gdb busy on shutdown - stopping gdb (SIGINT)" << endl;
|
|
dbgProcess_->kill(SIGINT);
|
|
start = TQTime::currentTime();
|
|
while (-1)
|
|
{
|
|
kapp->eventLoop()->processEvents( TQEventLoop::ExcludeUserInput, 20 );
|
|
now = TQTime::currentTime();
|
|
if (!stateIsOn(s_dbgBusy) || start.msecsTo( now ) > 2000)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the app is attached then we release it here. This doesn't stop
|
|
// the app running.
|
|
if (stateIsOn(s_attached))
|
|
{
|
|
const char *detach="detach\n";
|
|
if (!dbgProcess_->writeStdin(detach, strlen(detach)))
|
|
kdDebug(9012) << "failed to write 'detach' to gdb" << endl;
|
|
emit gdbUserCommandStdout("(gdb) detach\n");
|
|
start = TQTime::currentTime();
|
|
while (-1)
|
|
{
|
|
kapp->eventLoop()->processEvents( TQEventLoop::ExcludeUserInput, 20 );
|
|
now = TQTime::currentTime();
|
|
if (!stateIsOn(s_attached) || start.msecsTo( now ) > 2000)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Now try to stop gdb running.
|
|
const char *quit="quit\n";
|
|
if (!dbgProcess_->writeStdin(quit, strlen(quit)))
|
|
kdDebug(9012) << "failed to write 'quit' to gdb" << endl;
|
|
|
|
emit gdbUserCommandStdout("(gdb) quit");
|
|
start = TQTime::currentTime();
|
|
while (-1)
|
|
{
|
|
kapp->eventLoop()->processEvents( TQEventLoop::ExcludeUserInput, 20 );
|
|
now = TQTime::currentTime();
|
|
if (stateIsOn(s_programExited) || start.msecsTo( now ) > 2000)
|
|
break;
|
|
}
|
|
|
|
// We cannot wait forever.
|
|
if (!stateIsOn(s_programExited))
|
|
{
|
|
kdDebug(9012) << "gdb not shutdown - killing" << endl;
|
|
dbgProcess_->kill(SIGKILL);
|
|
}
|
|
|
|
destroyCmds();
|
|
delete dbgProcess_; dbgProcess_ = 0;
|
|
delete tty_; tty_ = 0;
|
|
|
|
// The gdb output buffer might contain start marker of some
|
|
// previously issued command that crashed gdb (so there's no end marker)
|
|
// If we don't clear this, then after restart, we'll be trying to search
|
|
// for the end marker of the command issued in previous gdb session,
|
|
// and never succeed.
|
|
gdbOutput_ = "";
|
|
|
|
setState(s_dbgNotStarted | s_appNotStarted);
|
|
emit dbgStatus (i18n("Debugger stopped"), state_);
|
|
|
|
raiseEvent(debugger_exited);
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotCoreFile(const TQString &coreFile)
|
|
{
|
|
setStateOff(s_programExited|s_appNotStarted);
|
|
setStateOn(s_core);
|
|
|
|
queueCmd(new GDBCommand(TQCString("core ") + coreFile.latin1()));
|
|
|
|
raiseEvent(connected_to_program);
|
|
raiseEvent(program_state_changed);
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotAttachTo(int pid)
|
|
{
|
|
setStateOff(s_appNotStarted|s_programExited);
|
|
setStateOn(s_attached);
|
|
|
|
// 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(TQString("file")));
|
|
|
|
// The MI interface does not implements -target-attach yet,
|
|
// and we don't recognize whatever gibberish 'attach' pours out, so...
|
|
queueCmd(new GDBCommand(
|
|
TQCString().sprintf("attach %d", pid)));
|
|
|
|
raiseEvent(connected_to_program);
|
|
|
|
// ...emit a separate MI command to step one instruction more. We'll
|
|
// notice the '*stopped' response from it and proceed as usual.
|
|
queueCmd(new GDBCommand("-exec-step-instruction"));
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotRun()
|
|
{
|
|
if (stateIsOn(s_dbgNotStarted|s_shuttingDown))
|
|
return;
|
|
|
|
if (stateIsOn(s_appNotStarted)) {
|
|
|
|
delete tty_;
|
|
tty_ = new STTY(config_dbgTerminal_, Settings::terminalEmulatorName( *kapp->config() ));
|
|
if (!config_dbgTerminal_)
|
|
{
|
|
connect( tty_, TQT_SIGNAL(OutOutput(const char*)), TQT_SIGNAL(ttyStdout(const char*)) );
|
|
connect( tty_, TQT_SIGNAL(ErrOutput(const char*)), TQT_SIGNAL(ttyStderr(const char*)) );
|
|
}
|
|
|
|
TQString tty(tty_->getSlave());
|
|
if (tty.isEmpty())
|
|
{
|
|
KMessageBox::information(0, i18n("GDB cannot use the tty* or pty* devices.\n"
|
|
"Check the settings on /dev/tty* and /dev/pty*\n"
|
|
"As root you may need to \"chmod ug+rw\" tty* and pty* devices "
|
|
"and/or add the user to the tty group using "
|
|
"\"usermod -G tty username\"."), "Warning", "gdb_error");
|
|
|
|
delete tty_;
|
|
tty_ = 0;
|
|
return;
|
|
}
|
|
|
|
queueCmd(new GDBCommand(TQCString("tty ")+tty.latin1()));
|
|
|
|
if (!config_runShellScript_.isEmpty()) {
|
|
// Special for remote debug...
|
|
TQCString tty(tty_->getSlave().latin1());
|
|
TQCString options = TQCString(">") + tty + TQCString(" 2>&1 <") + tty;
|
|
|
|
TDEProcess *proc = new TDEProcess;
|
|
|
|
*proc << "sh" << "-c";
|
|
*proc << config_runShellScript_ +
|
|
" " + application_.latin1() + options;
|
|
proc->start(TDEProcess::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 GDBCommand("source " + config_runGdbScript_));
|
|
|
|
// Note: script could contain "run" or "continue"
|
|
}
|
|
else {
|
|
|
|
TQFileInfo app(application_);
|
|
|
|
if (!app.exists())
|
|
{
|
|
KMessageBox::error(
|
|
0,
|
|
i18n("<b>Application does not exist</b>"
|
|
"<p>The application you are trying to debug,<br>"
|
|
" %1\n"
|
|
"<br>does not exist. Check that you have specified "
|
|
"the right application in the debugger configuration."
|
|
).arg(app.fileName()),
|
|
i18n("Application does not exist"));
|
|
|
|
// FIXME: after this, KDevelop will still show that debugger
|
|
// is running, because DebuggerPart::slotStopDebugger won't be
|
|
// called, and core()->running(this, false) won't be called too.
|
|
slotStopDebugger();
|
|
return;
|
|
}
|
|
if (!app.isExecutable())
|
|
{
|
|
KMessageBox::error(
|
|
0,
|
|
i18n("<b>Could not run application '%1'.</b>"
|
|
"<p>The application does not have the executable bit set. "
|
|
"Try rebuilding the project, or change permissions "
|
|
"manually."
|
|
).arg(app.fileName()),
|
|
i18n("Could not run application"));
|
|
slotStopDebugger();
|
|
}
|
|
else
|
|
{
|
|
GDBCommand *cmd = new GDBCommand("-exec-run");
|
|
cmd->setRun(true);
|
|
queueCmd(cmd);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
removeStateReloadingCommands();
|
|
|
|
queueCmd(new GDBCommand("-exec-continue"));
|
|
}
|
|
setStateOff(s_appNotStarted|s_programExited);
|
|
}
|
|
|
|
|
|
void GDBController::slotKill()
|
|
{
|
|
if (stateIsOn(s_dbgNotStarted|s_shuttingDown))
|
|
return;
|
|
|
|
if (stateIsOn(s_dbgBusy))
|
|
{
|
|
pauseApp();
|
|
}
|
|
|
|
queueCmd(new GDBCommand("kill"));
|
|
|
|
setStateOn(s_appNotStarted);
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotRunUntil(const TQString &fileName, int lineNum)
|
|
{
|
|
if (stateIsOn(s_dbgBusy|s_dbgNotStarted|s_shuttingDown))
|
|
return;
|
|
|
|
removeStateReloadingCommands();
|
|
|
|
if (fileName.isEmpty())
|
|
queueCmd(new GDBCommand(
|
|
TQCString().sprintf("-exec-until %d", lineNum)));
|
|
else
|
|
queueCmd(new GDBCommand(
|
|
TQCString().
|
|
sprintf("-exec-until %s:%d", fileName.latin1(), lineNum)));
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotJumpTo(const TQString &fileName, int lineNum)
|
|
{
|
|
if (stateIsOn(s_dbgBusy|s_dbgNotStarted|s_shuttingDown))
|
|
return;
|
|
|
|
if (!fileName.isEmpty()) {
|
|
queueCmd(new GDBCommand(TQCString().sprintf("tbreak %s:%d", fileName.latin1(), lineNum)));
|
|
queueCmd(new GDBCommand(TQCString().sprintf("jump %s:%d", fileName.latin1(), lineNum)));
|
|
}
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotStepInto()
|
|
{
|
|
if (stateIsOn(s_dbgBusy|s_appNotStarted|s_shuttingDown))
|
|
return;
|
|
|
|
removeStateReloadingCommands();
|
|
|
|
queueCmd(new GDBCommand("-exec-step"));
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotStepIntoIns()
|
|
{
|
|
if (stateIsOn(s_dbgBusy|s_appNotStarted|s_shuttingDown))
|
|
return;
|
|
|
|
removeStateReloadingCommands();
|
|
|
|
queueCmd(new GDBCommand("-exec-step-instruction"));
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotStepOver()
|
|
{
|
|
if (stateIsOn(s_dbgBusy|s_appNotStarted|s_shuttingDown))
|
|
return;
|
|
|
|
removeStateReloadingCommands();
|
|
|
|
queueCmd(new GDBCommand("-exec-next"));
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotStepOverIns()
|
|
{
|
|
if (stateIsOn(s_dbgBusy|s_appNotStarted|s_shuttingDown))
|
|
return;
|
|
|
|
removeStateReloadingCommands();
|
|
|
|
queueCmd(new GDBCommand("-exec-next-instruction"));
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotStepOutOff()
|
|
{
|
|
if (stateIsOn(s_dbgBusy|s_appNotStarted|s_shuttingDown))
|
|
return;
|
|
|
|
removeStateReloadingCommands();
|
|
|
|
queueCmd(new GDBCommand("-exec-finish"));
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
// Only interrupt a running program.
|
|
void GDBController::slotBreakInto()
|
|
{
|
|
pauseApp();
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::selectFrame(int frameNo, int threadNo)
|
|
{
|
|
// FIXME: this either should be removed completely, or
|
|
// trigger an error message.
|
|
if (stateIsOn(s_dbgBusy|s_dbgNotStarted|s_shuttingDown))
|
|
return;
|
|
|
|
if (threadNo != -1)
|
|
{
|
|
if (viewedThread_ != threadNo)
|
|
queueCmd(new GDBCommand(
|
|
TQString("-thread-select %1").arg(threadNo).ascii()));
|
|
}
|
|
|
|
queueCmd(new GDBCommand(
|
|
TQString("-stack-select-frame %1").arg(frameNo).ascii()));
|
|
|
|
// Will emit the 'thread_or_frame_changed' event.
|
|
queueCmd(new GDBCommand("-stack-info-frame",
|
|
this, &GDBController::handleMiFrameSwitch));
|
|
|
|
|
|
// FIXME: the above commands might not be the first in queue, and
|
|
// previous commands might using values of 'viewedThread_' or
|
|
// 'currentFrame_'. Ideally, should change the values only after
|
|
// response from gdb.
|
|
viewedThread_ = threadNo;
|
|
currentFrame_ = frameNo;
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::defaultErrorHandler(const GDBMI::ResultRecord& result)
|
|
{
|
|
TQString msg = result["msg"].literal();
|
|
|
|
if (msg.contains("No such process"))
|
|
{
|
|
setState(s_appNotStarted|s_programExited);
|
|
emit dbgStatus (i18n("Process exited"), state_);
|
|
raiseEvent(program_exited);
|
|
return;
|
|
}
|
|
|
|
KMessageBox::information(
|
|
0,
|
|
i18n("<b>Debugger error</b>"
|
|
"<p>Debugger reported the following error:"
|
|
"<p><tt>") + result["msg"].literal(),
|
|
i18n("Debugger error"), "gdb_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 (stateReloadingCommands_.count(currentCmd_) == 0)
|
|
raiseEvent(program_state_changed);
|
|
}
|
|
|
|
void GDBController::processMICommandResponse(const GDBMI::ResultRecord& result)
|
|
{
|
|
kdDebug(9012) << "MI stop reason " << result.reason << "\n";
|
|
if (result.reason == "stopped")
|
|
{
|
|
actOnProgramPauseMI(result);
|
|
}
|
|
else if (result.reason == "done")
|
|
{
|
|
// At least in one case, for 'detach', debuger write
|
|
// command directly, and 'currentCmd_' will be unset.
|
|
// Checking for currentCmd_ is safer in any case.
|
|
if (currentCmd_)
|
|
{
|
|
// Assume that if this command is part of state reloading,
|
|
// then any further commands issued in command handler
|
|
// are part of state reloading as well.
|
|
if (stateReloadingCommands_.count(currentCmd_))
|
|
{
|
|
stateReloadInProgress_ = true;
|
|
}
|
|
currentCmd_->invokeHandler(result);
|
|
stateReloadInProgress_ = false;
|
|
}
|
|
}
|
|
else if (result.reason == "error")
|
|
{
|
|
// Some commands want to handle errors themself.
|
|
if (currentCmd_ && currentCmd_->handlesError() &&
|
|
currentCmd_->invokeHandler(result))
|
|
{
|
|
// Done, nothing more needed
|
|
}
|
|
else
|
|
{
|
|
defaultErrorHandler(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Data from gdb gets processed here.
|
|
void GDBController::slotDbgStdout(TDEProcess *, char *buf, int buflen)
|
|
{
|
|
static bool parsing = false;
|
|
|
|
TQCString msg(buf, buflen+1);
|
|
|
|
// Copy the data out of the TDEProcess buffer before it gets overwritten
|
|
// Append to the back of the holding zone.
|
|
holdingZone_ += TQCString(buf, buflen+1);
|
|
|
|
// Already parsing? then get out quick.
|
|
// VP, 2006-01-30. I'm not sure how this could happen, since
|
|
// parsing of gdb reply should not ever execute TQt message loop. Except,
|
|
// maybe, when we pop up a message box. But even in that case,
|
|
// it's likely we won't return to slotDbgStdout again.
|
|
if (parsing)
|
|
{
|
|
kdDebug(9012) << "Already parsing" << endl;
|
|
return;
|
|
}
|
|
|
|
bool ready_for_next_command = false;
|
|
|
|
int i;
|
|
bool got_any_command = false;
|
|
// For each gdb reply. In MI mode, each reply is one string.
|
|
while((i = holdingZone_.find('\n')) != -1)
|
|
{
|
|
got_any_command = true;
|
|
|
|
TQCString reply(holdingZone_.left(i));
|
|
holdingZone_ = holdingZone_.mid(i+1);
|
|
|
|
kdDebug(9012) << "REPLY: " << reply << "\n";
|
|
|
|
FileSymbol file;
|
|
file.contents = reply;
|
|
|
|
std::auto_ptr<GDBMI::Record> r(mi_parser_.parse(&file));
|
|
|
|
if (r.get() == 0)
|
|
{
|
|
// FIXME: Issue an error!
|
|
kdDebug(9012) << "Invalid MI message: " << reply << "\n";
|
|
ready_for_next_command = true;
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
|
|
switch(r->kind)
|
|
{
|
|
case GDBMI::Record::Result: {
|
|
|
|
GDBMI::ResultRecord& result = static_cast<GDBMI::ResultRecord&>(*r);
|
|
|
|
if (result.reason != "running")
|
|
{
|
|
kdDebug(9012) << "Command execution time "
|
|
<< commandExecutionTime.elapsed() << " ms.\n";
|
|
}
|
|
|
|
/* The currentCmd_ may be NULL here, because when detaching
|
|
from debugger, we directly write "detach" to gdb and
|
|
busy-wait for a reply. Uisng the commands mechanism
|
|
won't work there, because command result are
|
|
communicated asynchronously.
|
|
This is will be fixed in KDevelop4. */
|
|
if (currentCmd_ && currentCmd_->isUserCommand())
|
|
emit gdbUserCommandStdout(reply);
|
|
else
|
|
emit gdbInternalCommandStdout(reply + "\n");
|
|
|
|
if (result.reason == "stopped")
|
|
{
|
|
// Transfers ownership.
|
|
// Needed so that in
|
|
// handleMiFileListExecSourceFile(GDBMI::ResultRecord& r);
|
|
// we can use the last stop reason.
|
|
last_stop_result.reset(static_cast<GDBMI::ResultRecord*>(r.get()));
|
|
r.release();
|
|
state_reload_needed = true;
|
|
}
|
|
else if (result.reason == "running")
|
|
{
|
|
setStateOn(s_appRunning);
|
|
raiseEvent(program_running);
|
|
}
|
|
|
|
// All MI commands have just one response, except for
|
|
// run-like command, which result in
|
|
//
|
|
// ^running
|
|
//
|
|
// followed by
|
|
//
|
|
// stopped.
|
|
|
|
ready_for_next_command = (result.reason != "running");
|
|
if (ready_for_next_command)
|
|
{
|
|
// Need to do this before procesing response,
|
|
// so that when processing response we don't
|
|
// think that application is running.
|
|
setStateOff(s_appRunning);
|
|
}
|
|
|
|
processMICommandResponse(result);
|
|
|
|
|
|
if (ready_for_next_command)
|
|
{
|
|
destroyCurrentCommand();
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
case GDBMI::Record::Stream: {
|
|
|
|
GDBMI::StreamRecord& s = dynamic_cast<GDBMI::StreamRecord&>(*r);
|
|
/* The way current code works is that we start gdb,
|
|
and immediately send commands to it without waiting for
|
|
a prompt. As result, when we return back to the event
|
|
loop and read the first line from GDB, currentCmd_ is
|
|
already set. But really, we want to output everything
|
|
that gdb prints prior to prompt -- it might be
|
|
output from user's .gdbinit that user cares about. */
|
|
if (!saw_gdb_prompt_
|
|
|| !currentCmd_ || currentCmd_->isUserCommand())
|
|
emit gdbUserCommandStdout(s.message.ascii());
|
|
else
|
|
emit gdbInternalCommandStdout(s.message.ascii());
|
|
|
|
if (currentCmd_)
|
|
currentCmd_->newOutput(s.message);
|
|
|
|
parseCliLine(s.message);
|
|
|
|
static TQRegExp print_output("^\\$(\\d+) = ");
|
|
if (print_output.search(s.message) != -1)
|
|
{
|
|
kdDebug(9012) << "Found 'print' output: " << s.message << "\n";
|
|
print_command_result = s.message.ascii();
|
|
}
|
|
|
|
/* This is output from the program. Route it to
|
|
the Application window. */
|
|
if (s.reason == '@')
|
|
emit ttyStderr(s.message.ascii());
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
case GDBMI::Record::Prompt:
|
|
saw_gdb_prompt_ = true;
|
|
break;
|
|
}
|
|
}
|
|
catch(const std::exception& e)
|
|
{
|
|
KMessageBox::detailedSorry(
|
|
0,
|
|
i18n("<b>Internal debugger error</b>",
|
|
"<p>The debugger component encountered an internal error while "
|
|
"processing a reply from gdb. Please submit a bug report."),
|
|
i18n("The exception is: %1\n"
|
|
"The MI response is: %2").arg(e.what()).arg(reply.data()),
|
|
i18n("Internal debugger error"));
|
|
|
|
destroyCurrentCommand();
|
|
ready_for_next_command = true;
|
|
}
|
|
}
|
|
|
|
// check the queue for any commands to send
|
|
if (ready_for_next_command)
|
|
{
|
|
executeCmd();
|
|
}
|
|
|
|
if (got_any_command)
|
|
kdDebug(9012) << "COMMANDS: " << cmdList_.count() << " in queue, "
|
|
<< int(bool(currentCmd_)) << " executing\n";
|
|
|
|
commandDone();
|
|
}
|
|
|
|
void GDBController::commandDone()
|
|
{
|
|
bool no_more_commands = (cmdList_.isEmpty() && !currentCmd_);
|
|
|
|
if (no_more_commands && state_reload_needed)
|
|
{
|
|
kdDebug(9012) << "Finishing program stop\n";
|
|
// 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();
|
|
}
|
|
|
|
if (no_more_commands)
|
|
{
|
|
kdDebug(9012) << "No more commands\n";
|
|
setStateOff(s_dbgBusy);
|
|
emit dbgStatus("", state_);
|
|
raiseEvent(debugger_ready);
|
|
}
|
|
}
|
|
|
|
void GDBController::destroyCurrentCommand()
|
|
{
|
|
stateReloadingCommands_.erase(currentCmd_);
|
|
delete currentCmd_;
|
|
currentCmd_ = 0;
|
|
}
|
|
|
|
void GDBController::removeStateReloadingCommands()
|
|
{
|
|
int i = cmdList_.count();
|
|
while (i)
|
|
{
|
|
i--;
|
|
GDBCommand* cmd = cmdList_.at(i);
|
|
if (stateReloadingCommands_.count(cmd))
|
|
{
|
|
kdDebug(9012) << "UNQUEUE: " << cmd->initialString() << "\n";
|
|
delete cmdList_.take(i);
|
|
}
|
|
}
|
|
|
|
if (stateReloadingCommands_.count(currentCmd_))
|
|
{
|
|
// This effectively prevents handler for this command
|
|
// to be ever invoked.
|
|
destroyCurrentCommand();
|
|
}
|
|
}
|
|
|
|
void GDBController::raiseEvent(event_t e)
|
|
{
|
|
if (e == program_exited || e == debugger_exited)
|
|
{
|
|
stateReloadInProgress_ = false;
|
|
}
|
|
|
|
if (e == program_state_changed)
|
|
{
|
|
stateReloadInProgress_ = true;
|
|
kdDebug(9012) << "State reload in progress\n";
|
|
}
|
|
|
|
emit event(e);
|
|
|
|
if (e == program_state_changed)
|
|
{
|
|
stateReloadInProgress_ = false;
|
|
}
|
|
}
|
|
|
|
|
|
void GDBController::slotDbgStderr(TDEProcess *proc, char *buf, int buflen)
|
|
{
|
|
// At the moment, just drop a message out and redirect
|
|
kdDebug(9012) << "STDERR: " << TQString::fromLatin1(buf, buflen+1) << endl;
|
|
slotDbgStdout(proc, buf, buflen);
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotDbgWroteStdin(TDEProcess *)
|
|
{
|
|
commandExecutionTime.start();
|
|
|
|
setStateOff(s_waitForWrite);
|
|
|
|
// FIXME: need to remove s_waitForWrite flag completely.
|
|
executeCmd();
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotDbgProcessExited(TDEProcess* process)
|
|
{
|
|
Q_ASSERT(process == dbgProcess_);
|
|
bool abnormal = !process->normalExit();
|
|
delete dbgProcess_;
|
|
dbgProcess_ = 0;
|
|
delete tty_;
|
|
tty_ = 0;
|
|
|
|
if (abnormal)
|
|
emit debuggerAbnormalExit();
|
|
|
|
raiseEvent(debugger_exited);
|
|
|
|
destroyCmds();
|
|
setState(s_dbgNotStarted|s_appNotStarted|s_programExited);
|
|
emit dbgStatus (i18n("Process exited"), state_);
|
|
|
|
emit gdbUserCommandStdout("(gdb) Process exited\n");
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void GDBController::slotUserGDBCmd(const TQString& cmd)
|
|
{
|
|
queueCmd(new UserCommand(cmd.latin1()));
|
|
|
|
// 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 GDBController::explainDebuggerStatus()
|
|
{
|
|
TQString information("%1 commands in queue\n"
|
|
"%2 commands being processed by gdb\n"
|
|
"Debugger state: %3\n");
|
|
information =
|
|
information.arg(cmdList_.count()).arg(currentCmd_ ? 1 : 0)
|
|
.arg(state_);
|
|
|
|
if (currentCmd_)
|
|
{
|
|
TQString extra("Current command class: '%1'\n"
|
|
"Current command text: '%2'\n"
|
|
"Current command origianl text: '%3'\n");
|
|
|
|
extra = extra.arg(
|
|
typeid(*currentCmd_).name()).arg(currentCmd_->cmdToSend()).
|
|
arg(currentCmd_->initialString());
|
|
information += extra;
|
|
}
|
|
|
|
KMessageBox::information(0, information, "Debugger status");
|
|
}
|
|
|
|
bool GDBController::stateIsOn(int state)
|
|
{
|
|
return state_ & state;
|
|
}
|
|
|
|
void GDBController::setStateOn(int stateOn)
|
|
{
|
|
debugStateChange(state_, state_ | stateOn);
|
|
state_ |= stateOn;
|
|
}
|
|
|
|
void GDBController::setStateOff(int stateOff)
|
|
{
|
|
debugStateChange(state_, state_ & ~stateOff);
|
|
state_ &= ~stateOff;
|
|
}
|
|
|
|
void GDBController::setState(int newState)
|
|
{
|
|
debugStateChange(state_, newState);
|
|
state_ = newState;
|
|
}
|
|
|
|
void GDBController::debugStateChange(int oldState, int newState)
|
|
{
|
|
int delta = oldState ^ newState;
|
|
if (delta)
|
|
{
|
|
TQString out = "STATE: ";
|
|
for(unsigned i = 1; i < s_lastDbgState; i <<= 1)
|
|
{
|
|
if (delta & i)
|
|
{
|
|
if (i & newState)
|
|
out += "+";
|
|
else
|
|
out += "-";
|
|
|
|
bool found = false;
|
|
#define STATE_CHECK(name)\
|
|
if (i == name) { out += #name; found = true; }
|
|
STATE_CHECK(s_dbgNotStarted);
|
|
STATE_CHECK(s_appNotStarted);
|
|
STATE_CHECK(s_waitForWrite);
|
|
STATE_CHECK(s_programExited);
|
|
STATE_CHECK(s_viewBT);
|
|
STATE_CHECK(s_viewBP);
|
|
STATE_CHECK(s_attached);
|
|
STATE_CHECK(s_core);
|
|
STATE_CHECK(s_waitTimer);
|
|
STATE_CHECK(s_shuttingDown);
|
|
STATE_CHECK(s_explicitBreakInto);
|
|
STATE_CHECK(s_dbgBusy);
|
|
STATE_CHECK(s_appRunning);
|
|
#undef STATE_CHECK
|
|
|
|
if (!found)
|
|
out += TQString::number(i);
|
|
out += " ";
|
|
|
|
}
|
|
}
|
|
kdDebug(9012) << out << "\n";
|
|
}
|
|
}
|
|
|
|
int GDBController::qtVersion( ) const
|
|
{
|
|
return DomUtil::readIntEntry( dom, "/kdevcppsupport/qt/version", 3 );
|
|
}
|
|
|
|
void GDBController::demandAttention() const
|
|
{
|
|
if ( TQWidget * w = kapp->mainWidget() )
|
|
{
|
|
KWin::demandAttention( w->winId(), true );
|
|
}
|
|
}
|
|
|
|
bool GDBController::miPendingBreakpoints() const
|
|
{
|
|
return mi_pending_breakpoints_;
|
|
}
|
|
|
|
}
|
|
|
|
// **************************************************************************
|
|
// **************************************************************************
|
|
// **************************************************************************
|
|
|
|
#include "gdbcontroller.moc"
|