You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kdbg/kdbg/debugger.cpp

2208 lines
58 KiB

/*
* Copyright Johannes Sixt
* This file is licensed under the GNU General Public License Version 2.
* See the file COPYING in the toplevel directory of the source directory.
*/
#include "debugger.h"
#include "dbgdriver.h"
#include "pgmargs.h"
#include "typetable.h"
#include "exprwnd.h"
#include "pgmsettings.h"
#include "programconfig.h"
#include <tqregexp.h>
#include <tqfileinfo.h>
#include <tqlistbox.h>
#include <tqstringlist.h>
#include <tdeapplication.h>
#include <tdeconfig.h>
#include <tdelocale.h> /* i18n */
#include <tdemessagebox.h>
#include <ctype.h>
#include <stdlib.h> /* strtol, atoi */
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* sleep(3) */
#endif
#include "mydebug.h"
KDebugger::KDebugger(TQWidget* parent,
ExprWnd* localVars,
ExprWnd* watchVars,
TQListBox* backtrace) :
TQObject(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);
connect(&m_localVariables, TQ_SIGNAL(expanded(TQListViewItem*)),
TQ_SLOT(slotExpanding(TQListViewItem*)));
connect(&m_watchVariables, TQ_SIGNAL(expanded(TQListViewItem*)),
TQ_SLOT(slotExpanding(TQListViewItem*)));
connect(&m_localVariables, TQ_SIGNAL(editValueCommitted(VarTree*, const TQString&)),
TQ_SLOT(slotValueEdited(VarTree*, const TQString&)));
connect(&m_watchVariables, TQ_SIGNAL(editValueCommitted(VarTree*, const TQString&)),
TQ_SLOT(slotValueEdited(VarTree*, const TQString&)));
connect(&m_btWindow, TQ_SIGNAL(highlighted(int)), TQ_SLOT(gotoFrame(int)));
emit updateUI();
}
KDebugger::~KDebugger()
{
if (m_programConfig != 0) {
saveProgramSettings();
m_programConfig->sync();
delete m_programConfig;
}
delete m_typeTable;
}
void KDebugger::saveSettings(TDEConfig* /*config*/)
{
}
void KDebugger::restoreSettings(TDEConfig* /*config*/)
{
}
//////////////////////////////////////////////////////////////////////
// external interface
const char GeneralGroup[] = "General";
const char DebuggerCmdStr[] = "DebuggerCmdStr";
const char TTYLevelEntry[] = "TTYLevel";
const char KDebugger::DriverNameEntry[] = "DriverName";
bool KDebugger::debugProgram(const TQString& name,
DebuggerDriver* driver)
{
if (m_d != 0 && m_d->isRunning())
{
TQApplication::setOverrideCursor(waitCursor);
stopDriver();
TQApplication::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, TQ_SIGNAL(activateFileLine(const TQString&,int,const DbgAddr&)),
this, TQ_SIGNAL(activateFileLine(const TQString&,int,const DbgAddr&)));
connect(driver, TQ_SIGNAL(processExited(TDEProcess*)), TQ_SLOT(gdbExited(TDEProcess*)));
connect(driver, TQ_SIGNAL(commandReceived(CmdQueueItem*,const char*)),
TQ_SLOT(parse(CmdQueueItem*,const char*)));
connect(driver, TQ_SIGNAL(wroteStdin(TDEProcess*)), TQ_SIGNAL(updateUI()));
connect(driver, TQ_SIGNAL(inferiorRunning()), TQ_SLOT(slotInferiorRunning()));
connect(driver, TQ_SIGNAL(enterIdleState()), TQ_SLOT(backgroundUpdate()));
connect(driver, TQ_SIGNAL(enterIdleState()), TQ_SIGNAL(updateUI()));
connect(&m_localVariables, TQ_SIGNAL(removingItem(VarTree*)),
driver, TQ_SLOT(dequeueCmdByVar(VarTree*)));
connect(&m_watchVariables, TQ_SIGNAL(removingItem(VarTree*)),
driver, TQ_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::TQMoverride);
m_d->queueCmd(DCframe, 0, DebuggerDriver::TQMnormal);
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(TQString corefile, bool batch)
{
m_corefile = corefile;
if (!batch) {
CmdQueueItem* cmd = loadCoreFile();
cmd->m_byUser = true;
}
}
void KDebugger::setAttachPid(const TQString& 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 = TQString();
m_programActive = true;
}
m_programRunning = true;
}
void KDebugger::attachProgram(const TQString& 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 = TQString();
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 TQString& fileName, int lineNo)
{
if (isReady() && m_programActive && !m_programRunning) {
// strip off directory part of file name
TQString 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(TQWidget* parent)
{
if (m_haveExecutable) {
TQStringList 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(TQWidget* 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() == TQDialog::Accepted)
{
m_debuggerCmd = dlg.m_chooseDriver.debuggerCmd();
m_ttyLevel = TTYLevel(dlg.m_output.ttyLevel());
}
}
bool KDebugger::setBreakpoint(TQString file, int lineNo,
const DbgAddr& address, bool temporary)
{
if (!isReady()) {
return false;
}
BrkptIterator bp = breakpointByFilePos(file, lineNo, address);
if (bp == m_brkpts.end())
{
/*
* 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 = executeBreakpoint(bp, queueOnly);
cmd->m_brkpt = bp; // used in newBreakpoint()
}
CmdQueueItem* KDebugger::executeBreakpoint(const 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
TQString 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::TQMoverride);
} 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::TQMoverride);
} else {
cmd = m_d->executeCmd(bp->temporary ? DCtbreakaddr : DCbreakaddr,
bp->address.asString());
}
}
return cmd;
}
bool KDebugger::enableDisableBreakpoint(TQString file, int lineNo,
const DbgAddr& address)
{
BrkptIterator bp = breakpointByFilePos(file, lineNo, address);
return enableDisableBreakpoint(bp);
}
bool KDebugger::enableDisableBreakpoint(BrkptIterator bp)
{
if (bp == m_brkpts.end())
return false;
/*
* 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(BrkptIterator bp,
const TQString& condition,
int ignoreCount)
{
if (bp == m_brkpts.end())
return false;
/*
* 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::TQMoverride);
}
} else {
bp->condition = condition;
bp->ignoreCount = ignoreCount;
emit breakpointsChanged();
}
return true;
}
bool KDebugger::deleteBreakpoint(BrkptIterator bp)
{
if (bp == m_brkpts.end())
return false;
/*
* 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 {
m_brkpts.erase(bp);
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(TDEProcess*)
{
/*
* 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 {
TQString 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 = TQString(); /* use global setting at next start! */
m_attachedPid = TQString(); /* we are no longer attached to a process */
m_ttyLevel = ttyFull;
m_brkpts.clear();
// erase PC
emit updatePC(TQString(), -1, DbgAddr(), 0);
}
TQString KDebugger::getConfigForExe(const TQString& name)
{
TQFileInfo fi(name);
TQString pgmConfigFile = fi.dirPath(true);
if (!pgmConfigFile.isEmpty()) {
pgmConfigFile += '/';
}
pgmConfigFile += ".kdbgrc." + fi.fileName();
TRACE("program config file = " + pgmConfigFile);
return pgmConfigFile;
}
void KDebugger::openProgramConfig(const TQString& name)
{
ASSERT(m_programConfig == 0);
TQString 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));
TQString 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);
TQDictIterator<EnvVar> it = m_envVars;
EnvVar* var;
TQString varName;
TQString 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);
VarTree* item = m_watchVariables.firstChild();
int watchNum = 0;
for (; item != 0; item = item->nextSibling(), ++watchNum) {
varName.sprintf(ExprFmt, watchNum);
m_programConfig->writeEntry(varName, item->getText());
}
// give others a chance
emit saveProgramSpecific(m_programConfig);
}
void KDebugger::overrideProgramArguments(const TQString& args)
{
ASSERT(m_programConfig != 0);
m_programConfig->setGroup(GeneralGroup);
m_programConfig->writeEntry(ProgramArgs, args);
}
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
TQString pgmArgs = m_programConfig->readEntry(ProgramArgs);
TQString pgmWd = m_programConfig->readEntry(WorkingDirectory);
TQStringList boolOptions = m_programConfig->readListEntry(OptionsSelected);
m_boolOptions = TQStringList();
// read environment variables
m_programConfig->setGroup(EnvironmentGroup);
m_envVars.clear();
TQDict<EnvVar> pgmVars;
EnvVar* var;
TQString varName;
TQString 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;
}
TQString 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;
}
TQString 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.
*/
TQString KDebugger::readDebuggerCmd()
{
TQString debuggerCmd = m_programConfig->readEntry(DebuggerCmdStr);
// always let the user confirm the debugger cmd if we are root
if (::geteuid() == 0)
{
if (!debuggerCmd.isEmpty()) {
TQString 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 = TQString();
}
}
}
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)
{
TQString groupName;
int i = 0;
for (BrkptIterator bp = m_brkpts.begin(); bp != m_brkpts.end(); ++bp)
{
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)
{
TQString 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::TQMoverride);
}
// parse output of command cmd
void KDebugger::parse(CmdQueueItem* cmd, const char* output)
{
ASSERT(cmd != 0); /* queue mustn't be empty */
TRACE(TQString(__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::TQMoverride);
m_programActive = true;
m_programRunning = true;
}
else if (!m_remoteDevice.isEmpty())
{
// handled elsewhere
}
else
{
m_d->queueCmd(DCinfolinemain, DebuggerDriver::TQMnormal);
}
if (!m_statusMessage.isEmpty())
emit updateStatusMessage();
} else {
TQString 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
TQString msg = m_d->driverName() + ": " + TQString(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::TQMnormal);
}
m_corefile = TQString(); /* 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(TQString(), -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::TQMoverrideMoreEqual);
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 DCprintTQStringStruct:
case DCprintWChar:
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 (BrkptIterator bp = m_brkpts.begin(); bp != m_brkpts.end(); ++bp)
{
if (bp->isOrphaned()) {
TRACE(TQString("re-trying brkpt loc: %2 file: %3 line: %1")
.arg(bp->lineNo).arg(bp->location, bp->fileName));
CmdQueueItem* cmd = executeBreakpoint(&*bp, true);
cmd->m_existingBrkpt = bp->id; // used in newBreakpoint()
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::TQMoverride);
}
/*
* 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::TQMoverride);
} else {
// program finished: erase PC
emit updatePC(TQString(), -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::TQMoverride);
}
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::TQMoverride);
// retrieve registers
m_d->queueCmd(DCinforegisters, DebuggerDriver::TQMoverride);
// get new memory dump
if (!m_memoryExpression.isEmpty()) {
queueMemoryDump(false);
}
// update watch expressions
VarTree* item = m_watchVariables.firstChild();
for (; item != 0; item = item->nextSibling()) {
m_watchEvalExpr.push_back(item->getText());
}
}
void KDebugger::updateProgEnvironment(const TQString& args, const TQString& wd,
const TQDict<EnvVar>& newVars,
const TQStringList& 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
TQDictIterator<EnvVar> it = newVars;
EnvVar* val;
for (; (val = it) != 0; ++it) {
TQString 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
TQStringList::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
TQStringList oldVars = m_localVariables.exprList();
/*
* Get local variables.
*/
std::list<ExprValue*> newVars;
parseLocals(output, newVars);
/*
* Clear any old VarTree item pointers, so that later we don't access
* dangling pointers.
*/
m_localVariables.clearPendingUpdates();
/*
* Match old variables against new ones.
*/
for (TQStringList::ConstIterator n = oldVars.begin(); n != oldVars.end(); ++n) {
// lookup this variable in the list of new variables
std::list<ExprValue*>::iterator v = newVars.begin();
while (v != newVars.end() && (*v)->m_name != *n)
++v;
if (v == newVars.end()) {
// old variable not in the new variables
TRACE("old var deleted: " + *n);
VarTree* v = m_localVariables.topLevelExprByName(*n);
if (v != 0) {
m_localVariables.removeExpr(v);
}
} else {
// variable in both old and new lists: update
TRACE("update var: " + *n);
m_localVariables.updateExpr(*v, *m_typeTable);
// remove the new variable from the list
delete *v;
newVars.erase(v);
}
}
// insert all remaining new variables
while (!newVars.empty())
{
ExprValue* v = newVars.front();
TRACE("new var: " + v->m_name);
m_localVariables.insertExpr(v, *m_typeTable);
delete v;
newVars.pop_front();
}
}
void KDebugger::parseLocals(const char* output, std::list<ExprValue*>& newVars)
{
std::list<ExprValue*> vars;
m_d->parseLocals(output, vars);
TQString origName; /* used in renaming variables */
while (!vars.empty())
{
ExprValue* variable = vars.front();
vars.pop_front();
/*
* 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->m_name;
for (std::list<ExprValue*>::iterator v = newVars.begin(); v != newVars.end(); ++v) {
if (variable->m_name == (*v)->m_name) {
// we found a duplicate, change name
block++;
TQString newName = origName + " (" + TQString().setNum(block) + ")";
variable->m_name = newName;
}
}
newVars.push_back(variable);
}
}
bool KDebugger::handlePrint(CmdQueueItem* cmd, const char* output)
{
ASSERT(cmd->m_expr != 0);
ExprValue* variable = m_d->parsePrintExpr(output, true);
if (variable == 0)
return false;
// set expression "name"
variable->m_name = cmd->m_expr->getText();
{
TRACE("update expr: " + cmd->m_expr->getText());
cmd->m_exprWnd->updateExpr(cmd->m_expr, variable, *m_typeTable);
delete variable;
}
evalExpressions(); /* enqueue dereferenced pointers */
return true;
}
bool KDebugger::handlePrintDeref(CmdQueueItem* cmd, const char* output)
{
ASSERT(cmd->m_expr != 0);
ExprValue* variable = m_d->parsePrintExpr(output, true);
if (variable == 0)
return false;
// set expression "name"
variable->m_name = cmd->m_expr->getText();
{
/*
* We must insert a dummy parent, because otherwise variable's value
* would overwrite cmd->m_expr's value.
*/
ExprValue* dummyParent = new ExprValue(variable->m_name, VarTree::NKplain);
dummyParent->m_varKind = VarTree::VKdummy;
// the name of the parsed variable is the address of the pointer
TQString addr = "*" + cmd->m_expr->value();
variable->m_name = addr;
variable->m_nameKind = VarTree::NKaddress;
dummyParent->m_child = variable;
// expand the first level for convenience
variable->m_initiallyExpanded = true;
TRACE("update ptr: " + cmd->m_expr->getText());
cmd->m_exprWnd->updateExpr(cmd->m_expr, dummyParent, *m_typeTable);
delete dummyParent;
}
evalExpressions(); /* enqueue dereferenced pointers */
return true;
}
// parse the output of bt
void KDebugger::handleBacktrace(const char* output)
{
// reduce flicker
m_btWindow.setUpdatesEnabled(false);
m_btWindow.clear();
std::list<StackFrame> stack;
m_d->parseBackTrace(output, stack);
if (!stack.empty()) {
std::list<StackFrame>::iterator frm = stack.begin();
// first frame must set PC
// note: frm->lineNo is zero-based
emit updatePC(frm->fileName, frm->lineNo, frm->address, frm->frameNo);
for (; frm != stack.end(); ++frm) {
TQString func;
if (frm->var != 0)
func = frm->var->m_name;
else
func = frm->fileName + ":" + TQString().setNum(frm->lineNo+1);
m_btWindow.insertItem(func);
TRACE("frame " + func + " (" + frm->fileName + ":" +
TQString().setNum(frm->lineNo+1) + ")");
}
}
m_btWindow.setUpdatesEnabled(true);
m_btWindow.repaint();
}
void KDebugger::gotoFrame(int frame)
{
m_d->executeCmd(DCframe, frame);
}
void KDebugger::handleFrameChange(const char* output)
{
TQString 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
// struct members in local variables
// struct members in watch expressions
VarTree* exprItem = 0;
if (!m_watchEvalExpr.empty())
{
TQString expr = m_watchEvalExpr.front();
m_watchEvalExpr.pop_front();
exprItem = m_watchVariables.topLevelExprByName(expr);
}
if (exprItem != 0) {
CmdQueueItem* cmd = m_d->queueCmd(DCprint, exprItem->getText(), DebuggerDriver::TQMoverride);
// remember which expr this was
cmd->m_expr = exprItem;
cmd->m_exprWnd = &m_watchVariables;
} else {
ExprWnd* wnd;
#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);
TQString expr = exprItem->computeExpr();
TRACE("dereferencing pointer: " + expr);
CmdQueueItem* cmd;
if (immediate) {
cmd = m_d->queueCmd(DCprintDeref, expr, DebuggerDriver::TQMoverrideMoreEqual);
} else {
cmd = m_d->queueCmd(DCprintDeref, expr, DebuggerDriver::TQMoverride);
}
// 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);
TQString expr = exprItem->computeExpr();
TRACE("get type of: " + expr);
CmdQueueItem* cmd;
cmd = m_d->queueCmd(DCfindType, expr, DebuggerDriver::TQMoverride);
// remember which expr this was
cmd->m_expr = exprItem;
cmd->m_exprWnd = wnd;
}
void KDebugger::handleFindType(CmdQueueItem* cmd, const char* output)
{
TQString 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 "+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);
ExprValue* partExpr;
if (cmd->m_cmd == DCprintTQStringStruct) {
partExpr = m_d->parseTQCharArray(output, false, m_typeTable->qCharIsShort());
} else if (cmd->m_cmd == DCprintWChar) {
partExpr = m_d->parseTQCharArray(output, false, true);
} else {
partExpr = m_d->parsePrintExpr(output, false);
}
bool errorValue =
partExpr == 0 ||
/* we only allow simple values at the moment */
partExpr->m_child != 0;
TQString 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 TQString.
*/
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;
if (var->m_type != TypeInfo::wchartType())
{
var->m_exprIndexUseGuard = true;
var->m_partialValue = var->m_type->m_displayString[0];
evalStructExpression(var, wnd, immediate);
}
else
{
var->m_exprIndexUseGuard = false;
TQString expr = var->computeExpr();
CmdQueueItem* cmd = m_d->queueCmd(DCprintWChar, expr,
immediate ? DebuggerDriver::TQMoverrideMoreEqual
: DebuggerDriver::TQMoverride);
// remember which expression this was
cmd->m_expr = var;
cmd->m_exprWnd = wnd;
}
}
/** queues a printStruct command; var must have been initialized correctly */
void KDebugger::evalStructExpression(VarTree* var, ExprWnd* wnd, bool immediate)
{
TQString base = var->computeExpr();
TQString expr;
if (var->m_exprIndexUseGuard) {
expr = var->m_type->m_guardStrings[var->m_exprIndex];
if (expr.isEmpty()) {
// no guard, omit it and go to expression
var->m_exprIndexUseGuard = false;
}
}
if (!var->m_exprIndexUseGuard) {
expr = var->m_type->m_exprStrings[var->m_exprIndex];
}
expr.replace("%s", base);
DbgCommand dbgCmd = DCprintStruct;
// check if this is a TQString::Data
if (expr.left(15) == "/TQString::Data ")
{
if (m_typeTable->parseTQt2TQStrings())
{
expr = expr.mid(15, expr.length()); /* strip off /TQString::Data */
dbgCmd = DCprintTQStringStruct;
} 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., tdecore was loaded but qt2
* was not (only qt2 enables the TQString feature).
*/
// TODO: remove this "print"; queue the next printStruct instead
expr = "*0";
}
}
TRACE("evalStruct: " + expr + (var->m_exprIndexUseGuard ? " // guard" : " // real"));
CmdQueueItem* cmd = m_d->queueCmd(dbgCmd, expr,
immediate ? DebuggerDriver::TQMoverrideMoreEqual
: DebuggerDriver::TQMnormal);
// remember which expression this was
cmd->m_expr = var;
cmd->m_exprWnd = wnd;
}
void KDebugger::handleSharedLibs(const char* output)
{
// parse the table of shared libraries
m_sharedLibs = m_d->parseSharedLibs(output);
m_sharedLibsListed = true;
// get type libraries
m_typeTable->loadLibTypes(m_sharedLibs);
// hand over the TQString data cmd
m_d->setPrintTQStringDataCmd(m_typeTable->printTQStringDataCmd());
}
CmdQueueItem* KDebugger::loadCoreFile()
{
return m_d->queueCmd(DCcorefile, m_corefile, DebuggerDriver::TQMoverride);
}
void KDebugger::slotExpanding(TQListViewItem* item)
{
VarTree* exprItem = static_cast<VarTree*>(item);
if (exprItem->m_varKind != VarTree::VKpointer) {
return;
}
ExprWnd* wnd = static_cast<ExprWnd*>(item->listView());
dereferencePointer(wnd, exprItem, true);
}
// add the expression in the edit field to the watch expressions
void KDebugger::addWatch(const TQString& t)
{
TQString expr = t.stripWhiteSpace();
// don't add a watched expression again
if (expr.isEmpty() || m_watchVariables.topLevelExprByName(expr) != 0)
return;
ExprValue e(expr, VarTree::NKplain);
m_watchVariables.insertExpr(&e, *m_typeTable);
// if we are boring ourselves, send down the command
if (m_programActive) {
m_watchEvalExpr.push_back(expr);
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 == 0 || !m_d->isIdle())
return;
VarTree* item = m_watchVariables.currentItem();
if (item == 0 || !item->isToplevelExpr())
return;
// remove the variable from the list to evaluate
TQStringList::iterator i = m_watchEvalExpr.find(item->getText());
if (i != m_watchEvalExpr.end()) {
m_watchEvalExpr.erase(i);
}
m_watchVariables.removeExpr(item);
// item is invalid at this point!
}
void KDebugger::handleRegisters(const char* output)
{
emit registersChanged(m_d->parseRegisters(output));
}
/*
* 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)
{
BrkptIterator bp;
if (cmd->m_brkpt != 0) {
// a new breakpoint, put it in the list
assert(cmd->m_brkpt->id == 0);
m_brkpts.push_back(*cmd->m_brkpt);
delete cmd->m_brkpt;
bp = m_brkpts.end();
--bp;
} else {
// an existing breakpoint was retried
assert(cmd->m_existingBrkpt != 0);
bp = breakpointById(cmd->m_existingBrkpt);
if (bp == m_brkpts.end())
return;
}
// parse the output to determine success or failure
int id;
TQString file;
int lineNo;
TQString 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 (bp->id == 0)
{
int minId = 0;
for (BrkptIterator i = m_brkpts.begin(); i != m_brkpts.end(); ++i) {
if (i->id < minId)
minId = i->id;
}
bp->id = minId-1;
}
return;
}
// The breakpoint was successfully set.
if (bp->id <= 0)
{
// this is a new or orphaned breakpoint:
// set the remaining properties
if (!bp->enabled) {
m_d->executeCmd(DCdisable, id);
}
if (!bp->condition.isEmpty()) {
m_d->executeCmd(DCcondition, bp->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
std::list<Breakpoint> brks;
m_d->parseBreakList(output, brks);
// merge existing information into the new list
// then swap the old and new lists
for (BrkptIterator bp = brks.begin(); bp != brks.end(); ++bp)
{
BrkptIterator i = breakpointById(bp->id);
if (i != m_brkpts.end())
{
// preserve accurate location information
// note that xsldbg doesn't have a location in
// the listed breakpoint if it has just been set
// therefore, we copy it as well if necessary
bp->text = i->text;
if (!i->fileName.isEmpty()) {
bp->fileName = i->fileName;
bp->lineNo = i->lineNo;
}
}
}
// orphaned breakpoints must be copied
for (BrkptIterator bp = m_brkpts.begin(); bp != m_brkpts.end(); ++bp)
{
if (bp->isOrphaned())
brks.push_back(*bp);
}
m_brkpts.swap(brks);
emit breakpointsChanged();
}
// look if there is at least one temporary breakpoint
// or a watchpoint
bool KDebugger::stopMayChangeBreakList() const
{
for (BrkptROIterator bp = m_brkpts.begin(); bp != m_brkpts.end(); ++bp)
{
if (bp->temporary || bp->type == Breakpoint::watchpoint)
return true;
}
return false;
}
KDebugger::BrkptIterator KDebugger::breakpointByFilePos(TQString file, int lineNo,
const DbgAddr& address)
{
// look for exact file name match
for (BrkptIterator bp = m_brkpts.begin(); bp != m_brkpts.end(); ++bp)
{
if (bp->lineNo == lineNo &&
bp->fileName == file &&
(address.isEmpty() || bp->address == address))
{
return bp;
}
}
// not found, so try basename
// strip off directory part of file name
int offset = file.findRev("/");
file.remove(0, offset+1);
for (BrkptIterator bp = m_brkpts.begin(); bp != m_brkpts.end(); ++bp)
{
// get base name of breakpoint's file
TQString basename = bp->fileName;
int offset = basename.findRev("/");
if (offset >= 0) {
basename.remove(0, offset+1);
}
if (bp->lineNo == lineNo &&
basename == file &&
(address.isEmpty() || bp->address == address))
{
return bp;
}
}
// not found
return m_brkpts.end();
}
KDebugger::BrkptIterator KDebugger::breakpointById(int id)
{
for (BrkptIterator bp = m_brkpts.begin(); bp != m_brkpts.end(); ++bp)
{
if (bp->id == id) {
return bp;
}
}
// not found
return m_brkpts.end();
}
void KDebugger::slotValuePopup(const TQString& 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
TQString tip = v->getText() + " = ";
if (!v->value().isEmpty())
{
tip += v->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 TQString& fileName, int lineNo)
{
if (m_haveExecutable) {
CmdQueueItem* cmd = m_d->queueCmd(DCinfoline, fileName, lineNo,
DebuggerDriver::TQMoverrideMoreEqual);
cmd->m_fileName = fileName;
cmd->m_lineNo = lineNo;
}
}
void KDebugger::handleInfoLine(CmdQueueItem* cmd, const char* output)
{
TQString 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::TQMoverrideMoreEqual);
c->m_fileName = cmd->m_fileName;
c->m_lineNo = cmd->m_lineNo;
} else {
// no code
emit disassembled(cmd->m_fileName, cmd->m_lineNo, std::list<DisassembledCode>());
}
} 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)
{
emit disassembled(cmd->m_fileName, cmd->m_lineNo,
m_d->parseDisassemble(output));
}
void KDebugger::handleThreadList(const char* output)
{
emit threadsChanged(m_d->parseThreadList(output));
}
void KDebugger::setThread(int id)
{
m_d->queueCmd(DCthread, id, DebuggerDriver::TQMoverrideMoreEqual);
}
void KDebugger::setMemoryExpression(const TQString& 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::TQMoverrideMoreEqual :
DebuggerDriver::TQMoverride);
}
void KDebugger::handleMemoryDump(const char* output)
{
std::list<MemoryDump> memdump;
TQString msg = m_d->parseMemoryDump(output, memdump);
emit memoryDumpChanged(msg, memdump);
}
void KDebugger::setProgramCounter(const TQString& 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(VarTree* expr, const TQString& text)
{
if (text.simplifyWhiteSpace().isEmpty())
return; /* no text entered: ignore request */
ExprWnd* wnd = static_cast<ExprWnd*>(expr->listView());
TRACE(TQString("Changing %1 to ").arg(wnd->name()) + text);
// determine the lvalue to edit
TQString 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)
{
TQString 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
TQString expr = cmd->m_expr->computeExpr();
CmdQueueItem* printCmd =
m_d->queueCmd(DCprint, expr, DebuggerDriver::TQMoverrideMoreEqual);
printCmd->m_expr = cmd->m_expr;
printCmd->m_exprWnd = cmd->m_exprWnd;
}
#include "debugger.moc"