diff options
Diffstat (limited to 'languages/ruby/debugger/rdbcontroller.cpp')
-rw-r--r-- | languages/ruby/debugger/rdbcontroller.cpp | 1414 |
1 files changed, 1414 insertions, 0 deletions
diff --git a/languages/ruby/debugger/rdbcontroller.cpp b/languages/ruby/debugger/rdbcontroller.cpp new file mode 100644 index 00000000..160754a0 --- /dev/null +++ b/languages/ruby/debugger/rdbcontroller.cpp @@ -0,0 +1,1414 @@ +// ************************************************************************* +// rdbcontroller.cpp - description +// ------------------- +// begin : Sun Aug 8 1999 +// copyright : (C) 1999 by John Birch +// email : jbb@kdevelop.org +// +// Adapted for ruby debugging +// -------------------------- +// begin : Mon Nov 1 2004 +// copyright : (C) 2004 by Richard Dale +// email : Richard_Dale@tipitina.demon.co.uk +// ************************************************************************** +// +// ************************************************************************** +// * * +// * 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 "rdbcontroller.h" + +#include <sys/types.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <errno.h> + +#include "breakpoint.h" +#include "framestackwidget.h" +#include "rdbcommand.h" +#include "stty.h" +#include "variablewidget.h" +#include "domutil.h" +#include "settings.h" + +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kglobal.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kprocess.h> + +#include <qdatetime.h> +#include <qfileinfo.h> +#include <qregexp.h> +#include <qstring.h> +#include <qtextstream.h> + +#include <iostream> +#include <ctype.h> +#include <stdlib.h> +using namespace std; + +// ************************************************************************** +// +// Does all the communication between rdb and the kdevelop's debugger code. +// Significatant classes being used here are +// +// RDBParser - 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 +// +// +// ************************************************************************** + +namespace RDBDebugger +{ + +// This is here so we can check for startup /shutdown problems +int debug_controllerExists = false; + +// At the moment a Unix domain socket is used. It might be better to +// change to tcp/ip and listen on a port instead +QCString RDBController::unixSocketPath_; + + +RDBController::RDBController(VariableTree *varTree, FramestackWidget *frameStack, QDomDocument &projectDom) + : DbgController(), + frameStack_(frameStack), + varTree_(varTree), + currentFrame_(1), + viewedThread_(-1), + stdoutOutputLen_(0), + stdoutOutput_(new char[4096]), + holdingZone_(), + rdbOutputLen_(0), + rdbOutput_(new char[49152]), + socketNotifier_(0), + currentCmd_(0), + currentPrompt_("(rdb:1) "), + tty_(0), + state_(s_dbgNotStarted|s_appNotStarted|s_silent), + programHasExited_(false), + dom(projectDom), + config_forceBPSet_(true), + config_dbgTerminal_(false) +{ + struct sockaddr_un sockaddr; + unixSocketPath_.sprintf("/tmp/.rubydebugger%d", getpid()); + QFileInfo unixSocket(unixSocketPath_); + + stdoutSizeofBuf_ = sizeof(stdoutOutput_); + rdbSizeofBuf_ = sizeof(rdbOutput_); + + if (unixSocket.exists()) { + unlink(unixSocketPath_); + } + + masterSocket_ = socket(AF_UNIX, SOCK_STREAM, 0); + sockaddr.sun_family = AF_UNIX; + strcpy(sockaddr.sun_path, unixSocketPath_); + bind(masterSocket_, (const struct sockaddr*) &sockaddr, sizeof(sockaddr)); + listen(masterSocket_, 1); + acceptNotifier_ = new QSocketNotifier(masterSocket_, QSocketNotifier::Read, this); + QObject::connect( acceptNotifier_, SIGNAL(activated(int)), + this, SLOT(slotAcceptConnection(int)) ); + + configure(); + cmdList_.setAutoDelete(true); + + Q_ASSERT(! debug_controllerExists); + debug_controllerExists = true; +} + +// ************************************************************************** + +// Deleting the controller involves shutting down rdb nicely. +// When were attached to a process, we must first detach so that the process +// can continue running as it was before being attached. rdb is quite slow to +// detach from a process, so we must process events within here to get a "clean" +// shutdown. +RDBController::~RDBController() +{ + delete[] stdoutOutput_; + delete[] rdbOutput_; + debug_controllerExists = false; + + QFileInfo unixSocket(unixSocketPath_); + if (unixSocket.exists()) { + unlink(unixSocketPath_); + } +} + +// ************************************************************************** + +void RDBController::configure() +{ +} + +// ************************************************************************** + +// 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 RDBController::queueCmd(DbgCommand *cmd, bool executeNext) +{ + // We remove any info command or _run_ command if we are about to + // add a run command. + if (cmd->isARunCmd()) + removeInfoRequests(); + + if (executeNext) + cmdList_.insert(0, cmd); + else + cmdList_.append (cmd); +} + +// ************************************************************************** + +// 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 rdbs state in someway) +// or they can be "run" commands. If a command is sent to rdb our internal +// state will get updated. +void RDBController::executeCmd() +{ + if (stateIsOn(s_dbgNotStarted|s_waitForWrite|s_appBusy|s_shuttingDown) || !dbgProcess_) + return; + + if (currentCmd_ == 0) { + if (cmdList_.isEmpty()) + return; + + currentCmd_ = cmdList_.take(0); + } + + if (!currentCmd_->moreToSend()) { + delete currentCmd_; + if (cmdList_.isEmpty()) { + currentCmd_ = 0; + return; + } + + currentCmd_ = cmdList_.take(0); + } + + char * ptr = currentCmd_->cmdToSend().data(); + int bytesToWrite = currentCmd_->cmdLength(); + int bytesWritten = 0; + + while (bytesToWrite > 0) { + bytesWritten = write(socket_, ptr, bytesToWrite); + bytesToWrite -= bytesWritten; + ptr += bytesWritten; + } + + if (currentCmd_->isARunCmd()) { + setStateOn(s_appBusy); + kdDebug(9012) << "App is busy" << endl; + setStateOff(s_appNotStarted|s_programExited|s_silent); + } + + QString prettyCmd = currentCmd_->cmdToSend(); + prettyCmd = currentPrompt_ + prettyCmd; + emit rdbStdout( prettyCmd.latin1() ); + + if (!stateIsOn(s_silent)) + emit dbgStatus("", state_); +} + +// ************************************************************************** + +void RDBController::destroyCmds() +{ + if (currentCmd_) + { + delete currentCmd_; + currentCmd_ = 0; + } + + while (!cmdList_.isEmpty()) + delete cmdList_.take(0); +} + +// ********************************************************************** + +void RDBController::removeInfoRequests() +{ + int i = cmdList_.count(); + while (i) + { + i--; + DbgCommand *cmd = cmdList_.at(i); + if (cmd->isAnInfoCmd() || cmd->isARunCmd()) + delete cmdList_.take(i); + } +} + +// ********************************************************************** + +// 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 RDBController::pauseApp() +{ + int i = cmdList_.count(); + while (i) + { + i--; + DbgCommand *cmd = cmdList_.at(i); + if ((stateIsOn(s_silent) && cmd->isAnInfoCmd()) || cmd->isARunCmd()) + delete cmdList_.take(i); + } + + if (dbgProcess_ && stateIsOn(s_appBusy)) + dbgProcess_->kill(SIGINT); +} + +// ********************************************************************** + +// Whenever the program pauses we need to refresh the data visible to +// the user. The reason we've stopped may be passed in to be emitted. +void RDBController::actOnProgramPause(const QString &msg) +{ + // We're only stopping if we were running, of course. + if (stateIsOn(s_appBusy)) + { + kdDebug(9012) << "App is paused" << endl; + setStateOff(s_appBusy); + if (stateIsOn(s_silent)) + return; + + emit dbgStatus (msg, state_); + + // We're always at frame one when the program stops + // and we must reset the active flag + currentFrame_ = 1; + varTree_->nextActivationId(); + setStateOn(s_fetchLocals); + + queueCmd(new RDBCommand("where", NOTRUNCMD, INFOCMD), true); + queueCmd(new RDBCommand("thread list", NOTRUNCMD, INFOCMD), true); + + if (stateIsOn(s_fetchGlobals)) { + queueCmd(new RDBCommand("var global", NOTRUNCMD, INFOCMD)); + } + + emit acceptPendingBPs(); + } +} + +// ************************************************************************** + +// There is no app anymore. This can be caused by program exiting +// an invalid program specified or ... +// rdb is still running though, but only the run command (may) make sense +// all other commands are disabled. +void RDBController::programNoApp(const QString &msg, bool msgBox) +{ + state_ = (s_appNotStarted|s_programExited|(state_&(s_shuttingDown))); + destroyCmds(); + + // We're always at frame one when the program stops + // and we must reset the active flag + viewedThread_ = -1; + currentFrame_ = 1; + varTree_->nextActivationId(); + + // Now wipe the tree out + varTree_->viewport()->setUpdatesEnabled(false); + varTree_->prune(); + varTree_->viewport()->setUpdatesEnabled(true); + varTree_->repaint(); + + frameStack_->clear(); + + if (msgBox) + KMessageBox::error(0, i18n("rdb message:\n")+msg); + + emit dbgStatus (msg, state_); +} + +// ************************************************************************** + +// The program location falls out of rdb. We treat +// it as a wrapped command. +// The data gets parsed here and emitted in its component parts. +void RDBController::parseProgramLocation(char *buf) +{ + QString buffer(buf); + QString line; + QTextStream input(&buffer, IO_ReadOnly); + QString sourceFile; + int sourceLine = 0; + + // "1: a = 1" + QRegExp display_re("^(\\d+):\\s(.*)$"); + + // "/opt/qt/src/widgets/qlistview.rb:1558:puts 'hello world'" + QRegExp sourcepos_re("^([^:]+):(\\d+):"); + + line = input.readLine(); + while (! line.isNull()) { + if (sourcepos_re.search(line, 0) >= 0) { + sourceFile = sourcepos_re.cap(1); + sourceLine = sourcepos_re.cap(2).toInt(); + } else if (display_re.search(line, 0) >= 0) { + varTree_->watchRoot()->updateWatchExpression(display_re.cap(1).toInt(), display_re.cap(2)); + } + + line = input.readLine(); + } + + if ( !sourceFile.isNull() + && ( traceIntoRuby_ + || ( !sourceFile.endsWith("/qtruby.rb") + && !sourceFile.endsWith("/korundum.rb") ) ) + && !sourceFile.endsWith("/debuggee.rb") ) + { + actOnProgramPause(QString()); + emit showStepInSource(sourceFile, sourceLine, ""); + return; + } + + if (stateIsOn(s_appBusy)) + actOnProgramPause(i18n("No source: %1").arg(sourceFile)); + else + emit dbgStatus (i18n("No source: %1").arg(sourceFile), state_); +} + +// ************************************************************************** + +// parsing the backtrace list will cause the vartree to be refreshed +void RDBController::parseBacktraceList(char *buf) +{ + frameStack_->parseRDBBacktraceList(buf); +} + +// ************************************************************************** + +void RDBController::parseThreadList(char *buf) +{ + frameStack_->parseRDBThreadList(buf); + viewedThread_ = frameStack_->viewedThread(); + varTree_->setCurrentThread(viewedThread_); +} + +// ************************************************************************** + +void RDBController::parseSwitchThread(char *buf) +{ + // Look for the thread number + // 2 #<Thread:0x30091998 sleep> /home/duke/play/testit/trykorundum/src/bar.rb:13 + QRegExp thread_re("(\\d+)"); + if (thread_re.search(buf) != -1) { + viewedThread_ = thread_re.cap(1).toInt(); + currentFrame_ = 1; + } +} + +// ************************************************************************** + +// After an 'up nnn' or 'down nnn' command, get the new source file and line no. +void RDBController::parseFrameMove(char *buf) +{ + QString sourceFile; + int sourceLine = 0; + + if (stateIsOn(s_fetchLocals)) { + return; + } + + // "#2 /home/duke/play/testit/trykorundum/src/main.rb:11" + QRegExp sourcepos_re("#\\d+\\s([^:]+):(\\d+)"); + if (sourcepos_re.search(buf) != -1) { + sourceFile = sourcepos_re.cap(1); + sourceLine = sourcepos_re.cap(2).toInt(); + + if ( !sourceFile.isNull() + && ( traceIntoRuby_ + || ( !sourceFile.endsWith("/qtruby.rb") + && !sourceFile.endsWith("/korundum.rb") ) ) + && !sourceFile.endsWith("/debuggee.rb") ) + { + emit showStepInSource(sourceFile, sourceLine, ""); + return; + } + } + + emit dbgStatus(i18n("No source: %1").arg(sourceFile), state_); +} + +// ************************************************************************** + +// When a breakpoint has been set, rdb responds with some data about the +// new breakpoint. We just inform the breakpoint system about this. +void RDBController::parseBreakpointSet(char *buf) +{ + if (RDBSetBreakpointCommand *BPCmd = dynamic_cast<RDBSetBreakpointCommand*>(currentCmd_)) + { + // ... except in this case :-) A -1 key tells us that this is + // a special internal breakpoint, and we shouldn't do anything + // with it. Currently there are _no_ internal breakpoints. + if (BPCmd->getKey() != -1) { + emit rawRDBBreakpointSet(buf, BPCmd->getKey()); + } + } +} + +// ************************************************************************** + +// Extra data needed by an item was requested. Here's the result. +// If it's an ordinary 'p ' command then just echo the result on +// the RDB console and don't bother parsing. +void RDBController::parseRequestedData(char *buf) +{ + if (RDBItemCommand *rdbItemCommand = dynamic_cast<RDBItemCommand*> (currentCmd_)) + { + // Fish out the item from the command and let it deal with the data + VarItem *item = rdbItemCommand->getItem(); + varTree_->viewport()->setUpdatesEnabled(false); + item->expandValue(buf); + varTree_->viewport()->setUpdatesEnabled(true); + varTree_->repaint(); + } +} + + +// ************************************************************************** + +// Select a different frame to view. We need to get and (maybe) display +// where we are in the program source. +void RDBController::parseFrameSelected(char *buf) +{ + if (!stateIsOn(s_silent)) { + emit showStepInSource("", -1, ""); + emit dbgStatus (i18n("No source: %1").arg(QString(buf)), state_); + } +} + +// ************************************************************************** + +// Sets the id of the display in the VarTree and a current value. +void RDBController::parseDisplay(char *buf, char * expr) +{ + varTree_->viewport()->setUpdatesEnabled(false); + varTree_->watchRoot()->setWatchExpression(buf, expr); + varTree_->viewport()->setUpdatesEnabled(true); + varTree_->repaint(); +} + +// ************************************************************************** + +// Updates the watch expressions with current values +void RDBController::parseUpdateDisplay(char *buf) +{ + varTree_->viewport()->setUpdatesEnabled(false); + + QRegExp display_re("(\\d+):\\s([^\n]*)\n"); + + int pos = display_re.search(buf); + while (pos != -1) { + varTree_->watchRoot()->updateWatchExpression(display_re.cap(1).toInt(), display_re.cap(2)); + + pos += display_re.matchedLength(); + pos = display_re.search(buf, pos); + } + + varTree_->viewport()->setUpdatesEnabled(true); + varTree_->repaint(); +} + +// ************************************************************************** + +// This is called on program stop to process the globals. +void RDBController::parseGlobals(char *buf) +{ + varTree_->viewport()->setUpdatesEnabled(false); + varTree_->globalRoot()->setGlobals(buf); + varTree_->viewport()->setUpdatesEnabled(true); + varTree_->repaint(); +} + +// ************************************************************************** + +// This is called on program stop to process the locals. +// Once the locals have been processed we prune the tree of items that are +// inactive. +void RDBController::parseLocals(char type, char *buf) +{ + varTree_->viewport()->setUpdatesEnabled(false); + + // The locals are always attached to the currentFrame + VarFrameRoot *frame = varTree_->findFrame(currentFrame_, viewedThread_); + if (!frame) + { + frame = new VarFrameRoot(varTree_, currentFrame_, viewedThread_); + frame->setFrameName( + frameStack_->findFrame(currentFrame_, viewedThread_)->frameName()); + } + + Q_ASSERT(frame); + + if (type == (char) CONSTANTS) { + frame->addLocals(buf); + } else if (type == (char) CVARS) { + frame->addLocals(buf); + } else if (type == (char) IVARS) { + frame->addLocals(buf); + } else { + frame->addLocals(buf); + frame->setLocals(); + } + + varTree_->viewport()->setUpdatesEnabled(true); + varTree_->repaint(); +} + + + +// ************************************************************************** + +void RDBController::parse(char *buf) +{ + if (currentCmd_ == 0) { + return; + } + + if (currentCmd_->isARunCmd()) { + parseProgramLocation(buf); + } else if (currentCmd_->rawDbgCommand() == "break") { + emit rawRDBBreakpointList(buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "break ", strlen("break ")) == 0) { + parseBreakpointSet(buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "watch ", strlen("watch ")) == 0) { + parseBreakpointSet(buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "display ", strlen("display ")) == 0) { + parseDisplay(buf, currentCmd_->rawDbgCommand().data() + strlen("display ")); + } else if (currentCmd_->rawDbgCommand() == "display") { + parseUpdateDisplay(buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "undisplay ", strlen("undisplay ")) == 0) { + ; + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "method instance ", strlen("method instance ")) == 0) { + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "method ", strlen("method ")) == 0) { + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "pp ", strlen("pp ")) == 0) { + parseRequestedData(buf); + } else if (currentCmd_->rawDbgCommand() == "thread list") { + parseThreadList(buf); + } else if ( qstrncmp(currentCmd_->rawDbgCommand(), "up ", strlen("up ")) == 0 + || qstrncmp(currentCmd_->rawDbgCommand(), "down ", strlen("down ")) == 0 ) + { + parseFrameMove(buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "thread switch ", strlen("thread switch ")) == 0) { + parseSwitchThread(buf); + } else if (currentCmd_->rawDbgCommand() == "thread current") { + parseThreadList(buf); + } else if (currentCmd_->rawDbgCommand() == "where") { + parseBacktraceList(buf); + } else if (currentCmd_->rawDbgCommand() == "var global") { + parseGlobals(buf); + } else if (currentCmd_->rawDbgCommand() == "var local") { + parseLocals(LOCALS, buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "var instance ", strlen("var instance ")) == 0) { + parseLocals(IVARS, buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "var class ", strlen("var class ")) == 0) { + parseLocals(CVARS, buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "var const ", strlen("var const ")) == 0) { + parseLocals(CONSTANTS, buf); + } + + return; +} + +// ************************************************************************** + +void RDBController::setBreakpoint(const QCString &BPSetCmd, int key) +{ + queueCmd(new RDBSetBreakpointCommand(BPSetCmd, key)); +} + +// ************************************************************************** + +void RDBController::clearBreakpoint(const QCString &BPClearCmd) +{ + queueCmd(new RDBCommand(BPClearCmd, NOTRUNCMD, NOTINFOCMD)); + // Note: this is NOT an info command, because rdb doesn't explictly tell + // us that the breakpoint has been deleted, so if we don't have it the + // BP list doesn't get updated. + queueCmd(new RDBCommand("break", NOTRUNCMD, NOTINFOCMD)); +} + +// ************************************************************************** + +void RDBController::modifyBreakpoint( const Breakpoint& BP ) +{ + Q_ASSERT(BP.isActionModify()); + if (BP.dbgId() > 0) + { + if (BP.changedEnable()) + queueCmd(new RDBCommand(QCString().sprintf("%s %d", + BP.isEnabled() ? "enable" : "disable", + BP.dbgId()), NOTRUNCMD, NOTINFOCMD)); + + // BP.setDbgProcessing(true); + // Note: this is NOT an info command, because rdb doesn't explictly tell + // us that the breakpoint has been deleted, so if we don't have it the + // BP list doesn't get updated. + queueCmd(new RDBCommand("break", NOTRUNCMD, NOTINFOCMD)); + } +} + +// ************************************************************************** +// SLOTS +// ***** +// For most of these slots data can only be sent to rdb when it +// isn't busy and it is running. + +// ************************************************************************** + +void RDBController::slotStart(const QString& ruby_interpreter, const QString& character_coding, const QString& run_directory, const QString& debuggee_path, const QString &application, const QString& run_arguments, bool show_constants, bool trace_into_ruby) +{ + Q_ASSERT (!dbgProcess_ && !tty_); + +// tty_ = new STTY(config_dbgTerminal_, "konsole"); + tty_ = new STTY(config_dbgTerminal_, Settings::terminalEmulatorName( *kapp->config() )); + if (!config_dbgTerminal_) + { + connect( tty_, SIGNAL(OutOutput(const char*)), SIGNAL(ttyStdout(const char*)) ); + connect( tty_, SIGNAL(ErrOutput(const char*)), SIGNAL(ttyStderr(const char*)) ); + } + + QString tty(tty_->getSlave()); + if (tty.isEmpty()) + { + KMessageBox::error(0, i18n("The ruby debugger 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\".")); + + delete tty_; + tty_ = 0; + return; + } + + dbgProcess_ = new KProcess; + + connect( dbgProcess_, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotDbgStdout(KProcess *, char *, int)) ); + + connect( dbgProcess_, SIGNAL(receivedStderr(KProcess *, char *, int)), + this, SLOT(slotDbgStderr(KProcess *, char *, int)) ); + + connect( dbgProcess_, SIGNAL(wroteStdin(KProcess *)), + this, SLOT(slotDbgWroteStdin(KProcess *)) ); + + connect( dbgProcess_, SIGNAL(processExited(KProcess*)), + this, SLOT(slotDbgProcessExited(KProcess*)) ); + + rubyInterpreter_ = ruby_interpreter; + characterCoding_ = character_coding; + runDirectory_ = run_directory; + debuggeePath_ = debuggee_path; + application_ = application; + runArguments_ = run_arguments; + showConstants_ = show_constants; + traceIntoRuby_ = trace_into_ruby; + + *dbgProcess_ << ruby_interpreter; + *dbgProcess_ << character_coding; + *dbgProcess_ << "-C" << QString(QFile::encodeName( run_directory )); + *dbgProcess_ << "-r" << debuggee_path; + *dbgProcess_ << application; + + if (!run_arguments.isNull() && !run_arguments.isEmpty()) { + *dbgProcess_ << run_arguments; + } + + emit rdbStdout(QString( ruby_interpreter + " " + character_coding + + " -C " + QString(QFile::encodeName( run_directory )) + + " -r " + debuggee_path + " " + + application + " " + run_arguments ).latin1() ); + + if (!dbgProcess_->start( KProcess::NotifyOnExit, + KProcess::Communication(KProcess::All)) ) + { + kdDebug(9012) << "Couldn't start ruby debugger" << endl; + } + + // Initialise rdb. At this stage rdb 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 + + // Now the ruby debugger 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. + + if (!dbgProcess_->writeStdin(QString("%1\n").arg(unixSocketPath_).latin1(), strlen(unixSocketPath_) + 1)) { + kdDebug(9012) << "failed to write Unix domain socket path to rdb " + << QString("%1\n").arg(unixSocketPath_).latin1() << endl; + } + + setStateOff(s_programExited); + setStateOn(s_dbgNotStarted|s_appNotStarted|s_silent); +} + +// ************************************************************************** + +void RDBController::slotStopDebugger() +{ + if (stateIsOn(s_shuttingDown) || !dbgProcess_) + return; + + setStateOn(s_shuttingDown|s_silent); + destroyCmds(); + + QTime start; + QTime now; + + // Get rdb's attention if it's busy. We need rdb to be at the + // command line so we can stop it. + if (stateIsOn(s_appBusy)) + { + kdDebug(9012) << "ruby debugger busy on shutdown - stopping rdb (SIGINT)" << endl; + dbgProcess_->kill(SIGINT); + start = QTime::currentTime(); + while (-1) + { + kapp->processEvents(20); + now = QTime::currentTime(); + if (!stateIsOn(s_appBusy) || start.msecsTo( now ) > 2000) + break; + } + } + + + // Now try to stop the ruby debugger running. + kdDebug(9012) << "App is busy" << endl; + setStateOn(s_appBusy); + const char *quit="quit\n"; + if (!dbgProcess_->writeStdin(quit, strlen(quit))) + kdDebug(9012) << "failed to write 'quit' to ruby debugger" << endl; + + emit rdbStdout("(rdb:1) quit"); + start = QTime::currentTime(); + while (-1) + { + kapp->processEvents(20); + now = QTime::currentTime(); + if (stateIsOn(s_programExited) || start.msecsTo( now ) > 2000) + break; + } + + // We cannot wait forever. + if (!stateIsOn(s_programExited)) + { + kdDebug(9012) << "rdb not shutdown - killing" << endl; + dbgProcess_->kill(SIGKILL); + } + + delete dbgProcess_; dbgProcess_ = 0; + delete tty_; tty_ = 0; + + state_ = s_dbgNotStarted | s_appNotStarted | s_silent; + emit dbgStatus (i18n("Debugger stopped"), state_); +} + + + +// ************************************************************************** + +void RDBController::slotRun() +{ + if (stateIsOn(s_appBusy|s_dbgNotStarted|s_shuttingDown)) + return; + + if (stateIsOn(s_programExited)) { + slotStart(rubyInterpreter_, characterCoding_, runDirectory_, debuggeePath_, application_, runArguments_, showConstants_, traceIntoRuby_); + return; + } + + queueCmd(new RDBCommand("cont", RUNCMD, NOTINFOCMD)); + if (currentCmd_ == 0) { + executeCmd(); + } +} + +// ************************************************************************** + +void RDBController::slotRunUntil(const QString &fileName, int lineNum) +{ + if (stateIsOn(s_appBusy|s_dbgNotStarted|s_shuttingDown)) + return; + + if (fileName.isEmpty()) + queueCmd(new RDBCommand( QCString().sprintf("break %d", lineNum), + RUNCMD, NOTINFOCMD)); + else + queueCmd(new RDBCommand( + QCString().sprintf("break %s:%d", fileName.latin1(), lineNum), + RUNCMD, NOTINFOCMD)); + queueCmd(new RDBCommand("cont", RUNCMD, NOTINFOCMD)); + + if (currentCmd_ == 0) { + executeCmd(); + } +} + +// ************************************************************************** + +void RDBController::slotStepInto() +{ + if (stateIsOn(s_appBusy|s_appNotStarted|s_shuttingDown)) + return; + + queueCmd(new RDBCommand("step", RUNCMD, NOTINFOCMD)); + if (currentCmd_ == 0) { + executeCmd(); + } +} + + +// ************************************************************************** + +void RDBController::slotStepOver() +{ + if (stateIsOn(s_appBusy|s_appNotStarted|s_shuttingDown)) + return; + + queueCmd(new RDBCommand("next", RUNCMD, NOTINFOCMD)); + if (currentCmd_ == 0) { + executeCmd(); + } +} + + +// ************************************************************************** + +void RDBController::slotStepOutOff() +{ + if (stateIsOn(s_appBusy|s_appNotStarted|s_shuttingDown)) + return; + + queueCmd(new RDBCommand("finish", RUNCMD, NOTINFOCMD)); + if (currentCmd_ == 0) { + executeCmd(); + } +} + +// ************************************************************************** + +// Only interrupt a running program. +void RDBController::slotBreakInto() +{ + pauseApp(); +} + +// ************************************************************************** + +// See what, if anything needs doing to this breakpoint. +void RDBController::slotBPState( const Breakpoint& BP ) +{ + // Are we in a position to do anything to this breakpoint? + if (stateIsOn(s_dbgNotStarted|s_shuttingDown) || !BP.isPending() || + BP.isActionDie()) + return; + + // We need this flag so that we can continue execution. I did use + // the s_silent state flag but it can be set prior to this method being + // called, hence is invalid. + bool restart = false; + if (stateIsOn(s_appBusy)) + { + if (!config_forceBPSet_) + return; + + // When forcing breakpoints to be set/unset, interrupt a running app + // and change the state. + setStateOn(s_silent); + pauseApp(); + restart = true; + } + + if (BP.isActionAdd()) + { + setBreakpoint(BP.dbgSetCommand().latin1(), BP.key()); + // BP.setDbgProcessing(true); + } + else + { + if (BP.isActionClear()) + { + clearBreakpoint(BP.dbgRemoveCommand().latin1()); + // BP.setDbgProcessing(true); + } + else + { + if (BP.isActionModify()) + { + modifyBreakpoint(BP); // Note: DbgProcessing gets set in modify fn + } + } + } + + if (restart) + queueCmd(new RDBCommand("cont", RUNCMD, NOTINFOCMD)); +} + +// ************************************************************************** + +void RDBController::slotClearAllBreakpoints() +{ + // Are we in a position to do anything to this breakpoint? + if (stateIsOn(s_dbgNotStarted|s_shuttingDown)) + return; + + bool restart = false; + if (stateIsOn(s_appBusy)) + { + if (!config_forceBPSet_) + return; + + // When forcing breakpoints to be set/unset, interrupt a running app + // and change the state. + setStateOn(s_silent); + pauseApp(); + restart = true; + } + + queueCmd(new RDBCommand("delete", NOTRUNCMD, NOTINFOCMD)); + // Note: this is NOT an info command, because rdb doesn't explictly tell + // us that the breakpoint has been deleted, so if we don't have it the + // BP list doesn't get updated. + queueCmd(new RDBCommand("break", NOTRUNCMD, NOTINFOCMD)); + + if (restart) + queueCmd(new RDBCommand("cont", RUNCMD, NOTINFOCMD)); + + executeCmd(); +} + + + +// ************************************************************************** + +void RDBController::slotSelectFrame(int frameNo, int threadNo, const QString& frameName) +{ + if (stateIsOn(s_appBusy|s_dbgNotStarted|s_shuttingDown)) { + kdDebug(9012) << "RDBController::slotSelectFrame wrong state" << endl; + return; + } + + if (viewedThread_ != threadNo) { + // Note that 'thread switch nnn' is a run command + queueCmd(new RDBCommand(QCString().sprintf("thread switch %d", + threadNo), RUNCMD, INFOCMD)); + executeCmd(); + return; + } + + if (frameNo > currentFrame_) { + queueCmd(new RDBCommand(QCString().sprintf("up %d", frameNo - currentFrame_), NOTRUNCMD, INFOCMD)); + if (!stateIsOn(s_fetchLocals)) { + queueCmd(new RDBCommand("display", NOTRUNCMD, INFOCMD)); + } + } else if (frameNo < currentFrame_) { + queueCmd(new RDBCommand(QCString().sprintf("down %d", currentFrame_ - frameNo), NOTRUNCMD, INFOCMD)); + if (!stateIsOn(s_fetchLocals)) { + queueCmd(new RDBCommand("display", NOTRUNCMD, INFOCMD)); + } + } + + // Hold on to this thread/frame so that we know where to put the local + // variables if generated. + viewedThread_ = threadNo; + currentFrame_ = frameNo; + + VarFrameRoot *frame = varTree_->findFrame(frameNo, viewedThread_); + if (frame == 0) { + frame = new VarFrameRoot(varTree_, currentFrame_, viewedThread_); + } + + frame->setFrameName(frameName); + varTree_->setSelected(frame, true); + + // Have we already got these details? + if (frame->needsVariables()) { + // Ask for the locals + + if (showConstants_) { + queueCmd(new RDBCommand("var const self.class", NOTRUNCMD, INFOCMD)); + } + + queueCmd(new RDBCommand("var instance self", NOTRUNCMD, INFOCMD)); + queueCmd(new RDBCommand("var class self.class", NOTRUNCMD, INFOCMD)); + queueCmd(new RDBCommand("var local", NOTRUNCMD, INFOCMD)); + frame->startWaitingForData(); + } + + if (currentCmd_ == 0) { + executeCmd(); + } + + return; +} + + + +// ************************************************************************** + +// This is called when an item needs special processing to show a value. +void RDBController::slotExpandItem(VarItem *item, const QCString &userRequest) +{ + if (stateIsOn(s_appBusy|s_dbgNotStarted|s_shuttingDown)) + return; + + Q_ASSERT(item != 0); + + // Bad user data!! + if (userRequest.isEmpty()) + return; + + queueCmd(new RDBItemCommand(item, QCString("pp ") + userRequest.data(), false)); + + if (currentCmd_ == 0) { + executeCmd(); + } +} + +// ************************************************************************** + +// This method evaluates text selected with the 'Inspect:' context menu +void RDBController::slotRubyInspect(const QString &inspectText) +{ + queueCmd(new RDBCommand( QCString().sprintf("p %s", inspectText.latin1()), + NOTRUNCMD, + INFOCMD ), true ); + executeCmd(); +} + + +// ************************************************************************** + +// Add a new expression to be displayed in the Watch variable tree +void RDBController::slotAddWatchExpression(const QString& expr, bool execute) +{ + queueCmd(new RDBCommand( QCString().sprintf("display %s", expr.latin1()), + NOTRUNCMD, + NOTINFOCMD ) ); + if (execute) { + executeCmd(); + } +} + +// ************************************************************************** + +// Add a new expression to be displayed in the Watch variable tree +void RDBController::slotRemoveWatchExpression(int displayId) +{ + queueCmd(new RDBCommand( QCString().sprintf("undisplay %d", displayId), + NOTRUNCMD, + INFOCMD ) ); + executeCmd(); +} + + +// ************************************************************************** + +// The user will only get globals if the Global frame is open +void RDBController::slotFetchGlobals(bool fetch) +{ + if (fetch) { + setStateOn(s_fetchGlobals); + queueCmd(new RDBCommand("var global", NOTRUNCMD, INFOCMD)); + executeCmd(); + } else { + setStateOff(s_fetchGlobals); + } + + kdDebug(9012) << (fetch ? "<Globals ON>": "<Globals OFF>") << endl; +} + +// ************************************************************************** + +// Data from the ruby program's stdout gets processed here. +void RDBController::slotDbgStdout(KProcess *, char *buf, int buflen) +{ + QCString msg(buf, buflen+1); + emit ttyStdout(msg); +} + +// ************************************************************************** + +// Data from the ruby program's stderr gets processed here. +void RDBController::slotDbgStderr(KProcess *, char *buf, int buflen) +{ + QCString msg(buf, buflen+1); + emit ttyStderr(msg); +} + +// ************************************************************************** + +void RDBController::slotDbgWroteStdin(KProcess *) +{ +// setStateOff(s_waitForWrite); + // if (!stateIsOn(s_silent)) + // emit dbgStatus ("", state_); +// executeCmd(); +} + +// ************************************************************************** + +void RDBController::slotAcceptConnection(int masterSocket) +{ + Q_ASSERT(masterSocket == masterSocket_); + + struct sockaddr sockaddr; + socklen_t fromlen; + + if (socketNotifier_ != 0) { + close(socket_); + delete socketNotifier_; + } + + socket_ = accept(masterSocket, &sockaddr, &fromlen); + if (fcntl(socket_, F_SETFL, O_NONBLOCK) == -1) { + kdDebug(9012) << "RDBController::slotAcceptConnection can't set nonblocking socket " << errno << endl; + } + + socketNotifier_ = new QSocketNotifier(socket_, QSocketNotifier::Read, 0); + QObject::connect( socketNotifier_, SIGNAL(activated(int)), + this, SLOT(slotReadFromSocket(int)) ); + + setStateOff(s_dbgNotStarted); + emit dbgStatus ("", state_); + + cmdList_.clear(); + rdbOutputLen_ = 0; + + // Organise any breakpoints. + emit acceptPendingBPs(); + + if (traceIntoRuby_) { + queueCmd(new RDBCommand("trace_ruby on", NOTRUNCMD, NOTINFOCMD)); + } + + queueCmd(new RDBCommand("cont", RUNCMD, NOTINFOCMD)); + + // Reset the display id for any watch expressions already in the variable tree + varTree_->resetWatchVars(); +} + +// ************************************************************************** + +// Output from rdb via the Unix socket gets processed here. +void RDBController::slotReadFromSocket(int socket) +{ + Q_ASSERT(socket == socket_); + + static bool parsing = false; + + int bytesRead = read(socket, rdbOutput_ + rdbOutputLen_, rdbSizeofBuf_); + + rdbOutputLen_ += bytesRead; + *(rdbOutput_ + rdbOutputLen_) = 0; + + + // Already parsing? then get out quick. + if (parsing) + { + kdDebug(9012) << "Already parsing" << endl; + return; + } + +// kdDebug(9012) << "RDBController::slotReadFromSocket length: " << rdbOutputLen_ << " input: " << rdbOutput_ << endl; + + QRegExp prompt_re("(\\(rdb:(\\d+)\\) )$"); + int promptPos = prompt_re.search(rdbOutput_, 0); + + // Keep appending output to the rbdOutput_ buffer until the + // ruby debugger writes the next prompt + if (promptPos == -1) { + return; + } + +// kdDebug(9012) << "RDBController::slotReadFromSocket length: " << rdbOutputLen_ << " input: " << rdbOutput_ << endl; + + // Save the prompt, and remove it from the buffer + currentPrompt_ = prompt_re.cap(1).latin1(); + rdbOutputLen_ -= prompt_re.matchedLength(); + *(rdbOutput_ + rdbOutputLen_) = 0; + + emit rdbStdout(rdbOutput_); + + parsing = true; + parse(rdbOutput_); + parsing = false; + rdbOutputLen_ = 0; + + executeCmd(); + + if (currentCmd_ == 0 && stateIsOn(s_fetchLocals)) { + if (!varTree_->schedule()) { + setStateOff(s_fetchLocals); + } + } +} + +// ************************************************************************** + +void RDBController::slotDbgProcessExited(KProcess*) +{ + destroyCmds(); + state_ = s_appNotStarted|s_programExited|(state_&(s_shuttingDown)); + emit dbgStatus (i18n("Process exited"), state_); + emit rdbStdout("(rdb:1) Process exited\n"); + frameStack_->clear(); + varTree_->clear(); + + if (socketNotifier_ != 0) { + delete socketNotifier_; + socketNotifier_ = 0; + close(socket_); + } + + delete dbgProcess_; dbgProcess_ = 0; + delete tty_; tty_ = 0; +} + + +// ************************************************************************** + +// Takes abbreviated commands and expands them, before passing them on to rdb +// +void RDBController::slotUserRDBCmd(const QString& cmd) +{ + kdDebug(9012) << "Requested user cmd: " << cmd << endl; + QRegExp break_re("^b(reak)?(\\s.*)?"); + QRegExp watch_re("^wat(ch)?\\s+(.*)"); + QRegExp delete_re("^del(ete)?(\\s.*)?"); + QRegExp display_re("^disp(lay)?(\\s.*)?"); + QRegExp undisplay_re("^undisp(lay)?(\\s.*)?"); + QRegExp step_re("^s(tep)?(\\s[\\d]+)?$"); + QRegExp next_re("^n(ext)?(\\s[\\d]+)?$"); + QRegExp varlocal_re("^v(ar)?\\s+l(ocal)?"); + QRegExp varglobal_re("^v(ar)?\\s+g(lobal)?"); + QRegExp varinstance_re("^v(ar)?\\s+i(nstance)?\\s(.*)"); + QRegExp varconst_re("^v(ar)?\\s+c(onst)?\\s(.*)"); + QRegExp threadlist_re("^th(read)?\\s+l(ist)?"); + QRegExp threadcurrent_re("^th(read)?(\\sc(ur(rent)?)?)?$"); + QRegExp threadswitch_re("^th(read)?(\\ssw(itch)?)?(\\s.*)"); + QRegExp thread_re("^th(read)?(\\s+.*)?"); + QRegExp methodinstance_re("^m(ethod)?\\s+i(nstance)?\\s+(.*)"); + QRegExp method_re("^m(ethod)?\\s+(.*)"); + QRegExp list_re("^l(ist)?(\\s+\\d+-\\d+)?$"); + + if ( break_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("break%s", break_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( watch_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("watch %s", watch_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( delete_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("delete%s", delete_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( display_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("display%s", display_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( undisplay_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("undisplay%s", undisplay_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( step_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("step%s", step_re.cap(2).latin1()), + RUNCMD, + INFOCMD ), true ); + } else if ( next_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("next%s", next_re.cap(2).latin1()), + RUNCMD, + INFOCMD ), true ); + } else if ( varlocal_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand("var local", NOTRUNCMD, INFOCMD)); + } else if ( varglobal_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand("var global", NOTRUNCMD, INFOCMD)); + } else if ( varinstance_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("var instance %s", varinstance_re.cap(3).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( varconst_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("var const %s", varconst_re.cap(3).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( methodinstance_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("method instance %s", methodinstance_re.cap(3).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( method_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("method %s", method_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( list_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("list%s", list_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if (cmd == "c" || cmd == "cont") { + queueCmd(new RDBCommand("cont", RUNCMD, NOTINFOCMD)); + } else if (cmd == "fi" || cmd == "finish") { + queueCmd(new RDBCommand("finish", RUNCMD, NOTINFOCMD)); + } else if ( threadlist_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand("thread list", NOTRUNCMD, INFOCMD), true); + } else if ( threadcurrent_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand("thread current", NOTRUNCMD, INFOCMD), true ); + } else if ( threadswitch_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("thread switch%s", threadswitch_re.cap(4).latin1()), + RUNCMD, + INFOCMD ), true ); + } else if ( thread_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("thread%s", thread_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if (cmd == "frame" || cmd == "f" || cmd == "where" || cmd == "w") { + queueCmd(new RDBCommand("where", NOTRUNCMD, INFOCMD), true); + } else if (cmd == "q" || cmd == "quit") { + slotStopDebugger(); + return; + } else { + kdDebug(9012) << "Passing directly to rdb: " << cmd << endl; + queueCmd(new RDBCommand(cmd.latin1(), NOTRUNCMD, INFOCMD)); + } + + executeCmd(); +} + +} + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** +#include "rdbcontroller.moc" |