// $Id$ // Copyright by Johannes Sixt // This file is under GPL, the GNU General Public Licence #include "debugger.h" #include "dbgdriver.h" #include "pgmargs.h" #include "typetable.h" #include "exprwnd.h" #include "pgmsettings.h" #include "programconfig.h" #include "valarray.h" #include #include #include #include #include #include #include /* i18n */ #include #include #include /* strtol, atoi */ #ifdef HAVE_UNISTD_H #include /* sleep(3) */ #endif #include "mydebug.h" KDebugger::KDebugger(QWidget* parent, ExprWnd* localVars, ExprWnd* watchVars, QListBox* backtrace) : QObject(parent, "debugger"), m_ttyLevel(ttyFull), m_memoryFormat(MDTword | MDThex), m_haveExecutable(false), m_programActive(false), m_programRunning(false), m_sharedLibsListed(false), m_typeTable(0), m_programConfig(0), m_d(0), m_localVariables(*localVars), m_watchVariables(*watchVars), m_btWindow(*backtrace) { m_envVars.setAutoDelete(true); m_brkpts.setAutoDelete(true); connect(&m_localVariables, SIGNAL(expanding(KTreeViewItem*,bool&)), SLOT(slotLocalsExpanding(KTreeViewItem*,bool&))); connect(&m_watchVariables, SIGNAL(expanding(KTreeViewItem*,bool&)), SLOT(slotWatchExpanding(KTreeViewItem*,bool&))); connect(&m_localVariables, SIGNAL(editValueCommitted(int, const QString&)), SLOT(slotValueEdited(int, const QString&))); connect(&m_watchVariables, SIGNAL(editValueCommitted(int, const QString&)), SLOT(slotValueEdited(int, const QString&))); connect(&m_btWindow, SIGNAL(highlighted(int)), SLOT(gotoFrame(int))); emit updateUI(); } KDebugger::~KDebugger() { if (m_programConfig != 0) { saveProgramSettings(); m_programConfig->sync(); delete m_programConfig; } delete m_typeTable; } void KDebugger::saveSettings(KConfig* /*config*/) { } void KDebugger::restoreSettings(KConfig* /*config*/) { } ////////////////////////////////////////////////////////////////////// // external interface const char GeneralGroup[] = "General"; const char DebuggerCmdStr[] = "DebuggerCmdStr"; const char TTYLevelEntry[] = "TTYLevel"; const char KDebugger::DriverNameEntry[] = "DriverName"; bool KDebugger::debugProgram(const QString& name, DebuggerDriver* driver) { if (m_d != 0 && m_d->isRunning()) { QApplication::setOverrideCursor(waitCursor); stopDriver(); QApplication::restoreOverrideCursor(); if (m_d->isRunning() || m_haveExecutable) { /* timed out! We can't really do anything useful now */ TRACE("timed out while waiting for gdb to die!"); return false; } delete m_d; m_d = 0; } // wire up the driver connect(driver, SIGNAL(activateFileLine(const QString&,int,const DbgAddr&)), this, SIGNAL(activateFileLine(const QString&,int,const DbgAddr&))); connect(driver, SIGNAL(processExited(KProcess*)), SLOT(gdbExited(KProcess*))); connect(driver, SIGNAL(commandReceived(CmdQueueItem*,const char*)), SLOT(parse(CmdQueueItem*,const char*))); connect(driver, SIGNAL(wroteStdin(KProcess*)), SIGNAL(updateUI())); connect(driver, SIGNAL(inferiorRunning()), SLOT(slotInferiorRunning())); connect(driver, SIGNAL(enterIdleState()), SLOT(backgroundUpdate())); connect(driver, SIGNAL(enterIdleState()), SIGNAL(updateUI())); connect(&m_localVariables, SIGNAL(removingItem(VarTree*)), driver, SLOT(dequeueCmdByVar(VarTree*))); connect(&m_watchVariables, SIGNAL(removingItem(VarTree*)), driver, SLOT(dequeueCmdByVar(VarTree*))); // create the program settings object openProgramConfig(name); // get debugger command from per-program settings if (m_programConfig != 0) { m_programConfig->setGroup(GeneralGroup); m_debuggerCmd = readDebuggerCmd(); // get terminal emulation level m_ttyLevel = TTYLevel(m_programConfig->readNumEntry(TTYLevelEntry, ttyFull)); } // the rest is read in later in the handler of DCexecutable m_d = driver; if (!startDriver()) { TRACE("startDriver failed"); m_d = 0; return false; } TRACE("before file cmd"); m_d->executeCmd(DCexecutable, name); m_executable = name; // set remote target if (!m_remoteDevice.isEmpty()) { m_d->executeCmd(DCtargetremote, m_remoteDevice); m_d->queueCmd(DCbt, DebuggerDriver::QMoverride); m_d->queueCmd(DCframe, 0, DebuggerDriver::QMnormal); m_programActive = true; m_haveExecutable = true; } // create a type table m_typeTable = new ProgramTypeTable; m_sharedLibsListed = false; emit updateUI(); return true; } void KDebugger::shutdown() { // shut down debugger driver if (m_d != 0 && m_d->isRunning()) { stopDriver(); } } void KDebugger::useCoreFile(QString corefile, bool batch) { m_corefile = corefile; if (!batch) { CmdQueueItem* cmd = loadCoreFile(); cmd->m_byUser = true; } } void KDebugger::setAttachPid(const QString& pid) { m_attachedPid = pid; } void KDebugger::programRun() { if (!isReady()) return; // when program is active, but not a core file, continue // otherwise run the program if (m_programActive && m_corefile.isEmpty()) { // gdb command: continue m_d->executeCmd(DCcont, true); } else { // gdb command: run m_d->executeCmd(DCrun, true); m_corefile = QString(); m_programActive = true; } m_programRunning = true; } void KDebugger::attachProgram(const QString& pid) { if (!isReady()) return; m_attachedPid = pid; TRACE("Attaching to " + m_attachedPid); m_d->executeCmd(DCattach, m_attachedPid); m_programActive = true; m_programRunning = true; } void KDebugger::programRunAgain() { if (canSingleStep()) { m_d->executeCmd(DCrun, true); m_corefile = QString(); m_programRunning = true; } } void KDebugger::programStep() { if (canSingleStep()) { m_d->executeCmd(DCstep, true); m_programRunning = true; } } void KDebugger::programNext() { if (canSingleStep()) { m_d->executeCmd(DCnext, true); m_programRunning = true; } } void KDebugger::programStepi() { if (canSingleStep()) { m_d->executeCmd(DCstepi, true); m_programRunning = true; } } void KDebugger::programNexti() { if (canSingleStep()) { m_d->executeCmd(DCnexti, true); m_programRunning = true; } } void KDebugger::programFinish() { if (canSingleStep()) { m_d->executeCmd(DCfinish, true); m_programRunning = true; } } void KDebugger::programKill() { if (haveExecutable() && isProgramActive()) { if (m_programRunning) { m_d->interruptInferior(); } // this is an emergency command; flush queues m_d->flushCommands(true); m_d->executeCmd(DCkill, true); } } bool KDebugger::runUntil(const QString& fileName, int lineNo) { if (isReady() && m_programActive && !m_programRunning) { // strip off directory part of file name QString file = fileName; int offset = file.findRev("/"); if (offset >= 0) { file.remove(0, offset+1); } m_d->executeCmd(DCuntil, file, lineNo, true); m_programRunning = true; return true; } else { return false; } } void KDebugger::programBreak() { if (m_haveExecutable && m_programRunning) { m_d->interruptInferior(); } } void KDebugger::programArgs(QWidget* parent) { if (m_haveExecutable) { QStringList allOptions = m_d->boolOptionList(); PgmArgs dlg(parent, m_executable, m_envVars, allOptions); dlg.setArgs(m_programArgs); dlg.setWd(m_programWD); dlg.setOptions(m_boolOptions); if (dlg.exec()) { updateProgEnvironment(dlg.args(), dlg.wd(), dlg.envVars(), dlg.options()); } } } void KDebugger::programSettings(QWidget* parent) { if (!m_haveExecutable) return; ProgramSettings dlg(parent, m_executable); dlg.m_chooseDriver.setDebuggerCmd(m_debuggerCmd); dlg.m_output.setTTYLevel(m_ttyLevel); if (dlg.exec() == QDialog::Accepted) { m_debuggerCmd = dlg.m_chooseDriver.debuggerCmd(); m_ttyLevel = TTYLevel(dlg.m_output.ttyLevel()); } } bool KDebugger::setBreakpoint(QString file, int lineNo, const DbgAddr& address, bool temporary) { if (!isReady()) { return false; } Breakpoint* bp = breakpointByFilePos(file, lineNo, address); if (bp == 0) { /* * No such breakpoint, so set a new one. If we have an address, we * set the breakpoint exactly there. Otherwise we use the file name * plus line no. */ Breakpoint* bp = new Breakpoint; bp->temporary = temporary; if (address.isEmpty()) { bp->fileName = file; bp->lineNo = lineNo; } else { bp->address = address; } setBreakpoint(bp, false); } else { /* * If the breakpoint is disabled, enable it; if it's enabled, * delete that breakpoint. */ if (bp->enabled) { deleteBreakpoint(bp); } else { enableDisableBreakpoint(bp); } } return true; } void KDebugger::setBreakpoint(Breakpoint* bp, bool queueOnly) { CmdQueueItem* cmd; if (!bp->text.isEmpty()) { /* * The breakpoint was set using the text box in the breakpoint * list. This is the only way in which watchpoints are set. */ if (bp->type == Breakpoint::watchpoint) { cmd = m_d->executeCmd(DCwatchpoint, bp->text); } else { cmd = m_d->executeCmd(DCbreaktext, bp->text); } } else if (bp->address.isEmpty()) { // strip off directory part of file name QString file = bp->fileName; int offset = file.findRev("/"); if (offset >= 0) { file.remove(0, offset+1); } if (queueOnly) { cmd = m_d->queueCmd(bp->temporary ? DCtbreakline : DCbreakline, file, bp->lineNo, DebuggerDriver::QMoverride); } else { cmd = m_d->executeCmd(bp->temporary ? DCtbreakline : DCbreakline, file, bp->lineNo); } } else { if (queueOnly) { cmd = m_d->queueCmd(bp->temporary ? DCtbreakaddr : DCbreakaddr, bp->address.asString(), DebuggerDriver::QMoverride); } else { cmd = m_d->executeCmd(bp->temporary ? DCtbreakaddr : DCbreakaddr, bp->address.asString()); } } cmd->m_brkpt = bp; // used in newBreakpoint() } bool KDebugger::enableDisableBreakpoint(QString file, int lineNo, const DbgAddr& address) { Breakpoint* bp = breakpointByFilePos(file, lineNo, address); return bp == 0 || enableDisableBreakpoint(bp); } bool KDebugger::enableDisableBreakpoint(Breakpoint* bp) { /* * Toggle enabled/disabled state. * * The driver is not bothered if we are modifying an orphaned * breakpoint. */ if (!bp->isOrphaned()) { if (!canChangeBreakpoints()) { return false; } m_d->executeCmd(bp->enabled ? DCdisable : DCenable, bp->id); } else { bp->enabled = !bp->enabled; emit breakpointsChanged(); } return true; } bool KDebugger::conditionalBreakpoint(Breakpoint* bp, const QString& condition, int ignoreCount) { /* * Change the condition and ignore count. * * The driver is not bothered if we are removing an orphaned * breakpoint. */ if (!bp->isOrphaned()) { if (!canChangeBreakpoints()) { return false; } bool changed = false; if (bp->condition != condition) { // change condition m_d->executeCmd(DCcondition, condition, bp->id); changed = true; } if (bp->ignoreCount != ignoreCount) { // change ignore count m_d->executeCmd(DCignore, bp->id, ignoreCount); changed = true; } if (changed) { // get the changes m_d->queueCmd(DCinfobreak, DebuggerDriver::QMoverride); } } else { bp->condition = condition; bp->ignoreCount = ignoreCount; emit breakpointsChanged(); } return true; } bool KDebugger::deleteBreakpoint(Breakpoint* bp) { /* * Remove the breakpoint. * * The driver is not bothered if we are removing an orphaned * breakpoint. */ if (!bp->isOrphaned()) { if (!canChangeBreakpoints()) { return false; } m_d->executeCmd(DCdelete, bp->id); } else { // move the last entry to bp's slot and shorten the list int i = m_brkpts.findRef(bp); m_brkpts.insert(i, m_brkpts.take(m_brkpts.size()-1)); m_brkpts.resize(m_brkpts.size()-1); emit breakpointsChanged(); } return false; } bool KDebugger::canSingleStep() { return isReady() && m_programActive && !m_programRunning; } bool KDebugger::canChangeBreakpoints() { return isReady() && !m_programRunning; } bool KDebugger::canStart() { return isReady() && !m_programActive; } bool KDebugger::isReady() const { return m_haveExecutable && m_d != 0 && m_d->canExecuteImmediately(); } bool KDebugger::isIdle() const { return m_d == 0 || m_d->isIdle(); } ////////////////////////////////////////////////////////// // debugger driver bool KDebugger::startDriver() { emit debuggerStarting(); /* must set m_inferiorTerminal */ /* * If the per-program command string is empty, use the global setting * (which might also be empty, in which case the driver uses its * default). */ m_explicitKill = false; if (!m_d->startup(m_debuggerCmd)) { return false; } /* * If we have an output terminal, we use it. Otherwise we will run the * program with input and output redirected to /dev/null. Other * redirections are also necessary depending on the tty emulation * level. */ int redirect = RDNstdin|RDNstdout|RDNstderr; /* redirect everything */ if (!m_inferiorTerminal.isEmpty()) { switch (m_ttyLevel) { default: case ttyNone: // redirect everything break; case ttySimpleOutputOnly: redirect = RDNstdin; break; case ttyFull: redirect = 0; break; } } m_d->executeCmd(DCtty, m_inferiorTerminal, redirect); return true; } void KDebugger::stopDriver() { m_explicitKill = true; if (m_attachedPid.isEmpty()) { m_d->terminate(); } else { m_d->detachAndTerminate(); } /* * We MUST wait until the slot gdbExited() has been called. But to * avoid a deadlock, we wait only for some certain maximum time. Should * this timeout be reached, the only reasonable thing one could do then * is exiting kdbg. */ kapp->processEvents(1000); /* ideally, this will already shut it down */ int maxTime = 20; /* about 20 seconds */ while (m_haveExecutable && maxTime > 0) { // give gdb time to die (and send a SIGCLD) ::sleep(1); --maxTime; kapp->processEvents(1000); } } void KDebugger::gdbExited(KProcess*) { /* * Save settings, but only if gdb has already processed "info line * main", otherwise we would save an empty config file, because it * isn't read in until then! */ if (m_programConfig != 0) { if (m_haveExecutable) { saveProgramSettings(); m_programConfig->sync(); } delete m_programConfig; m_programConfig = 0; } // erase types delete m_typeTable; m_typeTable = 0; if (m_explicitKill) { TRACE(m_d->driverName() + " exited normally"); } else { QString msg = i18n("%1 exited unexpectedly.\n" "Restart the session (e.g. with File|Executable)."); KMessageBox::error(parentWidget(), msg.arg(m_d->driverName())); } // reset state m_haveExecutable = false; m_executable = ""; m_programActive = false; m_programRunning = false; m_explicitKill = false; m_debuggerCmd = QString(); /* use global setting at next start! */ m_attachedPid = QString(); /* we are no longer attached to a process */ m_ttyLevel = ttyFull; m_brkpts.clear(); // erase PC emit updatePC(QString(), -1, DbgAddr(), 0); } QString KDebugger::getConfigForExe(const QString& name) { QFileInfo fi(name); QString pgmConfigFile = fi.dirPath(true); if (!pgmConfigFile.isEmpty()) { pgmConfigFile += '/'; } pgmConfigFile += ".kdbgrc." + fi.fileName(); TRACE("program config file = " + pgmConfigFile); return pgmConfigFile; } void KDebugger::openProgramConfig(const QString& name) { ASSERT(m_programConfig == 0); QString pgmConfigFile = getConfigForExe(name); m_programConfig = new ProgramConfig(pgmConfigFile); } const char EnvironmentGroup[] = "Environment"; const char WatchGroup[] = "Watches"; const char FileVersion[] = "FileVersion"; const char ProgramArgs[] = "ProgramArgs"; const char WorkingDirectory[] = "WorkingDirectory"; const char OptionsSelected[] = "OptionsSelected"; const char Variable[] = "Var%d"; const char Value[] = "Value%d"; const char ExprFmt[] = "Expr%d"; void KDebugger::saveProgramSettings() { ASSERT(m_programConfig != 0); m_programConfig->setGroup(GeneralGroup); m_programConfig->writeEntry(FileVersion, 1); m_programConfig->writeEntry(ProgramArgs, m_programArgs); m_programConfig->writeEntry(WorkingDirectory, m_programWD); m_programConfig->writeEntry(OptionsSelected, m_boolOptions); m_programConfig->writeEntry(DebuggerCmdStr, m_debuggerCmd); m_programConfig->writeEntry(TTYLevelEntry, int(m_ttyLevel)); QString driverName; if (m_d != 0) driverName = m_d->driverName(); m_programConfig->writeEntry(DriverNameEntry, driverName); // write environment variables m_programConfig->deleteGroup(EnvironmentGroup); m_programConfig->setGroup(EnvironmentGroup); QDictIterator it = m_envVars; EnvVar* var; QString varName; QString varValue; for (int i = 0; (var = it) != 0; ++it, ++i) { varName.sprintf(Variable, i); varValue.sprintf(Value, i); m_programConfig->writeEntry(varName, it.currentKey()); m_programConfig->writeEntry(varValue, var->value); } saveBreakpoints(m_programConfig); // watch expressions // first get rid of whatever was in this group m_programConfig->deleteGroup(WatchGroup); // then start a new group m_programConfig->setGroup(WatchGroup); KTreeViewItem* item = m_watchVariables.itemAt(0); int watchNum = 0; for (; item != 0; item = item->getSibling(), ++watchNum) { varName.sprintf(ExprFmt, watchNum); m_programConfig->writeEntry(varName, item->getText()); } // give others a chance emit saveProgramSpecific(m_programConfig); } void KDebugger::restoreProgramSettings() { ASSERT(m_programConfig != 0); m_programConfig->setGroup(GeneralGroup); /* * We ignore file version for now we will use it in the future to * distinguish different versions of this configuration file. */ // m_debuggerCmd has been read in already // m_ttyLevel has been read in already QString pgmArgs = m_programConfig->readEntry(ProgramArgs); QString pgmWd = m_programConfig->readEntry(WorkingDirectory); QStringList boolOptions = m_programConfig->readListEntry(OptionsSelected); m_boolOptions = QStringList(); // read environment variables m_programConfig->setGroup(EnvironmentGroup); m_envVars.clear(); QDict pgmVars; EnvVar* var; QString varName; QString varValue; for (int i = 0;; ++i) { varName.sprintf(Variable, i); varValue.sprintf(Value, i); if (!m_programConfig->hasKey(varName)) { /* entry not present, assume that we've hit them all */ break; } QString name = m_programConfig->readEntry(varName); if (name.isEmpty()) { // skip empty names continue; } var = new EnvVar; var->value = m_programConfig->readEntry(varValue); var->status = EnvVar::EVnew; pgmVars.insert(name, var); } updateProgEnvironment(pgmArgs, pgmWd, pgmVars, boolOptions); restoreBreakpoints(m_programConfig); // watch expressions m_programConfig->setGroup(WatchGroup); m_watchVariables.clear(); for (int i = 0;; ++i) { varName.sprintf(ExprFmt, i); if (!m_programConfig->hasKey(varName)) { /* entry not present, assume that we've hit them all */ break; } QString expr = m_programConfig->readEntry(varName); if (expr.isEmpty()) { // skip empty expressions continue; } addWatch(expr); } // give others a chance emit restoreProgramSpecific(m_programConfig); } /** * Reads the debugger command line from the program settings. The config * group must have been set by the caller. */ QString KDebugger::readDebuggerCmd() { QString debuggerCmd = m_programConfig->readEntry(DebuggerCmdStr); // always let the user confirm the debugger cmd if we are root if (::geteuid() == 0) { if (!debuggerCmd.isEmpty()) { QString msg = i18n( "The settings for this program specify " "the following debugger command:\n%1\n" "Shall this command be used?"); if (KMessageBox::warningYesNo(parentWidget(), msg.arg(debuggerCmd)) != KMessageBox::Yes) { // don't use it debuggerCmd = QString(); } } } return debuggerCmd; } /* * Breakpoints are saved one per group. */ const char BPGroup[] = "Breakpoint %d"; const char File[] = "File"; const char Line[] = "Line"; const char Text[] = "Text"; const char Address[] = "Address"; const char Temporary[] = "Temporary"; const char Enabled[] = "Enabled"; const char Condition[] = "Condition"; void KDebugger::saveBreakpoints(ProgramConfig* config) { QString groupName; int i = 0; for (uint j = 0; j < m_brkpts.size(); j++) { Breakpoint* bp = m_brkpts[j]; if (bp->type == Breakpoint::watchpoint) continue; /* don't save watchpoints */ groupName.sprintf(BPGroup, i++); /* remove remmants */ config->deleteGroup(groupName); config->setGroup(groupName); if (!bp->text.isEmpty()) { /* * The breakpoint was set using the text box in the breakpoint * list. We do not save the location by filename+line number, * but instead honor what the user typed (a function name, for * example, which could move between sessions). */ config->writeEntry(Text, bp->text); } else if (!bp->fileName.isEmpty()) { config->writeEntry(File, bp->fileName); config->writeEntry(Line, bp->lineNo); /* * Addresses are hardly correct across sessions, so we don't * save it. */ } else { config->writeEntry(Address, bp->address.asString()); } config->writeEntry(Temporary, bp->temporary); config->writeEntry(Enabled, bp->enabled); if (!bp->condition.isEmpty()) config->writeEntry(Condition, bp->condition); // we do not save the ignore count } // delete remaining groups // we recognize that a group is present if there is an Enabled entry for (;; i++) { groupName.sprintf(BPGroup, i); config->setGroup(groupName); if (!config->hasKey(Enabled)) { /* group not present, assume that we've hit them all */ break; } config->deleteGroup(groupName); } } void KDebugger::restoreBreakpoints(ProgramConfig* config) { QString groupName; /* * We recognize the end of the list if there is no Enabled entry * present. */ for (int i = 0;; i++) { groupName.sprintf(BPGroup, i); config->setGroup(groupName); if (!config->hasKey(Enabled)) { /* group not present, assume that we've hit them all */ break; } Breakpoint* bp = new Breakpoint; bp->fileName = config->readEntry(File); bp->lineNo = config->readNumEntry(Line, -1); bp->text = config->readEntry(Text); bp->address = config->readEntry(Address); // check consistency if ((bp->fileName.isEmpty() || bp->lineNo < 0) && bp->text.isEmpty() && bp->address.isEmpty()) { delete bp; continue; } bp->enabled = config->readBoolEntry(Enabled, true); bp->temporary = config->readBoolEntry(Temporary, false); bp->condition = config->readEntry(Condition); /* * Add the breakpoint. */ setBreakpoint(bp, false); // the new breakpoint is disabled or conditionalized later // in newBreakpoint() } m_d->queueCmd(DCinfobreak, DebuggerDriver::QMoverride); } // parse output of command cmd void KDebugger::parse(CmdQueueItem* cmd, const char* output) { ASSERT(cmd != 0); /* queue mustn't be empty */ TRACE(QString(__PRETTY_FUNCTION__) + " parsing " + output); switch (cmd->m_cmd) { case DCtargetremote: // the output (if any) is uninteresting case DCsetargs: case DCtty: // there is no output case DCsetenv: case DCunsetenv: case DCsetoption: /* if value is empty, we see output, but we don't care */ break; case DCcd: /* display gdb's message in the status bar */ m_d->parseChangeWD(output, m_statusMessage); emit updateStatusMessage(); break; case DCinitialize: break; case DCexecutable: if (m_d->parseChangeExecutable(output, m_statusMessage)) { // success; restore breakpoints etc. if (m_programConfig != 0) { restoreProgramSettings(); } // load file containing main() or core file if (!m_corefile.isEmpty()) { // load core file loadCoreFile(); } else if (!m_attachedPid.isEmpty()) { m_d->queueCmd(DCattach, m_attachedPid, DebuggerDriver::QMoverride); m_programActive = true; m_programRunning = true; } else if (!m_remoteDevice.isEmpty()) { // handled elsewhere } else { m_d->queueCmd(DCinfolinemain, DebuggerDriver::QMnormal); } if (!m_statusMessage.isEmpty()) emit updateStatusMessage(); } else { QString msg = m_d->driverName() + ": " + m_statusMessage; KMessageBox::sorry(parentWidget(), msg); m_executable = ""; m_corefile = ""; /* don't process core file */ m_haveExecutable = false; } break; case DCcorefile: // in any event we have an executable at this point m_haveExecutable = true; if (m_d->parseCoreFile(output)) { // loading a core is like stopping at a breakpoint m_programActive = true; handleRunCommands(output); // do not reset m_corefile } else { // report error QString msg = m_d->driverName() + ": " + QString(output); KMessageBox::sorry(parentWidget(), msg); // if core file was loaded from command line, revert to info line main if (!cmd->m_byUser) { m_d->queueCmd(DCinfolinemain, DebuggerDriver::QMnormal); } m_corefile = QString(); /* core file not available any more */ } break; case DCinfolinemain: // ignore the output, marked file info follows m_haveExecutable = true; break; case DCinfolocals: // parse local variables if (output[0] != '\0') { handleLocals(output); } break; case DCinforegisters: handleRegisters(output); break; case DCexamine: handleMemoryDump(output); break; case DCinfoline: handleInfoLine(cmd, output); break; case DCdisassemble: handleDisassemble(cmd, output); break; case DCframe: handleFrameChange(output); updateAllExprs(); break; case DCbt: handleBacktrace(output); updateAllExprs(); break; case DCprint: handlePrint(cmd, output); break; case DCprintDeref: handlePrintDeref(cmd, output); break; case DCattach: m_haveExecutable = true; // fall through case DCrun: case DCcont: case DCstep: case DCstepi: case DCnext: case DCnexti: case DCfinish: case DCuntil: case DCthread: handleRunCommands(output); break; case DCkill: m_programRunning = m_programActive = false; // erase PC emit updatePC(QString(), -1, DbgAddr(), 0); break; case DCbreaktext: case DCbreakline: case DCtbreakline: case DCbreakaddr: case DCtbreakaddr: case DCwatchpoint: newBreakpoint(cmd, output); // fall through case DCdelete: case DCenable: case DCdisable: // these commands need immediate response m_d->queueCmd(DCinfobreak, DebuggerDriver::QMoverrideMoreEqual); break; case DCinfobreak: // note: this handler must not enqueue a command, since // DCinfobreak is used at various different places. updateBreakList(output); break; case DCfindType: handleFindType(cmd, output); break; case DCprintStruct: case DCprintQStringStruct: handlePrintStruct(cmd, output); break; case DCinfosharedlib: handleSharedLibs(output); break; case DCcondition: case DCignore: // we are not interested in the output break; case DCinfothreads: handleThreadList(output); break; case DCsetpc: handleSetPC(output); break; case DCsetvariable: handleSetVariable(cmd, output); break; } } void KDebugger::backgroundUpdate() { /* * If there are still expressions that need to be updated, then do so. */ if (m_programActive) evalExpressions(); } void KDebugger::handleRunCommands(const char* output) { uint flags = m_d->parseProgramStopped(output, m_statusMessage); emit updateStatusMessage(); m_programActive = flags & DebuggerDriver::SFprogramActive; // refresh files if necessary if (flags & DebuggerDriver::SFrefreshSource) { TRACE("re-reading files"); emit executableUpdated(); } /* * Try to set any orphaned breakpoints now. */ for (int i = m_brkpts.size()-1; i >= 0; i--) { if (m_brkpts[i]->isOrphaned()) { TRACE("re-trying brkpt loc: "+m_brkpts[i]->location+ " file: "+m_brkpts[i]->fileName+ QString().sprintf(" line: %d", m_brkpts[i]->lineNo)); setBreakpoint(m_brkpts[i], true); flags |= DebuggerDriver::SFrefreshBreak; } } /* * If we stopped at a breakpoint, we must update the breakpoint list * because the hit count changes. Also, if the breakpoint was temporary * it would go away now. */ if ((flags & (DebuggerDriver::SFrefreshBreak|DebuggerDriver::SFrefreshSource)) || stopMayChangeBreakList()) { m_d->queueCmd(DCinfobreak, DebuggerDriver::QMoverride); } /* * If we haven't listed the shared libraries yet, do so. We must do * this before we emit any commands that list variables, since the type * libraries depend on the shared libraries. */ if (!m_sharedLibsListed) { // must be a high-priority command! m_d->executeCmd(DCinfosharedlib); } // get the backtrace if the program is running if (m_programActive) { m_d->queueCmd(DCbt, DebuggerDriver::QMoverride); } else { // program finished: erase PC emit updatePC(QString(), -1, DbgAddr(), 0); // dequeue any commands in the queues m_d->flushCommands(); } /* Update threads list */ if (m_programActive && (flags & DebuggerDriver::SFrefreshThreads)) { m_d->queueCmd(DCinfothreads, DebuggerDriver::QMoverride); } m_programRunning = false; emit programStopped(); } void KDebugger::slotInferiorRunning() { m_programRunning = true; } void KDebugger::updateAllExprs() { if (!m_programActive) return; // retrieve local variables m_d->queueCmd(DCinfolocals, DebuggerDriver::QMoverride); // retrieve registers m_d->queueCmd(DCinforegisters, DebuggerDriver::QMoverride); // get new memory dump if (!m_memoryExpression.isEmpty()) { queueMemoryDump(false); } // update watch expressions KTreeViewItem* item = m_watchVariables.itemAt(0); for (; item != 0; item = item->getSibling()) { m_watchEvalExpr.append(static_cast(item)); } } void KDebugger::updateProgEnvironment(const QString& args, const QString& wd, const QDict& newVars, const QStringList& newOptions) { m_programArgs = args; m_d->executeCmd(DCsetargs, m_programArgs); TRACE("new pgm args: " + m_programArgs + "\n"); m_programWD = wd.stripWhiteSpace(); if (!m_programWD.isEmpty()) { m_d->executeCmd(DCcd, m_programWD); TRACE("new wd: " + m_programWD + "\n"); } // update environment variables QDictIterator it = newVars; EnvVar* val; for (; (val = it) != 0; ++it) { QString var = it.currentKey(); switch (val->status) { case EnvVar::EVnew: m_envVars.insert(var, val); // fall thru case EnvVar::EVdirty: // the value must be in our list ASSERT(m_envVars[var] == val); // update value m_d->executeCmd(DCsetenv, var, val->value); break; case EnvVar::EVdeleted: // must be in our list ASSERT(m_envVars[var] == val); // delete value m_d->executeCmd(DCunsetenv, var); m_envVars.remove(var); break; default: ASSERT(false); case EnvVar::EVclean: // variable not changed break; } } // update options QStringList::ConstIterator oi; for (oi = newOptions.begin(); oi != newOptions.end(); ++oi) { if (m_boolOptions.findIndex(*oi) < 0) { // the options is currently not set, so set it m_d->executeCmd(DCsetoption, *oi, 1); } else { // option is set, no action required, but move it to the end m_boolOptions.remove(*oi); } m_boolOptions.append(*oi); } /* * Now all options that should be set are at the end of m_boolOptions. * If some options need to be unset, they are at the front of the list. * Here we unset and remove them. */ while (m_boolOptions.count() > newOptions.count()) { m_d->executeCmd(DCsetoption, m_boolOptions.first(), 0); m_boolOptions.remove(m_boolOptions.begin()); } } void KDebugger::handleLocals(const char* output) { // retrieve old list of local variables QStrList oldVars; m_localVariables.exprList(oldVars); /* * Get local variables. */ QList newVars; parseLocals(output, newVars); /* * Clear any old VarTree item pointers, so that later we don't access * dangling pointers. */ m_localVariables.clearPendingUpdates(); // reduce flicker bool autoU = m_localVariables.autoUpdate(); m_localVariables.setAutoUpdate(false); bool repaintNeeded = false; /* * Match old variables against new ones. */ for (const char* n = oldVars.first(); n != 0; n = oldVars.next()) { // lookup this variable in the list of new variables VarTree* v = newVars.first(); while (v != 0 && strcmp(v->getText(), n) != 0) { v = newVars.next(); } if (v == 0) { // old variable not in the new variables TRACE(QString("old var deleted: ") + n); v = m_localVariables.topLevelExprByName(n); if (v != 0) { m_localVariables.removeExpr(v); repaintNeeded = true; } } else { // variable in both old and new lists: update TRACE(QString("update var: ") + n); m_localVariables.updateExpr(newVars.current()); // remove the new variable from the list newVars.remove(); delete v; repaintNeeded = true; } } // insert all remaining new variables for (VarTree* v = newVars.first(); v != 0; v = newVars.next()) { TRACE("new var: " + v->getText()); m_localVariables.insertExpr(v); repaintNeeded = true; } // repaint m_localVariables.setAutoUpdate(autoU); if (repaintNeeded && autoU && m_localVariables.isVisible()) m_localVariables.repaint(); } void KDebugger::parseLocals(const char* output, QList& newVars) { QList vars; m_d->parseLocals(output, vars); QString origName; /* used in renaming variables */ while (vars.count() > 0) { VarTree* variable = vars.take(0); // get some types variable->inferTypesOfChildren(*m_typeTable); /* * When gdb prints local variables, those from the innermost block * come first. We run through the list of already parsed variables * to find duplicates (ie. variables that hide local variables from * a surrounding block). We keep the name of the inner variable, but * rename those from the outer block so that, when the value is * updated in the window, the value of the variable that is * _visible_ changes the color! */ int block = 0; origName = variable->getText(); for (VarTree* v = newVars.first(); v != 0; v = newVars.next()) { if (variable->getText() == v->getText()) { // we found a duplicate, change name block++; QString newName = origName + " (" + QString().setNum(block) + ")"; variable->setText(newName); } } newVars.append(variable); } } bool KDebugger::handlePrint(CmdQueueItem* cmd, const char* output) { ASSERT(cmd->m_expr != 0); VarTree* variable = parseExpr(output, true); if (variable == 0) return false; // set expression "name" variable->setText(cmd->m_expr->getText()); { TRACE("update expr: " + cmd->m_expr->getText()); cmd->m_exprWnd->updateExpr(cmd->m_expr, variable); delete variable; } evalExpressions(); /* enqueue dereferenced pointers */ return true; } bool KDebugger::handlePrintDeref(CmdQueueItem* cmd, const char* output) { ASSERT(cmd->m_expr != 0); VarTree* variable = parseExpr(output, true); if (variable == 0) return false; // set expression "name" variable->setText(cmd->m_expr->getText()); { /* * We must insert a dummy parent, because otherwise variable's value * would overwrite cmd->m_expr's value. */ VarTree* dummyParent = new VarTree(variable->getText(), VarTree::NKplain); dummyParent->m_varKind = VarTree::VKdummy; // the name of the parsed variable is the address of the pointer QString addr = "*" + cmd->m_expr->m_value; variable->setText(addr); variable->m_nameKind = VarTree::NKaddress; dummyParent->appendChild(variable); dummyParent->setDeleteChildren(true); // expand the first level for convenience variable->setExpanded(true); TRACE("update ptr: " + cmd->m_expr->getText()); cmd->m_exprWnd->updateExpr(cmd->m_expr, dummyParent); delete dummyParent; } evalExpressions(); /* enqueue dereferenced pointers */ return true; } VarTree* KDebugger::parseExpr(const char* output, bool wantErrorValue) { VarTree* variable; // check for error conditions bool goodValue = m_d->parsePrintExpr(output, wantErrorValue, variable); if (variable != 0 && goodValue) { // get some types variable->inferTypesOfChildren(*m_typeTable); } return variable; } // parse the output of bt void KDebugger::handleBacktrace(const char* output) { // reduce flicker m_btWindow.setAutoUpdate(false); m_btWindow.clear(); QList stack; m_d->parseBackTrace(output, stack); if (stack.count() > 0) { StackFrame* frm = stack.take(0); // first frame must set PC // note: frm->lineNo is zero-based emit updatePC(frm->fileName, frm->lineNo, frm->address, frm->frameNo); do { QString func; if (frm->var != 0) func = frm->var->getText(); else func = frm->fileName + ":" + QString().setNum(frm->lineNo+1); m_btWindow.insertItem(func); TRACE("frame " + func + " (" + frm->fileName + ":" + QString().setNum(frm->lineNo+1) + ")"); delete frm; } while ((frm = stack.take()) != 0); } m_btWindow.setAutoUpdate(true); m_btWindow.repaint(); } void KDebugger::gotoFrame(int frame) { m_d->executeCmd(DCframe, frame); } void KDebugger::handleFrameChange(const char* output) { QString fileName; int frameNo; int lineNo; DbgAddr address; if (m_d->parseFrameChange(output, frameNo, fileName, lineNo, address)) { /* lineNo can be negative here if we can't find a file name */ emit updatePC(fileName, lineNo, address, frameNo); } else { emit updatePC(fileName, -1, address, frameNo); } } void KDebugger::evalExpressions() { // evaluate expressions in the following order: // watch expressions // pointers in local variables // pointers in watch expressions // types in local variables // types in watch expressions // pointers in 'this' // types in 'this' VarTree* exprItem = m_watchEvalExpr.first(); if (exprItem != 0) { m_watchEvalExpr.remove(); QString expr = exprItem->computeExpr(); TRACE("watch expr: " + expr); CmdQueueItem* cmd = m_d->queueCmd(DCprint, expr, DebuggerDriver::QMoverride); // remember which expr this was cmd->m_expr = exprItem; cmd->m_exprWnd = &m_watchVariables; } else { ExprWnd* wnd; VarTree* exprItem; #define POINTER(widget) \ wnd = &widget; \ exprItem = widget.nextUpdatePtr(); \ if (exprItem != 0) goto pointer #define STRUCT(widget) \ wnd = &widget; \ exprItem = widget.nextUpdateStruct(); \ if (exprItem != 0) goto ustruct #define TYPE(widget) \ wnd = &widget; \ exprItem = widget.nextUpdateType(); \ if (exprItem != 0) goto type repeat: POINTER(m_localVariables); POINTER(m_watchVariables); STRUCT(m_localVariables); STRUCT(m_watchVariables); TYPE(m_localVariables); TYPE(m_watchVariables); #undef POINTER #undef STRUCT #undef TYPE return; pointer: // we have an expression to send dereferencePointer(wnd, exprItem, false); return; ustruct: // paranoia if (exprItem->m_type == 0 || exprItem->m_type == TypeInfo::unknownType()) goto repeat; evalInitialStructExpression(exprItem, wnd, false); return; type: /* * Sometimes a VarTree gets registered twice for a type update. So * it may happen that it has already been updated. Hence, we ignore * it here and go on to the next task. */ if (exprItem->m_type != 0) goto repeat; determineType(wnd, exprItem); } } void KDebugger::dereferencePointer(ExprWnd* wnd, VarTree* exprItem, bool immediate) { ASSERT(exprItem->m_varKind == VarTree::VKpointer); QString expr = exprItem->computeExpr(); TRACE("dereferencing pointer: " + expr); CmdQueueItem* cmd; if (immediate) { cmd = m_d->queueCmd(DCprintDeref, expr, DebuggerDriver::QMoverrideMoreEqual); } else { cmd = m_d->queueCmd(DCprintDeref, expr, DebuggerDriver::QMoverride); } // remember which expr this was cmd->m_expr = exprItem; cmd->m_exprWnd = wnd; } void KDebugger::determineType(ExprWnd* wnd, VarTree* exprItem) { ASSERT(exprItem->m_varKind == VarTree::VKstruct); QString expr = exprItem->computeExpr(); TRACE("get type of: " + expr); CmdQueueItem* cmd; cmd = m_d->queueCmd(DCfindType, expr, DebuggerDriver::QMoverride); // remember which expr this was cmd->m_expr = exprItem; cmd->m_exprWnd = wnd; } void KDebugger::handleFindType(CmdQueueItem* cmd, const char* output) { QString type; if (m_d->parseFindType(output, type)) { ASSERT(cmd != 0 && cmd->m_expr != 0); TypeInfo* info = m_typeTable->lookup(type); if (info == 0) { /* * We've asked gdb for the type of the expression in * cmd->m_expr, but it returned a name we don't know. The base * class (and member) types have been checked already (at the * time when we parsed that particular expression). Now it's * time to derive the type from the base classes as a last * resort. */ info = cmd->m_expr->inferTypeFromBaseClass(); // if we found a type through this method, register an alias if (info != 0) { TRACE("infered alias: " + type); m_typeTable->registerAlias(type, info); } } if (info == 0) { TRACE("unknown type"); cmd->m_expr->m_type = TypeInfo::unknownType(); } else { cmd->m_expr->m_type = info; /* since this node has a new type, we get its value immediately */ evalInitialStructExpression(cmd->m_expr, cmd->m_exprWnd, false); return; } } evalExpressions(); /* queue more of them */ } void KDebugger::handlePrintStruct(CmdQueueItem* cmd, const char* output) { VarTree* var = cmd->m_expr; ASSERT(var != 0); ASSERT(var->m_varKind == VarTree::VKstruct); VarTree* partExpr; if (cmd->m_cmd != DCprintQStringStruct) { partExpr = parseExpr(output, false); } else { partExpr = m_d->parseQCharArray(output, false, m_typeTable->qCharIsShort()); } bool errorValue = partExpr == 0 || /* we only allow simple values at the moment */ partExpr->childCount() != 0; QString partValue; if (errorValue) { partValue = "?""?""?"; // 2 question marks in a row would be a trigraph } else { partValue = partExpr->m_value; } delete partExpr; partExpr = 0; /* * Updating a struct value works like this: var->m_partialValue holds * the value that we have gathered so far (it's been initialized with * var->m_type->m_displayString[0] earlier). Each time we arrive here, * we append the printed result followed by the next * var->m_type->m_displayString to var->m_partialValue. * * If the expression we just evaluated was a guard expression, and it * resulted in an error, we must not evaluate the real expression, but * go on to the next index. (We must still add the question marks to * the value). * * Next, if this was the length expression, we still have not seen the * real expression, but the length of a QString. */ ASSERT(var->m_exprIndex >= 0 && var->m_exprIndex <= typeInfoMaxExpr); if (errorValue || !var->m_exprIndexUseGuard) { // add current partValue (which might be the question marks) var->m_partialValue += partValue; var->m_exprIndex++; /* next part */ var->m_exprIndexUseGuard = true; var->m_partialValue += var->m_type->m_displayString[var->m_exprIndex]; } else { // this was a guard expression that succeeded // go for the real expression var->m_exprIndexUseGuard = false; } /* go for more sub-expressions if needed */ if (var->m_exprIndex < var->m_type->m_numExprs) { /* queue a new print command with quite high priority */ evalStructExpression(var, cmd->m_exprWnd, true); return; } cmd->m_exprWnd->updateStructValue(var); evalExpressions(); /* enqueue dereferenced pointers */ } /* queues the first printStruct command for a struct */ void KDebugger::evalInitialStructExpression(VarTree* var, ExprWnd* wnd, bool immediate) { var->m_exprIndex = 0; var->m_exprIndexUseGuard = true; var->m_partialValue = var->m_type->m_displayString[0]; evalStructExpression(var, wnd, immediate); } /* queues a printStruct command; var must have been initialized correctly */ void KDebugger::evalStructExpression(VarTree* var, ExprWnd* wnd, bool immediate) { QString base = var->computeExpr(); QString exprFmt; if (var->m_exprIndexUseGuard) { exprFmt = var->m_type->m_guardStrings[var->m_exprIndex]; if (exprFmt.isEmpty()) { // no guard, omit it and go to expression var->m_exprIndexUseGuard = false; } } if (!var->m_exprIndexUseGuard) { exprFmt = var->m_type->m_exprStrings[var->m_exprIndex]; } SIZED_QString(expr, exprFmt.length() + base.length() + 10); expr.sprintf(exprFmt, base.data()); DbgCommand dbgCmd = DCprintStruct; // check if this is a QString::Data if (strncmp(expr, "/QString::Data ", 15) == 0) { if (m_typeTable->parseQt2QStrings()) { expr = expr.mid(15, expr.length()); /* strip off /QString::Data */ dbgCmd = DCprintQStringStruct; } else { /* * This should not happen: the type libraries should be set up * in a way that this can't happen. If this happens * nevertheless it means that, eg., kdecore was loaded but qt2 * was not (only qt2 enables the QString feature). */ // TODO: remove this "print"; queue the next printStruct instead expr = "*0"; } } else { expr = expr; } TRACE("evalStruct: " + expr + (var->m_exprIndexUseGuard ? " // guard" : " // real")); CmdQueueItem* cmd = m_d->queueCmd(dbgCmd, expr, immediate ? DebuggerDriver::QMoverrideMoreEqual : DebuggerDriver::QMnormal); // remember which expression this was cmd->m_expr = var; cmd->m_exprWnd = wnd; } void KDebugger::handleSharedLibs(const char* output) { // delete all known libraries m_sharedLibs.clear(); // parse the table of shared libraries m_d->parseSharedLibs(output, m_sharedLibs); m_sharedLibsListed = true; // get type libraries m_typeTable->loadLibTypes(m_sharedLibs); // hand over the QString data cmd m_d->setPrintQStringDataCmd(m_typeTable->printQStringDataCmd()); } CmdQueueItem* KDebugger::loadCoreFile() { return m_d->queueCmd(DCcorefile, m_corefile, DebuggerDriver::QMoverride); } void KDebugger::slotLocalsExpanding(KTreeViewItem* item, bool& allow) { exprExpandingHelper(&m_localVariables, item, allow); } void KDebugger::slotWatchExpanding(KTreeViewItem* item, bool& allow) { exprExpandingHelper(&m_watchVariables, item, allow); } void KDebugger::exprExpandingHelper(ExprWnd* wnd, KTreeViewItem* item, bool&) { VarTree* exprItem = static_cast(item); if (exprItem->m_varKind != VarTree::VKpointer) { return; } dereferencePointer(wnd, exprItem, true); } // add the expression in the edit field to the watch expressions void KDebugger::addWatch(const QString& t) { QString expr = t.stripWhiteSpace(); if (expr.isEmpty()) return; VarTree* exprItem = new VarTree(expr, VarTree::NKplain); m_watchVariables.insertExpr(exprItem); // if we are boring ourselves, send down the command if (m_programActive) { m_watchEvalExpr.append(exprItem); if (m_d->isIdle()) { evalExpressions(); } } } // delete a toplevel watch expression void KDebugger::slotDeleteWatch() { // delete only allowed while debugger is idle; or else we might delete // the very expression the debugger is currently working on... if (!m_d->isIdle()) return; int index = m_watchVariables.currentItem(); if (index < 0) return; VarTree* item = static_cast(m_watchVariables.itemAt(index)); if (!item->isToplevelExpr()) return; // remove the variable from the list to evaluate if (m_watchEvalExpr.findRef(item) >= 0) { m_watchEvalExpr.remove(); } m_watchVariables.removeExpr(item); // item is invalid at this point! } void KDebugger::handleRegisters(const char* output) { QList regs; m_d->parseRegisters(output, regs); emit registersChanged(regs); // delete them all regs.setAutoDelete(true); } /* * The output of the DCbreak* commands has more accurate information about * the file and the line number. * * All newly set breakpoints are inserted in the m_brkpts, even those that * were not set sucessfully. The unsuccessful breakpoints ("orphaned * breakpoints") are assigned negative ids, and they are tried to set later * when the program stops again at a breakpoint. */ void KDebugger::newBreakpoint(CmdQueueItem* cmd, const char* output) { Breakpoint* bp = cmd->m_brkpt; assert(bp != 0); if (bp == 0) return; // if this is a new breakpoint, put it in the list bool isNew = !m_brkpts.contains(bp); if (isNew) { assert(bp->id == 0); int n = m_brkpts.size(); m_brkpts.resize(n+1); m_brkpts.insert(n, bp); } // parse the output to determine success or failure int id; QString file; int lineNo; QString address; if (!m_d->parseBreakpoint(output, id, file, lineNo, address)) { /* * Failure, the breakpoint could not be set. If this is a new * breakpoint, assign it a negative id. We look for the minimal id * of all breakpoints (that are already in the list) to get the new * id. */ if (isNew) { assert(bp->id == 0); for (int i = m_brkpts.size()-2; i >= 0; i--) { if (m_brkpts[i]->id < bp->id) { bp->id = m_brkpts[i]->id; break; } } --bp->id; } return; } // The breakpoint was successfully set. if (bp->id <= 0) { // this is a new or orphaned breakpoint: // set the remaining properties if (!cmd->m_brkpt->enabled) { m_d->executeCmd(DCdisable, id); } if (!cmd->m_brkpt->condition.isEmpty()) { m_d->executeCmd(DCcondition, cmd->m_brkpt->condition, id); } } bp->id = id; bp->fileName = file; bp->lineNo = lineNo; if (!address.isEmpty()) bp->address = address; } void KDebugger::updateBreakList(const char* output) { // get the new list QList brks; brks.setAutoDelete(false); m_d->parseBreakList(output, brks); // merge new information into existing breakpoints for (int i = m_brkpts.size()-1; i >= 0; i--) // decrement! { // skip orphaned breakpoints if (m_brkpts[i]->id < 0) continue; for (Breakpoint* bp = brks.first(); bp != 0; bp = brks.next()) { if (bp->id == m_brkpts[i]->id) { // keep accurate location // except that xsldbg doesn't have a location in // the old breakpoint if it's just been set bp->text = m_brkpts[i]->text; if (!m_brkpts[i]->fileName.isEmpty()) { bp->fileName = m_brkpts[i]->fileName; bp->lineNo = m_brkpts[i]->lineNo; } m_brkpts.insert(i, bp); // old object is deleted goto stillAlive; } } /* * If we get here, this breakpoint is no longer present. * * To delete the breakpoint at i, we place the last breakpoint in * the list into the slot i. This will delete the old object at i. * Then we shorten the list by one. */ m_brkpts.insert(i, m_brkpts.take(m_brkpts.size()-1)); m_brkpts.resize(m_brkpts.size()-1); TRACE(QString().sprintf("deleted brkpt %d, have now %d brkpts", i, m_brkpts.size())); stillAlive:; } // brks may contain new breakpoints not already in m_brkpts for (const Breakpoint* bp = brks.first(); bp != 0; bp = brks.next()) { bool found = false; for (uint i = 0; i < m_brkpts.size(); i++) { if (bp->id == m_brkpts[i]->id) { found = true; break; } } if (!found){ int n = m_brkpts.size(); m_brkpts.resize(n+1); m_brkpts.insert(n, bp); } } emit breakpointsChanged(); } // look if there is at least one temporary breakpoint // or a watchpoint bool KDebugger::stopMayChangeBreakList() const { for (int i = m_brkpts.size()-1; i >= 0; i--) { Breakpoint* bp = m_brkpts[i]; if (bp->temporary || bp->type == Breakpoint::watchpoint) return true; } return false; } Breakpoint* KDebugger::breakpointByFilePos(QString file, int lineNo, const DbgAddr& address) { // look for exact file name match int i; for (i = m_brkpts.size()-1; i >= 0; i--) { if (m_brkpts[i]->lineNo == lineNo && m_brkpts[i]->fileName == file && (address.isEmpty() || m_brkpts[i]->address == address)) { return m_brkpts[i]; } } // not found, so try basename // strip off directory part of file name int offset = file.findRev("/"); file.remove(0, offset+1); for (i = m_brkpts.size()-1; i >= 0; i--) { // get base name of breakpoint's file QString basename = m_brkpts[i]->fileName; int offset = basename.findRev("/"); if (offset >= 0) { basename.remove(0, offset+1); } if (m_brkpts[i]->lineNo == lineNo && basename == file && (address.isEmpty() || m_brkpts[i]->address == address)) { return m_brkpts[i]; } } // not found return 0; } Breakpoint* KDebugger::breakpointById(int id) { for (int i = m_brkpts.size()-1; i >= 0; i--) { if (m_brkpts[i]->id == id) { return m_brkpts[i]; } } // not found return 0; } void KDebugger::slotValuePopup(const QString& expr) { // search the local variables for a match VarTree* v = m_localVariables.topLevelExprByName(expr); if (v == 0) { // not found, check watch expressions v = m_watchVariables.topLevelExprByName(expr); if (v == 0) { // try a member of 'this' v = m_localVariables.topLevelExprByName("this"); if (v != 0) v = ExprWnd::ptrMemberByName(v, expr); if (v == 0) { // nothing found; do nothing return; } } } // construct the tip QString tip = v->getText() + " = "; if (!v->m_value.isEmpty()) { tip += v->m_value; } else { // no value: we use some hint switch (v->m_varKind) { case VarTree::VKstruct: tip += "{...}"; break; case VarTree::VKarray: tip += "[...]"; break; default: tip += "?""?""?"; // 2 question marks in a row would be a trigraph break; } } emit valuePopup(tip); } void KDebugger::slotDisassemble(const QString& fileName, int lineNo) { CmdQueueItem* cmd = m_d->queueCmd(DCinfoline, fileName, lineNo, DebuggerDriver::QMoverrideMoreEqual); cmd->m_fileName = fileName; cmd->m_lineNo = lineNo; } void KDebugger::handleInfoLine(CmdQueueItem* cmd, const char* output) { QString addrFrom, addrTo; if (cmd->m_lineNo >= 0) { // disassemble if (m_d->parseInfoLine(output, addrFrom, addrTo)) { // got the address range, now get the real code CmdQueueItem* c = m_d->queueCmd(DCdisassemble, addrFrom, addrTo, DebuggerDriver::QMoverrideMoreEqual); c->m_fileName = cmd->m_fileName; c->m_lineNo = cmd->m_lineNo; } else { // no code QList empty; emit disassembled(cmd->m_fileName, cmd->m_lineNo, empty); } } else { // set program counter if (m_d->parseInfoLine(output, addrFrom, addrTo)) { // move the program counter to the start address m_d->executeCmd(DCsetpc, addrFrom); } } } void KDebugger::handleDisassemble(CmdQueueItem* cmd, const char* output) { QList code; code.setAutoDelete(true); m_d->parseDisassemble(output, code); emit disassembled(cmd->m_fileName, cmd->m_lineNo, code); } void KDebugger::handleThreadList(const char* output) { QList threads; threads.setAutoDelete(true); m_d->parseThreadList(output, threads); emit threadsChanged(threads); } void KDebugger::setThread(int id) { m_d->queueCmd(DCthread, id, DebuggerDriver::QMoverrideMoreEqual); } void KDebugger::setMemoryExpression(const QString& memexpr) { m_memoryExpression = memexpr; // queue the new expression if (!m_memoryExpression.isEmpty() && isProgramActive() && !isProgramRunning()) { queueMemoryDump(true); } } void KDebugger::queueMemoryDump(bool immediate) { m_d->queueCmd(DCexamine, m_memoryExpression, m_memoryFormat, immediate ? DebuggerDriver::QMoverrideMoreEqual : DebuggerDriver::QMoverride); } void KDebugger::handleMemoryDump(const char* output) { QList memdump; memdump.setAutoDelete(true); QString msg = m_d->parseMemoryDump(output, memdump); emit memoryDumpChanged(msg, memdump); } void KDebugger::setProgramCounter(const QString& file, int line, const DbgAddr& addr) { if (addr.isEmpty()) { // find address of the specified line CmdQueueItem* cmd = m_d->executeCmd(DCinfoline, file, line); cmd->m_lineNo = -1; /* indicates "Set PC" UI command */ } else { // move the program counter to that address m_d->executeCmd(DCsetpc, addr.asString()); } } void KDebugger::handleSetPC(const char* /*output*/) { // TODO: handle errors // now go to the top-most frame // this also modifies the program counter indicator in the UI gotoFrame(0); } void KDebugger::slotValueEdited(int row, const QString& text) { if (text.simplifyWhiteSpace().isEmpty()) return; /* no text entered: ignore request */ ASSERT(sender()->inherits("ExprWnd")); ExprWnd* wnd = const_cast(static_cast(sender())); TRACE(QString().sprintf("Changing %s at row %d to ", wnd->name(), row) + text); // determine the lvalue to edit VarTree* expr = static_cast(wnd->itemAt(row)); QString lvalue = expr->computeExpr(); CmdQueueItem* cmd = m_d->executeCmd(DCsetvariable, lvalue, text); cmd->m_expr = expr; cmd->m_exprWnd = wnd; } void KDebugger::handleSetVariable(CmdQueueItem* cmd, const char* output) { QString msg = m_d->parseSetVariable(output); if (!msg.isEmpty()) { // there was an error; display it in the status bar m_statusMessage = msg; emit updateStatusMessage(); return; } // get the new value QString expr = cmd->m_expr->computeExpr(); CmdQueueItem* printCmd = m_d->queueCmd(DCprint, expr, DebuggerDriver::QMoverrideMoreEqual); printCmd->m_expr = cmd->m_expr; printCmd->m_exprWnd = cmd->m_exprWnd; } #include "debugger.moc"