// ************************************************************************* // rdbcontroller.cpp - description // ------------------- // begin : Sun Aug 8 1999 // copyright : (C) 1999 by John Birch // email : jbb@tdevelop.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 <tqdatetime.h> #include <tqfileinfo.h> #include <tqregexp.h> #include <tqstring.h> #include <tqtextstream.h> #include <iostream> #include <ctype.h> #include <stdlib.h> using namespace std; // ************************************************************************** // // Does all the communication between rdb and the tdevelop'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 TQCString RDBController::unixSocketPath_; RDBController::RDBController(VariableTree *varTree, FramestackWidget *frameStack, TQDomDocument &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()); TQFileInfo 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 TQSocketNotifier(masterSocket_, TQSocketNotifier::Read, this); TQObject::connect( acceptNotifier_, TQT_SIGNAL(activated(int)), this, TQT_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; TQFileInfo 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); } TQString 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 TQString &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 TQString &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) { TQString buffer(buf); TQString line; TQTextStream input(&buffer, IO_ReadOnly); TQString sourceFile; int sourceLine = 0; // "1: a = 1" TQRegExp display_re("^(\\d+):\\s(.*)$"); // "/opt/qt/src/widgets/qlistview.rb:1558:puts 'hello world'" TQRegExp 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(TQString()); 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 TQRegExp 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) { TQString sourceFile; int sourceLine = 0; if (stateIsOn(s_fetchLocals)) { return; } // "#2 /home/duke/play/testit/trykorundum/src/main.rb:11" TQRegExp 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(TQString(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); TQRegExp 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 (tqstrncmp(currentCmd_->rawDbgCommand(), "break ", strlen("break ")) == 0) { parseBreakpointSet(buf); } else if (tqstrncmp(currentCmd_->rawDbgCommand(), "watch ", strlen("watch ")) == 0) { parseBreakpointSet(buf); } else if (tqstrncmp(currentCmd_->rawDbgCommand(), "display ", strlen("display ")) == 0) { parseDisplay(buf, currentCmd_->rawDbgCommand().data() + strlen("display ")); } else if (currentCmd_->rawDbgCommand() == "display") { parseUpdateDisplay(buf); } else if (tqstrncmp(currentCmd_->rawDbgCommand(), "undisplay ", strlen("undisplay ")) == 0) { ; } else if (tqstrncmp(currentCmd_->rawDbgCommand(), "method instance ", strlen("method instance ")) == 0) { } else if (tqstrncmp(currentCmd_->rawDbgCommand(), "method ", strlen("method ")) == 0) { } else if (tqstrncmp(currentCmd_->rawDbgCommand(), "pp ", strlen("pp ")) == 0) { parseRequestedData(buf); } else if (currentCmd_->rawDbgCommand() == "thread list") { parseThreadList(buf); } else if ( tqstrncmp(currentCmd_->rawDbgCommand(), "up ", strlen("up ")) == 0 || tqstrncmp(currentCmd_->rawDbgCommand(), "down ", strlen("down ")) == 0 ) { parseFrameMove(buf); } else if (tqstrncmp(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 (tqstrncmp(currentCmd_->rawDbgCommand(), "var instance ", strlen("var instance ")) == 0) { parseLocals(IVARS, buf); } else if (tqstrncmp(currentCmd_->rawDbgCommand(), "var class ", strlen("var class ")) == 0) { parseLocals(CVARS, buf); } else if (tqstrncmp(currentCmd_->rawDbgCommand(), "var const ", strlen("var const ")) == 0) { parseLocals(CONSTANTS, buf); } return; } // ************************************************************************** void RDBController::setBreakpoint(const TQCString &BPSetCmd, int key) { queueCmd(new RDBSetBreakpointCommand(BPSetCmd, key)); } // ************************************************************************** void RDBController::clearBreakpoint(const TQCString &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(TQCString().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 TQString& ruby_interpreter, const TQString& character_coding, const TQString& run_directory, const TQString& debuggee_path, const TQString &application, const TQString& 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_, 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::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_, TQT_SIGNAL(receivedStdout(KProcess *, char *, int)), this, TQT_SLOT(slotDbgStdout(KProcess *, char *, int)) ); connect( dbgProcess_, TQT_SIGNAL(receivedStderr(KProcess *, char *, int)), this, TQT_SLOT(slotDbgStderr(KProcess *, char *, int)) ); connect( dbgProcess_, TQT_SIGNAL(wroteStdin(KProcess *)), this, TQT_SLOT(slotDbgWroteStdin(KProcess *)) ); connect( dbgProcess_, TQT_SIGNAL(processExited(KProcess*)), this, TQT_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" << TQString(TQFile::encodeName( run_directory )); *dbgProcess_ << "-r" << debuggee_path; *dbgProcess_ << application; if (!run_arguments.isNull() && !run_arguments.isEmpty()) { *dbgProcess_ << run_arguments; } emit rdbStdout(TQString( ruby_interpreter + " " + character_coding + " -C " + TQString(TQFile::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(TQString("%1\n").arg(unixSocketPath_.data()).latin1(), strlen(unixSocketPath_) + 1)) { kdDebug(9012) << "failed to write Unix domain socket path to rdb " << TQString("%1\n").arg(unixSocketPath_.data()).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(); TQTime start; TQTime 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 = TQTime::currentTime(); while (-1) { kapp->processEvents(20); now = TQTime::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 = TQTime::currentTime(); while (-1) { kapp->processEvents(20); now = TQTime::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 TQString &fileName, int lineNum) { if (stateIsOn(s_appBusy|s_dbgNotStarted|s_shuttingDown)) return; if (fileName.isEmpty()) queueCmd(new RDBCommand( TQCString().sprintf("break %d", lineNum), RUNCMD, NOTINFOCMD)); else queueCmd(new RDBCommand( TQCString().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 TQString& 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(TQCString().sprintf("thread switch %d", threadNo), RUNCMD, INFOCMD)); executeCmd(); return; } if (frameNo > currentFrame_) { queueCmd(new RDBCommand(TQCString().sprintf("up %d", frameNo - currentFrame_), NOTRUNCMD, INFOCMD)); if (!stateIsOn(s_fetchLocals)) { queueCmd(new RDBCommand("display", NOTRUNCMD, INFOCMD)); } } else if (frameNo < currentFrame_) { queueCmd(new RDBCommand(TQCString().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 TQCString &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, TQCString("pp ") + userRequest.data(), false)); if (currentCmd_ == 0) { executeCmd(); } } // ************************************************************************** // This method evaluates text selected with the 'Inspect:' context menu void RDBController::slotRubyInspect(const TQString &inspectText) { queueCmd(new RDBCommand( TQCString().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 TQString& expr, bool execute) { queueCmd(new RDBCommand( TQCString().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( TQCString().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) { TQCString 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) { TQCString 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 TQSocketNotifier(socket_, TQSocketNotifier::Read, 0); TQObject::connect( socketNotifier_, TQT_SIGNAL(activated(int)), this, TQT_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; TQRegExp 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 TQString& cmd) { kdDebug(9012) << "Requested user cmd: " << cmd << endl; TQRegExp break_re("^b(reak)?(\\s.*)?"); TQRegExp watch_re("^wat(ch)?\\s+(.*)"); TQRegExp delete_re("^del(ete)?(\\s.*)?"); TQRegExp display_re("^disp(lay)?(\\s.*)?"); TQRegExp undisplay_re("^undisp(lay)?(\\s.*)?"); TQRegExp step_re("^s(tep)?(\\s[\\d]+)?$"); TQRegExp next_re("^n(ext)?(\\s[\\d]+)?$"); TQRegExp varlocal_re("^v(ar)?\\s+l(ocal)?"); TQRegExp varglobal_re("^v(ar)?\\s+g(lobal)?"); TQRegExp varinstance_re("^v(ar)?\\s+i(nstance)?\\s(.*)"); TQRegExp varconst_re("^v(ar)?\\s+c(onst)?\\s(.*)"); TQRegExp threadlist_re("^th(read)?\\s+l(ist)?"); TQRegExp threadcurrent_re("^th(read)?(\\sc(ur(rent)?)?)?$"); TQRegExp threadswitch_re("^th(read)?(\\ssw(itch)?)?(\\s.*)"); TQRegExp thread_re("^th(read)?(\\s+.*)?"); TQRegExp methodinstance_re("^m(ethod)?\\s+i(nstance)?\\s+(.*)"); TQRegExp method_re("^m(ethod)?\\s+(.*)"); TQRegExp list_re("^l(ist)?(\\s+\\d+-\\d+)?$"); if ( break_re.search(cmd) >= 0 ) { queueCmd(new RDBCommand( TQCString().sprintf("break%s", break_re.cap(2).latin1()), NOTRUNCMD, INFOCMD ), true ); } else if ( watch_re.search(cmd) >= 0 ) { queueCmd(new RDBCommand( TQCString().sprintf("watch %s", watch_re.cap(2).latin1()), NOTRUNCMD, INFOCMD ), true ); } else if ( delete_re.search(cmd) >= 0 ) { queueCmd(new RDBCommand( TQCString().sprintf("delete%s", delete_re.cap(2).latin1()), NOTRUNCMD, INFOCMD ), true ); } else if ( display_re.search(cmd) >= 0 ) { queueCmd(new RDBCommand( TQCString().sprintf("display%s", display_re.cap(2).latin1()), NOTRUNCMD, INFOCMD ), true ); } else if ( undisplay_re.search(cmd) >= 0 ) { queueCmd(new RDBCommand( TQCString().sprintf("undisplay%s", undisplay_re.cap(2).latin1()), NOTRUNCMD, INFOCMD ), true ); } else if ( step_re.search(cmd) >= 0 ) { queueCmd(new RDBCommand( TQCString().sprintf("step%s", step_re.cap(2).latin1()), RUNCMD, INFOCMD ), true ); } else if ( next_re.search(cmd) >= 0 ) { queueCmd(new RDBCommand( TQCString().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( TQCString().sprintf("var instance %s", varinstance_re.cap(3).latin1()), NOTRUNCMD, INFOCMD ), true ); } else if ( varconst_re.search(cmd) >= 0 ) { queueCmd(new RDBCommand( TQCString().sprintf("var const %s", varconst_re.cap(3).latin1()), NOTRUNCMD, INFOCMD ), true ); } else if ( methodinstance_re.search(cmd) >= 0 ) { queueCmd(new RDBCommand( TQCString().sprintf("method instance %s", methodinstance_re.cap(3).latin1()), NOTRUNCMD, INFOCMD ), true ); } else if ( method_re.search(cmd) >= 0 ) { queueCmd(new RDBCommand( TQCString().sprintf("method %s", method_re.cap(2).latin1()), NOTRUNCMD, INFOCMD ), true ); } else if ( list_re.search(cmd) >= 0 ) { queueCmd(new RDBCommand( TQCString().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( TQCString().sprintf("thread switch%s", threadswitch_re.cap(4).latin1()), RUNCMD, INFOCMD ), true ); } else if ( thread_re.search(cmd) >= 0 ) { queueCmd(new RDBCommand( TQCString().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"