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.
2208 lines
58 KiB
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"
|