summaryrefslogtreecommitdiffstats
path: root/kdbg/gdbdriver.cpp
diff options
context:
space:
mode:
authorSlávek Banko <slavek.banko@axis.cz>2013-07-03 01:47:30 +0200
committerSlávek Banko <slavek.banko@axis.cz>2013-07-03 01:47:30 +0200
commit239e873a38fa91a3fbd27e134bda015922abbabd (patch)
treed7c79f740bce93768ee78b6b787c83a2115b047f /kdbg/gdbdriver.cpp
downloadkdbg-239e873a38fa91a3fbd27e134bda015922abbabd.tar.gz
kdbg-239e873a38fa91a3fbd27e134bda015922abbabd.zip
Initial import
Diffstat (limited to 'kdbg/gdbdriver.cpp')
-rw-r--r--kdbg/gdbdriver.cpp2572
1 files changed, 2572 insertions, 0 deletions
diff --git a/kdbg/gdbdriver.cpp b/kdbg/gdbdriver.cpp
new file mode 100644
index 0000000..c0fcc94
--- /dev/null
+++ b/kdbg/gdbdriver.cpp
@@ -0,0 +1,2572 @@
+/*
+ * 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 "gdbdriver.h"
+#include "exprwnd.h"
+#include <qregexp.h>
+#include <qstringlist.h>
+#include <klocale.h> /* i18n */
+#include <ctype.h>
+#include <stdlib.h> /* strtol, atoi */
+#include <string.h> /* strcpy */
+
+#include "assert.h"
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "mydebug.h"
+
+static void skipString(const char*& p);
+static void skipNested(const char*& s, char opening, char closing);
+static ExprValue* parseVar(const char*& s);
+static bool parseName(const char*& s, QString& name, VarTree::NameKind& kind);
+static bool parseValue(const char*& s, ExprValue* variable);
+static bool parseNested(const char*& s, ExprValue* variable);
+static bool parseVarSeq(const char*& s, ExprValue* variable);
+static bool parseValueSeq(const char*& s, ExprValue* variable);
+
+#define PROMPT "(kdbg)"
+#define PROMPT_LEN 6
+#define PROMPT_LAST_CHAR ')' /* needed when searching for prompt string */
+
+
+// TODO: make this cmd info stuff non-static to allow multiple
+// simultaneous gdbs to run!
+
+struct GdbCmdInfo {
+ DbgCommand cmd;
+ const char* fmt; /* format string */
+ enum Args {
+ argNone, argString, argNum,
+ argStringNum, argNumString,
+ argString2, argNum2
+ } argsNeeded;
+};
+
+#if 0
+// This is how the QString data print statement generally looks like.
+// It is set by KDebugger via setPrintQStringDataCmd().
+
+static const char printQStringStructFmt[] =
+ // if the string data is junk, fail early
+ "print ($qstrunicode=($qstrdata=(%s))->unicode)?"
+ // print an array of shorts
+ "(*(unsigned short*)$qstrunicode)@"
+ // limit the length
+ "(($qstrlen=(unsigned int)($qstrdata->len))>100?100:$qstrlen)"
+ // if unicode data is 0, report a special value
+ ":1==0\n";
+#endif
+static const char printQStringStructFmt[] = "print (0?\"%s\":$kdbgundef)\n";
+
+/*
+ * The following array of commands must be sorted by the DC* values,
+ * because they are used as indices.
+ */
+static GdbCmdInfo cmds[] = {
+ { DCinitialize, "", GdbCmdInfo::argNone },
+ { DCtty, "tty %s\n", GdbCmdInfo::argString },
+ { DCexecutable, "file \"%s\"\n", GdbCmdInfo::argString },
+ { DCtargetremote, "target remote %s\n", GdbCmdInfo::argString },
+ { DCcorefile, "core-file %s\n", GdbCmdInfo::argString },
+ { DCattach, "attach %s\n", GdbCmdInfo::argString },
+ { DCinfolinemain, "kdbg_infolinemain\n", GdbCmdInfo::argNone },
+ { DCinfolocals, "kdbg__alllocals\n", GdbCmdInfo::argNone },
+ { DCinforegisters, "info all-registers\n", GdbCmdInfo::argNone},
+ { DCexamine, "x %s %s\n", GdbCmdInfo::argString2 },
+ { DCinfoline, "info line %s:%d\n", GdbCmdInfo::argStringNum },
+ { DCdisassemble, "disassemble %s %s\n", GdbCmdInfo::argString2 },
+ { DCsetargs, "set args %s\n", GdbCmdInfo::argString },
+ { DCsetenv, "set env %s %s\n", GdbCmdInfo::argString2 },
+ { DCunsetenv, "unset env %s\n", GdbCmdInfo::argString },
+ { DCsetoption, "setoption %s %d\n", GdbCmdInfo::argStringNum},
+ { DCcd, "cd %s\n", GdbCmdInfo::argString },
+ { DCbt, "bt\n", GdbCmdInfo::argNone },
+ { DCrun, "run\n", GdbCmdInfo::argNone },
+ { DCcont, "cont\n", GdbCmdInfo::argNone },
+ { DCstep, "step\n", GdbCmdInfo::argNone },
+ { DCstepi, "stepi\n", GdbCmdInfo::argNone },
+ { DCnext, "next\n", GdbCmdInfo::argNone },
+ { DCnexti, "nexti\n", GdbCmdInfo::argNone },
+ { DCfinish, "finish\n", GdbCmdInfo::argNone },
+ { DCuntil, "until %s:%d\n", GdbCmdInfo::argStringNum },
+ { DCkill, "kill\n", GdbCmdInfo::argNone },
+ { DCbreaktext, "break %s\n", GdbCmdInfo::argString },
+ { DCbreakline, "break %s:%d\n", GdbCmdInfo::argStringNum },
+ { DCtbreakline, "tbreak %s:%d\n", GdbCmdInfo::argStringNum },
+ { DCbreakaddr, "break *%s\n", GdbCmdInfo::argString },
+ { DCtbreakaddr, "tbreak *%s\n", GdbCmdInfo::argString },
+ { DCwatchpoint, "watch %s\n", GdbCmdInfo::argString },
+ { DCdelete, "delete %d\n", GdbCmdInfo::argNum },
+ { DCenable, "enable %d\n", GdbCmdInfo::argNum },
+ { DCdisable, "disable %d\n", GdbCmdInfo::argNum },
+ { DCprint, "print %s\n", GdbCmdInfo::argString },
+ { DCprintDeref, "print *(%s)\n", GdbCmdInfo::argString },
+ { DCprintStruct, "print %s\n", GdbCmdInfo::argString },
+ { DCprintQStringStruct, printQStringStructFmt, GdbCmdInfo::argString},
+ { DCframe, "frame %d\n", GdbCmdInfo::argNum },
+ { DCfindType, "whatis %s\n", GdbCmdInfo::argString },
+ { DCinfosharedlib, "info sharedlibrary\n", GdbCmdInfo::argNone },
+ { DCthread, "thread %d\n", GdbCmdInfo::argNum },
+ { DCinfothreads, "info threads\n", GdbCmdInfo::argNone },
+ { DCinfobreak, "info breakpoints\n", GdbCmdInfo::argNone },
+ { DCcondition, "condition %d %s\n", GdbCmdInfo::argNumString},
+ { DCsetpc, "set variable $pc=%s\n", GdbCmdInfo::argString },
+ { DCignore, "ignore %d %d\n", GdbCmdInfo::argNum2},
+ { DCprintWChar, "print ($s=%s)?*$s@wcslen($s):0x0\n", GdbCmdInfo::argString },
+ { DCsetvariable, "set variable %s=%s\n", GdbCmdInfo::argString2 },
+};
+
+#define NUM_CMDS (int(sizeof(cmds)/sizeof(cmds[0])))
+#define MAX_FMTLEN 200
+
+GdbDriver::GdbDriver() :
+ DebuggerDriver(),
+ m_gdbMajor(4), m_gdbMinor(16)
+{
+ strcpy(m_prompt, PROMPT);
+ m_promptMinLen = PROMPT_LEN;
+ m_promptLastChar = PROMPT_LAST_CHAR;
+
+#ifndef NDEBUG
+ // check command info array
+ const char* perc;
+ for (int i = 0; i < NUM_CMDS; i++) {
+ // must be indexable by DbgCommand values, i.e. sorted by DbgCommand values
+ assert(i == cmds[i].cmd);
+ // a format string must be associated
+ assert(cmds[i].fmt != 0);
+ assert(strlen(cmds[i].fmt) <= MAX_FMTLEN);
+ // format string must match arg specification
+ switch (cmds[i].argsNeeded) {
+ case GdbCmdInfo::argNone:
+ assert(strchr(cmds[i].fmt, '%') == 0);
+ break;
+ case GdbCmdInfo::argString:
+ perc = strchr(cmds[i].fmt, '%');
+ assert(perc != 0 && perc[1] == 's');
+ assert(strchr(perc+2, '%') == 0);
+ break;
+ case GdbCmdInfo::argNum:
+ perc = strchr(cmds[i].fmt, '%');
+ assert(perc != 0 && perc[1] == 'd');
+ assert(strchr(perc+2, '%') == 0);
+ break;
+ case GdbCmdInfo::argStringNum:
+ perc = strchr(cmds[i].fmt, '%');
+ assert(perc != 0 && perc[1] == 's');
+ perc = strchr(perc+2, '%');
+ assert(perc != 0 && perc[1] == 'd');
+ assert(strchr(perc+2, '%') == 0);
+ break;
+ case GdbCmdInfo::argNumString:
+ perc = strchr(cmds[i].fmt, '%');
+ assert(perc != 0 && perc[1] == 'd');
+ perc = strchr(perc+2, '%');
+ assert(perc != 0 && perc[1] == 's');
+ assert(strchr(perc+2, '%') == 0);
+ break;
+ case GdbCmdInfo::argString2:
+ perc = strchr(cmds[i].fmt, '%');
+ assert(perc != 0 && perc[1] == 's');
+ perc = strchr(perc+2, '%');
+ assert(perc != 0 && perc[1] == 's');
+ assert(strchr(perc+2, '%') == 0);
+ break;
+ case GdbCmdInfo::argNum2:
+ perc = strchr(cmds[i].fmt, '%');
+ assert(perc != 0 && perc[1] == 'd');
+ perc = strchr(perc+2, '%');
+ assert(perc != 0 && perc[1] == 'd');
+ assert(strchr(perc+2, '%') == 0);
+ break;
+ }
+ }
+ assert(strlen(printQStringStructFmt) <= MAX_FMTLEN);
+#endif
+}
+
+GdbDriver::~GdbDriver()
+{
+}
+
+
+QString GdbDriver::driverName() const
+{
+ return "GDB";
+}
+
+QString GdbDriver::defaultGdb()
+{
+ return
+ "gdb"
+ " --fullname" /* to get standard file names each time the prog stops */
+ " --nx"; /* do not execute initialization files */
+}
+
+QString GdbDriver::defaultInvocation() const
+{
+ if (m_defaultCmd.isEmpty()) {
+ return defaultGdb();
+ } else {
+ return m_defaultCmd;
+ }
+}
+
+QStringList GdbDriver::boolOptionList() const
+{
+ // no options
+ return QStringList();
+}
+
+bool GdbDriver::startup(QString cmdStr)
+{
+ if (!DebuggerDriver::startup(cmdStr))
+ return false;
+
+ static const char gdbInitialize[] =
+ /*
+ * Work around buggy gdbs that do command line editing even if they
+ * are not on a tty. The readline library echos every command back
+ * in this case, which is confusing for us.
+ */
+ "set editing off\n"
+ "set confirm off\n"
+ "set print static-members off\n"
+ "set print asm-demangle on\n"
+ /*
+ * Don't assume that program functions invoked from a watch expression
+ * always succeed.
+ */
+ "set unwindonsignal on\n"
+ /*
+ * Write a short macro that prints all locals: local variables and
+ * function arguments.
+ */
+ "define kdbg__alllocals\n"
+ "info locals\n" /* local vars supersede args with same name */
+ "info args\n" /* therefore, arguments must come last */
+ "end\n"
+ /*
+ * Work around a bug in gdb-6.3: "info line main" crashes gdb.
+ */
+ "define kdbg_infolinemain\n"
+ "list\n"
+ "info line\n"
+ "end\n"
+ // change prompt string and synchronize with gdb
+ "set prompt " PROMPT "\n"
+ ;
+
+ executeCmdString(DCinitialize, gdbInitialize, false);
+
+ // assume that QString::null is ok
+ cmds[DCprintQStringStruct].fmt = printQStringStructFmt;
+
+ return true;
+}
+
+void GdbDriver::commandFinished(CmdQueueItem* cmd)
+{
+ // command string must be committed
+ if (!cmd->m_committed) {
+ // not commited!
+ TRACE("calling " + (__PRETTY_FUNCTION__ + (" with uncommited command:\n\t" +
+ cmd->m_cmdString)));
+ return;
+ }
+
+ switch (cmd->m_cmd) {
+ case DCinitialize:
+ // get version number from preamble
+ {
+ int len;
+ QRegExp GDBVersion("\\nGDB [0-9]+\\.[0-9]+");
+ int offset = GDBVersion.match(m_output, 0, &len);
+ if (offset >= 0) {
+ char* start = m_output + offset + 5; // skip "\nGDB "
+ char* end;
+ m_gdbMajor = strtol(start, &end, 10);
+ m_gdbMinor = strtol(end + 1, 0, 10); // skip "."
+ if (start == end) {
+ // nothing was parsed
+ m_gdbMajor = 4;
+ m_gdbMinor = 16;
+ }
+ } else {
+ // assume some default version (what would make sense?)
+ m_gdbMajor = 4;
+ m_gdbMinor = 16;
+ }
+ // use a feasible core-file command
+ if (m_gdbMajor > 4 || (m_gdbMajor == 4 && m_gdbMinor >= 16)) {
+#ifdef __FreeBSD__
+ cmds[DCcorefile].fmt = "target FreeBSD-core %s\n";
+#else
+ cmds[DCcorefile].fmt = "target core %s\n";
+#endif
+ } else {
+ cmds[DCcorefile].fmt = "core-file %s\n";
+ }
+ }
+ {
+ /*
+ * Check for GDB 7.1 or later; the syntax for the disassemble
+ * command has changed.
+ * This RE picks the last version number in the first line,
+ * because at least OpenSUSE writes its own version number
+ * in the first line (but before GDB's version number).
+ */
+ QRegExp re(
+ " " // must be preceded by space
+ "[(]?" // SLES 10 embeds in parentheses
+ "(\\d+)\\.(\\d+)" // major, minor
+ "[^ ]*\\n" // no space until end of line
+ );
+ int pos = re.search(m_output);
+ const char* disass = "disassemble %s %s\n";
+ if (pos >= 0) {
+ int major = re.cap(1).toInt();
+ int minor = re.cap(2).toInt();
+ if (major > 7 || (major == 7 && minor >= 1))
+ {
+ disass = "disassemble %s, %s\n";
+ }
+ }
+ cmds[DCdisassemble].fmt = disass;
+ }
+ break;
+ default:;
+ }
+
+ /* ok, the command is ready */
+ emit commandReceived(cmd, m_output);
+
+ switch (cmd->m_cmd) {
+ case DCcorefile:
+ case DCinfolinemain:
+ case DCframe:
+ case DCattach:
+ case DCrun:
+ case DCcont:
+ case DCstep:
+ case DCstepi:
+ case DCnext:
+ case DCnexti:
+ case DCfinish:
+ case DCuntil:
+ parseMarker();
+ default:;
+ }
+}
+
+/*
+ * The --fullname option makes gdb send a special normalized sequence print
+ * each time the program stops and at some other points. The sequence has
+ * the form "\032\032filename:lineno:charoffset:(beg|middle):address".
+ */
+void GdbDriver::parseMarker()
+{
+ char* startMarker = strstr(m_output, "\032\032");
+ if (startMarker == 0)
+ return;
+
+ // extract the marker
+ startMarker += 2;
+ TRACE(QString("found marker: ") + startMarker);
+ char* endMarker = strchr(startMarker, '\n');
+ if (endMarker == 0)
+ return;
+
+ *endMarker = '\0';
+
+ // extract filename and line number
+ static QRegExp MarkerRE(":[0-9]+:[0-9]+:[begmidl]+:0x");
+
+ int len;
+ int lineNoStart = MarkerRE.match(startMarker, 0, &len);
+ if (lineNoStart >= 0) {
+ int lineNo = atoi(startMarker + lineNoStart+1);
+
+ // get address
+ const char* addrStart = startMarker + lineNoStart + len - 2;
+ DbgAddr address = QString(addrStart).stripWhiteSpace();
+
+ // now show the window
+ startMarker[lineNoStart] = '\0'; /* split off file name */
+ emit activateFileLine(startMarker, lineNo-1, address);
+ }
+}
+
+
+/*
+ * Escapes characters that might lead to problems when they appear on gdb's
+ * command line.
+ */
+static void normalizeStringArg(QString& arg)
+{
+ /*
+ * Remove trailing backslashes. This approach is a little simplistic,
+ * but we know that there is at the moment no case where a trailing
+ * backslash would make sense.
+ */
+ while (!arg.isEmpty() && arg[arg.length()-1] == '\\') {
+ arg = arg.left(arg.length()-1);
+ }
+}
+
+
+QString GdbDriver::makeCmdString(DbgCommand cmd, QString strArg)
+{
+ assert(cmd >= 0 && cmd < NUM_CMDS);
+ assert(cmds[cmd].argsNeeded == GdbCmdInfo::argString);
+
+ normalizeStringArg(strArg);
+
+ if (cmd == DCcd) {
+ // need the working directory when parsing the output
+ m_programWD = strArg;
+ } else if (cmd == DCsetargs && !m_redirect.isEmpty()) {
+ /*
+ * Use saved redirection. We prepend it in front of the user's
+ * arguments so that the user can override the redirections.
+ */
+ strArg = m_redirect + " " + strArg;
+ }
+
+ QString cmdString;
+ cmdString.sprintf(cmds[cmd].fmt, strArg.latin1());
+ return cmdString;
+}
+
+QString GdbDriver::makeCmdString(DbgCommand cmd, int intArg)
+{
+ assert(cmd >= 0 && cmd < NUM_CMDS);
+ assert(cmds[cmd].argsNeeded == GdbCmdInfo::argNum);
+
+ QString cmdString;
+ cmdString.sprintf(cmds[cmd].fmt, intArg);
+ return cmdString;
+}
+
+QString GdbDriver::makeCmdString(DbgCommand cmd, QString strArg, int intArg)
+{
+ assert(cmd >= 0 && cmd < NUM_CMDS);
+ assert(cmds[cmd].argsNeeded == GdbCmdInfo::argStringNum ||
+ cmds[cmd].argsNeeded == GdbCmdInfo::argNumString ||
+ cmd == DCexamine ||
+ cmd == DCtty);
+
+ normalizeStringArg(strArg);
+
+ QString cmdString;
+
+ if (cmd == DCtty)
+ {
+ /*
+ * intArg specifies which channels should be redirected to
+ * /dev/null. It is a value or'ed together from RDNstdin,
+ * RDNstdout, RDNstderr. We store the value for a later DCsetargs
+ * command.
+ *
+ * Note: We rely on that after the DCtty a DCsetargs will follow,
+ * which will ultimately apply the redirection.
+ */
+ static const char* const runRedir[8] = {
+ "",
+ "</dev/null",
+ ">/dev/null",
+ "</dev/null >/dev/null",
+ "2>/dev/null",
+ "</dev/null 2>/dev/null",
+ ">/dev/null 2>&1",
+ "</dev/null >/dev/null 2>&1"
+ };
+ if (strArg.isEmpty())
+ intArg = 7; /* failsafe if no tty */
+ m_redirect = runRedir[intArg & 7];
+
+ return makeCmdString(DCtty, strArg); /* note: no problem if strArg empty */
+ }
+
+ if (cmd == DCexamine) {
+ // make a format specifier from the intArg
+ static const char size[16] = {
+ '\0', 'b', 'h', 'w', 'g'
+ };
+ static const char format[16] = {
+ '\0', 'x', 'd', 'u', 'o', 't',
+ 'a', 'c', 'f', 's', 'i'
+ };
+ assert(MDTsizemask == 0xf); /* lowest 4 bits */
+ assert(MDTformatmask == 0xf0); /* next 4 bits */
+ int count = 16; /* number of entities to print */
+ char sizeSpec = size[intArg & MDTsizemask];
+ char formatSpec = format[(intArg & MDTformatmask) >> 4];
+ assert(sizeSpec != '\0');
+ assert(formatSpec != '\0');
+ // adjust count such that 16 lines are printed
+ switch (intArg & MDTformatmask) {
+ case MDTstring: case MDTinsn:
+ break; /* no modification needed */
+ default:
+ // all cases drop through:
+ switch (intArg & MDTsizemask) {
+ case MDTbyte:
+ case MDThalfword:
+ count *= 2;
+ case MDTword:
+ count *= 2;
+ case MDTgiantword:
+ count *= 2;
+ }
+ break;
+ }
+ QString spec;
+ spec.sprintf("/%d%c%c", count, sizeSpec, formatSpec);
+
+ return makeCmdString(DCexamine, spec, strArg);
+ }
+
+ if (cmds[cmd].argsNeeded == GdbCmdInfo::argStringNum)
+ {
+ // line numbers are zero-based
+ if (cmd == DCuntil || cmd == DCbreakline ||
+ cmd == DCtbreakline || cmd == DCinfoline)
+ {
+ intArg++;
+ }
+ if (cmd == DCinfoline)
+ {
+ // must split off file name part
+ int slash = strArg.findRev('/');
+ if (slash >= 0)
+ strArg = strArg.right(strArg.length()-slash-1);
+ }
+ cmdString.sprintf(cmds[cmd].fmt, strArg.latin1(), intArg);
+ }
+ else
+ {
+ cmdString.sprintf(cmds[cmd].fmt, intArg, strArg.latin1());
+ }
+ return cmdString;
+}
+
+QString GdbDriver::makeCmdString(DbgCommand cmd, QString strArg1, QString strArg2)
+{
+ assert(cmd >= 0 && cmd < NUM_CMDS);
+ assert(cmds[cmd].argsNeeded == GdbCmdInfo::argString2);
+
+ normalizeStringArg(strArg1);
+ normalizeStringArg(strArg2);
+
+ QString cmdString;
+ cmdString.sprintf(cmds[cmd].fmt, strArg1.latin1(), strArg2.latin1());
+ return cmdString;
+}
+
+QString GdbDriver::makeCmdString(DbgCommand cmd, int intArg1, int intArg2)
+{
+ assert(cmd >= 0 && cmd < NUM_CMDS);
+ assert(cmds[cmd].argsNeeded == GdbCmdInfo::argNum2);
+
+ QString cmdString;
+ cmdString.sprintf(cmds[cmd].fmt, intArg1, intArg2);
+ return cmdString;
+}
+
+CmdQueueItem* GdbDriver::executeCmd(DbgCommand cmd, bool clearLow)
+{
+ assert(cmd >= 0 && cmd < NUM_CMDS);
+ assert(cmds[cmd].argsNeeded == GdbCmdInfo::argNone);
+
+ if (cmd == DCrun) {
+ m_haveCoreFile = false;
+ }
+
+ return executeCmdString(cmd, cmds[cmd].fmt, clearLow);
+}
+
+CmdQueueItem* GdbDriver::executeCmd(DbgCommand cmd, QString strArg,
+ bool clearLow)
+{
+ return executeCmdString(cmd, makeCmdString(cmd, strArg), clearLow);
+}
+
+CmdQueueItem* GdbDriver::executeCmd(DbgCommand cmd, int intArg,
+ bool clearLow)
+{
+
+ return executeCmdString(cmd, makeCmdString(cmd, intArg), clearLow);
+}
+
+CmdQueueItem* GdbDriver::executeCmd(DbgCommand cmd, QString strArg, int intArg,
+ bool clearLow)
+{
+ return executeCmdString(cmd, makeCmdString(cmd, strArg, intArg), clearLow);
+}
+
+CmdQueueItem* GdbDriver::executeCmd(DbgCommand cmd, QString strArg1, QString strArg2,
+ bool clearLow)
+{
+ return executeCmdString(cmd, makeCmdString(cmd, strArg1, strArg2), clearLow);
+}
+
+CmdQueueItem* GdbDriver::executeCmd(DbgCommand cmd, int intArg1, int intArg2,
+ bool clearLow)
+{
+ return executeCmdString(cmd, makeCmdString(cmd, intArg1, intArg2), clearLow);
+}
+
+CmdQueueItem* GdbDriver::queueCmd(DbgCommand cmd, QueueMode mode)
+{
+ return queueCmdString(cmd, cmds[cmd].fmt, mode);
+}
+
+CmdQueueItem* GdbDriver::queueCmd(DbgCommand cmd, QString strArg,
+ QueueMode mode)
+{
+ return queueCmdString(cmd, makeCmdString(cmd, strArg), mode);
+}
+
+CmdQueueItem* GdbDriver::queueCmd(DbgCommand cmd, int intArg,
+ QueueMode mode)
+{
+ return queueCmdString(cmd, makeCmdString(cmd, intArg), mode);
+}
+
+CmdQueueItem* GdbDriver::queueCmd(DbgCommand cmd, QString strArg, int intArg,
+ QueueMode mode)
+{
+ return queueCmdString(cmd, makeCmdString(cmd, strArg, intArg), mode);
+}
+
+CmdQueueItem* GdbDriver::queueCmd(DbgCommand cmd, QString strArg1, QString strArg2,
+ QueueMode mode)
+{
+ return queueCmdString(cmd, makeCmdString(cmd, strArg1, strArg2), mode);
+}
+
+void GdbDriver::terminate()
+{
+ kill(SIGTERM);
+ m_state = DSidle;
+}
+
+void GdbDriver::detachAndTerminate()
+{
+ kill(SIGINT);
+ flushCommands();
+ executeCmdString(DCinitialize, "detach\nquit\n", true);
+}
+
+void GdbDriver::interruptInferior()
+{
+ kill(SIGINT);
+ // remove accidentally queued commands
+ flushHiPriQueue();
+}
+
+static bool isErrorExpr(const char* output)
+{
+ return
+ strncmp(output, "Cannot access memory at", 23) == 0 ||
+ strncmp(output, "Attempt to dereference a generic pointer", 40) == 0 ||
+ strncmp(output, "Attempt to take contents of ", 28) == 0 ||
+ strncmp(output, "Attempt to use a type name as an expression", 43) == 0 ||
+ strncmp(output, "There is no member or method named", 34) == 0 ||
+ strncmp(output, "A parse error in expression", 27) == 0 ||
+ strncmp(output, "No symbol \"", 11) == 0 ||
+ strncmp(output, "Internal error: ", 16) == 0;
+}
+
+/**
+ * Returns true if the output is an error message. If wantErrorValue is
+ * true, a new ExprValue object is created and filled with the error message.
+ * If there are warnings, they are skipped and output points past the warnings
+ * on return (even if there \e are errors).
+ */
+static bool parseErrorMessage(const char*& output,
+ ExprValue*& variable, bool wantErrorValue)
+{
+ // skip warnings
+ while (strncmp(output, "warning:", 8) == 0)
+ {
+ const char* end = strchr(output+8, '\n');
+ if (end == 0)
+ output += strlen(output);
+ else
+ output = end+1;
+ }
+
+ if (isErrorExpr(output))
+ {
+ if (wantErrorValue) {
+ // put the error message as value in the variable
+ variable = new ExprValue(QString(), VarTree::NKplain);
+ const char* endMsg = strchr(output, '\n');
+ if (endMsg == 0)
+ endMsg = output + strlen(output);
+ variable->m_value = QString::fromLatin1(output, endMsg-output);
+ } else {
+ variable = 0;
+ }
+ return true;
+ }
+ return false;
+}
+
+#if QT_VERSION >= 300
+union Qt2QChar {
+ short s;
+ struct {
+ uchar row;
+ uchar cell;
+ } qch;
+};
+#endif
+
+void GdbDriver::setPrintQStringDataCmd(const char* cmd)
+{
+ // don't accept the command if it is empty
+ if (cmd == 0 || *cmd == '\0')
+ return;
+ assert(strlen(cmd) <= MAX_FMTLEN);
+ cmds[DCprintQStringStruct].fmt = cmd;
+}
+
+ExprValue* GdbDriver::parseQCharArray(const char* output, bool wantErrorValue, bool qt3like)
+{
+ ExprValue* variable = 0;
+
+ /*
+ * Parse off white space. gdb sometimes prints white space first if the
+ * printed array leaded to an error.
+ */
+ while (isspace(*output))
+ output++;
+
+ // special case: empty string (0 repetitions)
+ if (strncmp(output, "Invalid number 0 of repetitions", 31) == 0)
+ {
+ variable = new ExprValue(QString(), VarTree::NKplain);
+ variable->m_value = "\"\"";
+ return variable;
+ }
+
+ // check for error conditions
+ if (parseErrorMessage(output, variable, wantErrorValue))
+ return variable;
+
+ // parse the array
+
+ // find '='
+ const char* p = output;
+ p = strchr(p, '=');
+ if (p == 0) {
+ goto error;
+ }
+ // skip white space
+ do {
+ p++;
+ } while (isspace(*p));
+
+ if (*p == '{')
+ {
+ // this is the real data
+ p++; /* skip '{' */
+
+ // parse the array
+ QString result;
+ QString repeatCount;
+ enum { wasNothing, wasChar, wasRepeat } lastThing = wasNothing;
+ /*
+ * A matrix for separators between the individual "things"
+ * that are added to the string. The first index is a bool,
+ * the second index is from the enum above.
+ */
+ static const char* separator[2][3] = {
+ { "\"", 0, ", \"" }, /* normal char is added */
+ { "'", "\", '", ", '" } /* repeated char is added */
+ };
+
+ while (isdigit(*p)) {
+ // parse a number
+ char* end;
+ unsigned short value = (unsigned short) strtoul(p, &end, 0);
+ if (end == p)
+ goto error; /* huh? no valid digits */
+ // skip separator and search for a repeat count
+ p = end;
+ while (isspace(*p) || *p == ',')
+ p++;
+ bool repeats = strncmp(p, "<repeats ", 9) == 0;
+ if (repeats) {
+ const char* start = p;
+ p = strchr(p+9, '>'); /* search end and advance */
+ if (p == 0)
+ goto error;
+ p++; /* skip '>' */
+ repeatCount = QString::fromLatin1(start, p-start);
+ while (isspace(*p) || *p == ',')
+ p++;
+ }
+ // p is now at the next char (or the end)
+
+ // interpret the value as a QChar
+ // TODO: make cross-architecture compatible
+ QChar ch;
+ if (qt3like) {
+ ch = QChar(value);
+ } else {
+#if QT_VERSION < 300
+ (unsigned short&)ch = value;
+#else
+ Qt2QChar c;
+ c.s = value;
+ ch.setRow(c.qch.row);
+ ch.setCell(c.qch.cell);
+#endif
+ }
+
+ // escape a few frequently used characters
+ char escapeCode = '\0';
+ switch (ch.latin1()) {
+ case '\n': escapeCode = 'n'; break;
+ case '\r': escapeCode = 'r'; break;
+ case '\t': escapeCode = 't'; break;
+ case '\b': escapeCode = 'b'; break;
+ case '\"': escapeCode = '\"'; break;
+ case '\\': escapeCode = '\\'; break;
+ case '\0': if (value == 0) { escapeCode = '0'; } break;
+ }
+
+ // add separator
+ result += separator[repeats][lastThing];
+ // add char
+ if (escapeCode != '\0') {
+ result += '\\';
+ ch = escapeCode;
+ }
+ result += ch;
+
+ // fixup repeat count and lastThing
+ if (repeats) {
+ result += "' ";
+ result += repeatCount;
+ lastThing = wasRepeat;
+ } else {
+ lastThing = wasChar;
+ }
+ }
+ if (*p != '}')
+ goto error;
+
+ // closing quote
+ if (lastThing == wasChar)
+ result += "\"";
+
+ // assign the value
+ variable = new ExprValue(QString(), VarTree::NKplain);
+ variable->m_value = result;
+ }
+ else if (strncmp(p, "true", 4) == 0)
+ {
+ variable = new ExprValue(QString(), VarTree::NKplain);
+ variable->m_value = "QString::null";
+ }
+ else if (strncmp(p, "false", 5) == 0)
+ {
+ variable = new ExprValue(QString(), VarTree::NKplain);
+ variable->m_value = "(null)";
+ }
+ else
+ goto error;
+ return variable;
+
+error:
+ if (wantErrorValue) {
+ variable = new ExprValue(QString(), VarTree::NKplain);
+ variable->m_value = "internal parse error";
+ }
+ return variable;
+}
+
+static ExprValue* parseVar(const char*& s)
+{
+ const char* p = s;
+
+ // skip whitespace
+ while (isspace(*p))
+ p++;
+
+ QString name;
+ VarTree::NameKind kind;
+ /*
+ * Detect anonymouse struct values: The 'name =' part is missing:
+ * s = { a = 1, { b = 2 }}
+ * Note that this detection works only inside structs when the anonymous
+ * struct is not the first member:
+ * s = {{ a = 1 }, b = 2}
+ * This is misparsed (by parseNested()) because it is mistakenly
+ * interprets the second opening brace as the first element of an array
+ * of structs.
+ */
+ if (*p == '{')
+ {
+ name = i18n("<anonymous struct or union>");
+ kind = VarTree::NKanonymous;
+ }
+ else
+ {
+ if (!parseName(p, name, kind)) {
+ return 0;
+ }
+
+ // go for '='
+ while (isspace(*p))
+ p++;
+ if (*p != '=') {
+ TRACE(QString().sprintf("parse error: = not found after %s", (const char*)name));
+ return 0;
+ }
+ // skip the '=' and more whitespace
+ p++;
+ while (isspace(*p))
+ p++;
+ }
+
+ ExprValue* variable = new ExprValue(name, kind);
+
+ if (!parseValue(p, variable)) {
+ delete variable;
+ return 0;
+ }
+ s = p;
+ return variable;
+}
+
+static void skipNested(const char*& s, char opening, char closing)
+{
+ const char* p = s;
+
+ // parse a nested type
+ int nest = 1;
+ p++;
+ /*
+ * Search for next matching `closing' char, skipping nested pairs of
+ * `opening' and `closing'.
+ */
+ while (*p && nest > 0) {
+ if (*p == opening) {
+ nest++;
+ } else if (*p == closing) {
+ nest--;
+ }
+ p++;
+ }
+ if (nest != 0) {
+ TRACE(QString().sprintf("parse error: mismatching %c%c at %-20.20s", opening, closing, s));
+ }
+ s = p;
+}
+
+/**
+ * This function skips text that is delimited by nested angle bracktes, '<>'.
+ * A complication arises because the delimited text can contain the names of
+ * operator<<, operator>>, operator<, and operator>, which have to be treated
+ * specially so that they do not count towards the nesting of '<>'.
+ * This function assumes that the delimited text does not contain strings.
+ */
+static void skipNestedAngles(const char*& s)
+{
+ const char* p = s;
+
+ int nest = 1;
+ p++; // skip the initial '<'
+ while (*p && nest > 0)
+ {
+ // Below we can check for p-s >= 9 instead of 8 because
+ // *s is '<' and cannot be part of "operator".
+ if (*p == '<')
+ {
+ if (p-s >= 9 && strncmp(p-8, "operator", 8) == 0) {
+ if (p[1] == '<')
+ p++;
+ } else {
+ nest++;
+ }
+ }
+ else if (*p == '>')
+ {
+ if (p-s >= 9 && strncmp(p-8, "operator", 8) == 0) {
+ if (p[1] == '>')
+ p++;
+ } else {
+ nest--;
+ }
+ }
+ p++;
+ }
+ if (nest != 0) {
+ TRACE(QString().sprintf("parse error: mismatching <> at %-20.20s", s));
+ }
+ s = p;
+}
+
+/**
+ * Find the end of line that is not inside braces
+ */
+static void findEnd(const char*& s)
+{
+ const char* p = s;
+ while (*p && *p!='\n') {
+ while (*p && *p!='\n' && *p!='{')
+ p++;
+ if (*p=='{') {
+ p++;
+ skipNested(p, '{', '}'); p--;
+ }
+ }
+ s = p;
+}
+
+static bool isNumberish(const char ch)
+{
+ return (ch>='0' && ch<='9') || ch=='.' || ch=='x';
+}
+
+void skipString(const char*& p)
+{
+moreStrings:
+ // opening quote
+ char quote = *p++;
+ while (*p != quote) {
+ if (*p == '\\') {
+ // skip escaped character
+ // no special treatment for octal values necessary
+ p++;
+ }
+ // simply return if no more characters
+ if (*p == '\0')
+ return;
+ p++;
+ }
+ // closing quote
+ p++;
+ /*
+ * Strings can consist of several parts, some of which contain repeated
+ * characters.
+ */
+ if (quote == '\'') {
+ // look ahaead for <repeats 123 times>
+ const char* q = p+1;
+ while (isspace(*q))
+ q++;
+ if (strncmp(q, "<repeats ", 9) == 0) {
+ p = q+9;
+ while (*p != '\0' && *p != '>')
+ p++;
+ if (*p != '\0') {
+ p++; /* skip the '>' */
+ }
+ }
+ }
+ // is the string continued?
+ if (*p == ',') {
+ // look ahead for another quote
+ const char* q = p+1;
+ while (isspace(*q))
+ q++;
+ if (*q == '"' || *q == '\'') {
+ // yes!
+ p = q;
+ goto moreStrings;
+ }
+ }
+ /* very long strings are followed by `...' */
+ if (*p == '.' && p[1] == '.' && p[2] == '.') {
+ p += 3;
+ }
+}
+
+static void skipNestedWithString(const char*& s, char opening, char closing)
+{
+ const char* p = s;
+
+ // parse a nested expression
+ int nest = 1;
+ p++;
+ /*
+ * Search for next matching `closing' char, skipping nested pairs of
+ * `opening' and `closing' as well as strings.
+ */
+ while (*p && nest > 0) {
+ if (*p == opening) {
+ nest++;
+ } else if (*p == closing) {
+ nest--;
+ } else if (*p == '\'' || *p == '\"') {
+ skipString(p);
+ continue;
+ }
+ p++;
+ }
+ if (nest > 0) {
+ TRACE(QString().sprintf("parse error: mismatching %c%c at %-20.20s", opening, closing, s));
+ }
+ s = p;
+}
+
+inline void skipName(const char*& p)
+{
+ // allow : (for enumeration values) and $ and . (for _vtbl.)
+ while (isalnum(*p) || *p == '_' || *p == ':' || *p == '$' || *p == '.')
+ p++;
+}
+
+static bool parseName(const char*& s, QString& name, VarTree::NameKind& kind)
+{
+ kind = VarTree::NKplain;
+
+ const char* p = s;
+ // examples of names:
+ // name
+ // <Object>
+ // <string<a,b<c>,7> >
+
+ if (*p == '<') {
+ skipNestedAngles(p);
+ name = QString::fromLatin1(s, p - s);
+ kind = VarTree::NKtype;
+ }
+ else
+ {
+ // name, which might be "static"; allow dot for "_vtbl."
+ skipName(p);
+ if (p == s) {
+ TRACE(QString().sprintf("parse error: not a name %-20.20s", s));
+ return false;
+ }
+ int len = p - s;
+ if (len == 6 && strncmp(s, "static", 6) == 0) {
+ kind = VarTree::NKstatic;
+
+ // its a static variable, name comes now
+ while (isspace(*p))
+ p++;
+ s = p;
+ skipName(p);
+ if (p == s) {
+ TRACE(QString().sprintf("parse error: not a name after static %-20.20s", s));
+ return false;
+ }
+ len = p - s;
+ }
+ name = QString::fromLatin1(s, len);
+ }
+ // return the new position
+ s = p;
+ return true;
+}
+
+static bool parseValue(const char*& s, ExprValue* variable)
+{
+ variable->m_value = "";
+
+repeat:
+ if (*s == '{') {
+ // Sometimes we find the following output:
+ // {<text variable, no debug info>} 0x40012000 <access>
+ // {<data variable, no debug info>}
+ // {<variable (not text or data), no debug info>}
+ if (strncmp(s, "{<text variable, ", 17) == 0 ||
+ strncmp(s, "{<data variable, ", 17) == 0 ||
+ strncmp(s, "{<variable (not text or data), ", 31) == 0)
+ {
+ const char* start = s;
+ skipNested(s, '{', '}');
+ variable->m_value = QString::fromLatin1(start, s-start);
+ variable->m_value += ' '; // add only a single space
+ while (isspace(*s))
+ s++;
+ goto repeat;
+ }
+ else
+ {
+ s++;
+ if (!parseNested(s, variable)) {
+ return false;
+ }
+ // must be the closing brace
+ if (*s != '}') {
+ TRACE("parse error: missing } of " + variable->m_name);
+ return false;
+ }
+ s++;
+ // final white space
+ while (isspace(*s))
+ s++;
+ }
+ } else {
+ // examples of leaf values (cannot be the empty string):
+ // 123
+ // -123
+ // 23.575e+37
+ // 0x32a45
+ // @0x012ab4
+ // (DwContentType&) @0x8123456: {...}
+ // 0x32a45 "text"
+ // 10 '\n'
+ // <optimized out>
+ // 0x823abc <Array<int> virtual table>
+ // (void (*)()) 0x8048480 <f(E *, char)>
+ // (E *) 0xbffff450
+ // red
+ // &parseP (HTMLClueV *, char *)
+ // Variable "x" is not available.
+ // The value of variable 'x' is distributed...
+ // -nan(0xfffff081defa0)
+
+ const char*p = s;
+
+ // check for type
+ QString type;
+ if (*p == '(') {
+ skipNested(p, '(', ')');
+
+ while (isspace(*p))
+ p++;
+ variable->m_value = QString::fromLatin1(s, p - s);
+ }
+
+ bool reference = false;
+ if (*p == '@') {
+ // skip reference marker
+ p++;
+ reference = true;
+ }
+ const char* start = p;
+ if (*p == '-')
+ p++;
+
+ // some values consist of more than one token
+ bool checkMultiPart = false;
+
+ if (p[0] == '0' && p[1] == 'x') {
+ // parse hex number
+ p += 2;
+ while (isxdigit(*p))
+ p++;
+
+ /*
+ * Assume this is a pointer, but only if it's not a reference, since
+ * references can't be expanded.
+ */
+ if (!reference) {
+ variable->m_varKind = VarTree::VKpointer;
+ } else {
+ /*
+ * References are followed by a colon, in which case we'll
+ * find the value following the reference address.
+ */
+ if (*p == ':') {
+ p++;
+ } else {
+ // Paranoia. (Can this happen, i.e. reference not followed by ':'?)
+ reference = false;
+ }
+ }
+ checkMultiPart = true;
+ } else if (isdigit(*p)) {
+ // parse decimal number, possibly a float
+ while (isdigit(*p))
+ p++;
+ if (*p == '.') { /* TODO: obey i18n? */
+ // In long arrays an integer may be followed by '...'.
+ // We test for this situation and don't gobble the '...'.
+ if (p[1] != '.' || p[0] != '.') {
+ // fractional part
+ p++;
+ while (isdigit(*p))
+ p++;
+ }
+ }
+ if (*p == 'e' || *p == 'E') {
+ p++;
+ // exponent
+ if (*p == '-' || *p == '+')
+ p++;
+ while (isdigit(*p))
+ p++;
+ }
+
+ // for char variables there is the char, eg. 10 '\n'
+ checkMultiPart = true;
+ } else if (*p == '<') {
+ // e.g. <optimized out>
+ skipNestedAngles(p);
+ } else if (*p == '"' || *p == '\'') {
+ // character may have multipart: '\000' <repeats 11 times>
+ checkMultiPart = *p == '\'';
+ // found a string
+ skipString(p);
+ } else if (*p == '&') {
+ // function pointer
+ p++;
+ skipName(p);
+ while (isspace(*p)) {
+ p++;
+ }
+ if (*p == '(') {
+ skipNested(p, '(', ')');
+ }
+ } else if (strncmp(p, "Variable \"", 10) == 0) {
+ // Variable "x" is not available.
+ p += 10; // skip to "
+ skipName(p);
+ if (strncmp(p, "\" is not available.", 19) == 0) {
+ p += 19;
+ }
+ } else if (strncmp(p, "The value of variable '", 23) == 0) {
+ p += 23;
+ skipName(p);
+ const char* e = strchr(p, '.');
+ if (e == 0) {
+ p += strlen(p);
+ } else {
+ p = e+1;
+ }
+ } else {
+ // must be an enumeration value
+ skipName(p);
+ // hmm, not necessarily: nan (floating point Not a Number)
+ // is followed by a number in ()
+ if (*p == '(')
+ skipNested(p, '(', ')');
+ }
+ variable->m_value += QString::fromLatin1(start, p - start);
+
+ // remove line breaks from the value; this is ok since
+ // string values never contain a literal line break
+ variable->m_value.replace('\n', ' ');
+
+ if (checkMultiPart) {
+ // white space
+ while (isspace(*p))
+ p++;
+ // may be followed by a string or <...>
+ start = p;
+
+ if (*p == '"' || *p == '\'') {
+ skipString(p);
+ } else if (*p == '<') {
+ // if this value is part of an array, it might be followed
+ // by <repeats 15 times>, which we don't skip here
+ if (strncmp(p, "<repeats ", 9) != 0)
+ skipNestedAngles(p);
+ }
+ if (p != start) {
+ // there is always a blank before the string,
+ // which we will include in the final string value
+ variable->m_value += QString::fromLatin1(start-1, (p - start)+1);
+ // if this was a pointer, reset that flag since we
+ // now got the value
+ variable->m_varKind = VarTree::VKsimple;
+ }
+ }
+
+ if (variable->m_value.length() == 0) {
+ TRACE("parse error: no value for " + variable->m_name);
+ return false;
+ }
+
+ // final white space
+ while (isspace(*p))
+ p++;
+ s = p;
+
+ /*
+ * If this was a reference, the value follows. It might even be a
+ * composite variable!
+ */
+ if (reference) {
+ goto repeat;
+ }
+ }
+
+ return true;
+}
+
+static bool parseNested(const char*& s, ExprValue* variable)
+{
+ // could be a structure or an array
+ while (isspace(*s))
+ s++;
+
+ const char* p = s;
+ bool isStruct = false;
+ /*
+ * If there is a name followed by an = or an < -- which starts a type
+ * name -- or "static", it is a structure
+ */
+ if (*p == '<' || *p == '}') {
+ isStruct = true;
+ } else if (strncmp(p, "static ", 7) == 0) {
+ isStruct = true;
+ } else if (isalpha(*p) || *p == '_' || *p == '$') {
+ // look ahead for a comma after the name
+ skipName(p);
+ while (isspace(*p))
+ p++;
+ if (*p == '=') {
+ isStruct = true;
+ }
+ p = s; /* rescan the name */
+ }
+ if (isStruct) {
+ if (!parseVarSeq(p, variable)) {
+ return false;
+ }
+ variable->m_varKind = VarTree::VKstruct;
+ } else {
+ if (!parseValueSeq(p, variable)) {
+ return false;
+ }
+ variable->m_varKind = VarTree::VKarray;
+ }
+ s = p;
+ return true;
+}
+
+static bool parseVarSeq(const char*& s, ExprValue* variable)
+{
+ // parse a comma-separated sequence of variables
+ ExprValue* var = variable; /* var != 0 to indicate success if empty seq */
+ for (;;) {
+ if (*s == '}')
+ break;
+ if (strncmp(s, "<No data fields>}", 17) == 0)
+ {
+ // no member variables, so break out immediately
+ s += 16; /* go to the closing brace */
+ break;
+ }
+ var = parseVar(s);
+ if (var == 0)
+ break; /* syntax error */
+ variable->appendChild(var);
+ if (*s != ',')
+ break;
+ // skip the comma and whitespace
+ s++;
+ while (isspace(*s))
+ s++;
+ }
+ return var != 0;
+}
+
+static bool parseValueSeq(const char*& s, ExprValue* variable)
+{
+ // parse a comma-separated sequence of variables
+ int index = 0;
+ bool good;
+ for (;;) {
+ QString name;
+ name.sprintf("[%d]", index);
+ ExprValue* var = new ExprValue(name, VarTree::NKplain);
+ good = parseValue(s, var);
+ if (!good) {
+ delete var;
+ return false;
+ }
+ // a value may be followed by "<repeats 45 times>"
+ if (strncmp(s, "<repeats ", 9) == 0) {
+ s += 9;
+ char* end;
+ int l = strtol(s, &end, 10);
+ if (end == s || strncmp(end, " times>", 7) != 0) {
+ // should not happen
+ delete var;
+ return false;
+ }
+ TRACE(QString().sprintf("found <repeats %d times> in array", l));
+ // replace name and advance index
+ name.sprintf("[%d .. %d]", index, index+l-1);
+ var->m_name = name;
+ index += l;
+ // skip " times>" and space
+ s = end+7;
+ // possible final space
+ while (isspace(*s))
+ s++;
+ } else {
+ index++;
+ }
+ variable->appendChild(var);
+ // long arrays may be terminated by '...'
+ if (strncmp(s, "...", 3) == 0) {
+ s += 3;
+ ExprValue* var = new ExprValue("...", VarTree::NKplain);
+ var->m_value = i18n("<additional entries of the array suppressed>");
+ variable->appendChild(var);
+ break;
+ }
+ if (*s != ',') {
+ break;
+ }
+ // skip the comma and whitespace
+ s++;
+ while (isspace(*s))
+ s++;
+ // sometimes there is a closing brace after a comma
+// if (*s == '}')
+// break;
+ }
+ return true;
+}
+
+/**
+ * Parses a stack frame.
+ */
+static void parseFrameInfo(const char*& s, QString& func,
+ QString& file, int& lineNo, DbgAddr& address)
+{
+ const char* p = s;
+
+ // next may be a hexadecimal address
+ if (*p == '0') {
+ const char* start = p;
+ p++;
+ if (*p == 'x')
+ p++;
+ while (isxdigit(*p))
+ p++;
+ address = QString::fromLatin1(start, p-start);
+ if (strncmp(p, " in ", 4) == 0)
+ p += 4;
+ } else {
+ address = DbgAddr();
+ }
+ const char* start = p;
+ // check for special signal handler frame
+ if (strncmp(p, "<signal handler called>", 23) == 0) {
+ func = QString::fromLatin1(start, 23);
+ file = QString();
+ lineNo = -1;
+ s = p+23;
+ if (*s == '\n')
+ s++;
+ return;
+ }
+
+ /*
+ * Skip the function name. It is terminated by a left parenthesis
+ * which does not delimit "(anonymous namespace)" and which is
+ * outside the angle brackets <> of template parameter lists
+ * and is preceded by a space.
+ */
+ while (*p != '\0')
+ {
+ if (*p == '<') {
+ // check for operator<< and operator<
+ if (p-start >= 8 && strncmp(p-8, "operator", 8) == 0)
+ {
+ p++;
+ if (*p == '<')
+ p++;
+ }
+ else
+ {
+ // skip template parameter list
+ skipNestedAngles(p);
+ }
+ } else if (*p == '(') {
+ // this skips "(anonymous namespace)" as well as the formal
+ // parameter list of the containing function if this is a member
+ // of a nested class
+ skipNestedWithString(p, '(', ')');
+ } else if (*p == ' ') {
+ ++p;
+ if (*p == '(')
+ break; // parameter list found
+ } else {
+ p++;
+ }
+ }
+
+ if (*p == '\0') {
+ func = start;
+ file = QString();
+ lineNo = -1;
+ s = p;
+ return;
+ }
+ /*
+ * Skip parameters. But notice that for complicated conversion
+ * functions (eg. "operator int(**)()()", ie. convert to pointer to
+ * pointer to function) as well as operator()(...) we have to skip
+ * additional pairs of parentheses. Furthermore, recent gdbs write the
+ * demangled name followed by the arguments in a pair of parentheses,
+ * where the demangled name can end in "const".
+ */
+ do {
+ skipNestedWithString(p, '(', ')');
+ while (isspace(*p))
+ p++;
+ // skip "const"
+ if (strncmp(p, "const", 5) == 0) {
+ p += 5;
+ while (isspace(*p))
+ p++;
+ }
+ } while (*p == '(');
+
+ // check for file position
+ if (strncmp(p, "at ", 3) == 0) {
+ p += 3;
+ const char* fileStart = p;
+ // go for the end of the line
+ while (*p != '\0' && *p != '\n')
+ p++;
+ // search back for colon
+ const char* colon = p;
+ do {
+ --colon;
+ } while (*colon != ':');
+ file = QString::fromLatin1(fileStart, colon-fileStart);
+ lineNo = atoi(colon+1)-1;
+ // skip new-line
+ if (*p != '\0')
+ p++;
+ } else {
+ // check for "from shared lib"
+ if (strncmp(p, "from ", 5) == 0) {
+ p += 5;
+ // go for the end of the line
+ while (*p != '\0' && *p != '\n')
+ p++;
+ // skip new-line
+ if (*p != '\0')
+ p++;
+ }
+ file = "";
+ lineNo = -1;
+ }
+ // construct the function name (including file info)
+ if (*p == '\0') {
+ func = start;
+ } else {
+ func = QString::fromLatin1(start, p-start-1); /* don't include \n */
+ }
+ s = p;
+
+ /*
+ * Replace \n (and whitespace around it) in func by a blank. We cannot
+ * use QString::simplifyWhiteSpace() for this because this would also
+ * simplify space that belongs to a string arguments that gdb sometimes
+ * prints in the argument lists of the function.
+ */
+ ASSERT(!isspace(func[0].latin1())); /* there must be non-white before first \n */
+ int nl = 0;
+ while ((nl = func.find('\n', nl)) >= 0) {
+ // search back to the beginning of the whitespace
+ int startWhite = nl;
+ do {
+ --startWhite;
+ } while (isspace(func[startWhite].latin1()));
+ startWhite++;
+ // search forward to the end of the whitespace
+ do {
+ nl++;
+ } while (isspace(func[nl].latin1()));
+ // replace
+ func.replace(startWhite, nl-startWhite, " ");
+ /* continue searching for more \n's at this place: */
+ nl = startWhite+1;
+ }
+}
+
+
+/**
+ * Parses a stack frame including its frame number
+ */
+static bool parseFrame(const char*& s, int& frameNo, QString& func,
+ QString& file, int& lineNo, DbgAddr& address)
+{
+ // Example:
+ // #1 0x8048881 in Dl::Dl (this=0xbffff418, r=3214) at testfile.cpp:72
+ // Breakpoint 3, Cl::f(int) const (this=0xbffff3c0, x=17) at testfile.cpp:155
+
+ // must start with a hash mark followed by number
+ // or with "Breakpoint " followed by number and comma
+ if (s[0] == '#') {
+ if (!isdigit(s[1]))
+ return false;
+ s++; /* skip the hash mark */
+ } else if (strncmp(s, "Breakpoint ", 11) == 0) {
+ if (!isdigit(s[11]))
+ return false;
+ s += 11; /* skip "Breakpoint" */
+ } else
+ return false;
+
+ // frame number
+ frameNo = atoi(s);
+ while (isdigit(*s))
+ s++;
+ // space and comma
+ while (isspace(*s) || *s == ',')
+ s++;
+ parseFrameInfo(s, func, file, lineNo, address);
+ return true;
+}
+
+void GdbDriver::parseBackTrace(const char* output, std::list<StackFrame>& stack)
+{
+ QString func, file;
+ int lineNo, frameNo;
+ DbgAddr address;
+
+ while (::parseFrame(output, frameNo, func, file, lineNo, address)) {
+ stack.push_back(StackFrame());
+ StackFrame* frm = &stack.back();
+ frm->frameNo = frameNo;
+ frm->fileName = file;
+ frm->lineNo = lineNo;
+ frm->address = address;
+ frm->var = new ExprValue(func, VarTree::NKplain);
+ }
+}
+
+bool GdbDriver::parseFrameChange(const char* output, int& frameNo,
+ QString& file, int& lineNo, DbgAddr& address)
+{
+ QString func;
+ return ::parseFrame(output, frameNo, func, file, lineNo, address);
+}
+
+
+bool GdbDriver::parseBreakList(const char* output, std::list<Breakpoint>& brks)
+{
+ // skip first line, which is the headline
+ const char* p = strchr(output, '\n');
+ if (p == 0)
+ return false;
+ p++;
+ if (*p == '\0')
+ return false;
+
+ // split up a line
+ const char* end;
+ char* dummy;
+ while (*p != '\0') {
+ Breakpoint bp;
+ // get Num
+ bp.id = strtol(p, &dummy, 10); /* don't care about overflows */
+ p = dummy;
+ // get Type
+ while (isspace(*p))
+ p++;
+ if (strncmp(p, "breakpoint", 10) == 0) {
+ p += 10;
+ } else if (strncmp(p, "hw watchpoint", 13) == 0) {
+ bp.type = Breakpoint::watchpoint;
+ p += 13;
+ } else if (strncmp(p, "watchpoint", 10) == 0) {
+ bp.type = Breakpoint::watchpoint;
+ p += 10;
+ }
+ while (isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ // get Disp
+ bp.temporary = *p++ == 'd';
+ while (*p != '\0' && !isspace(*p)) /* "keep" or "del" */
+ p++;
+ while (isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ // get Enb
+ bp.enabled = *p++ == 'y';
+ while (*p != '\0' && !isspace(*p)) /* "y" or "n" */
+ p++;
+ while (isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ // the address, if present
+ if (bp.type == Breakpoint::breakpoint &&
+ strncmp(p, "0x", 2) == 0)
+ {
+ const char* start = p;
+ while (*p != '\0' && !isspace(*p))
+ p++;
+ bp.address = QString::fromLatin1(start, p-start);
+ while (isspace(*p) && *p != '\n')
+ p++;
+ if (*p == '\0')
+ break;
+ }
+ // remainder is location, hit and ignore count, condition
+ end = strchr(p, '\n');
+ if (end == 0) {
+ bp.location = p;
+ p += bp.location.length();
+ } else {
+ bp.location = QString::fromLatin1(p, end-p).stripWhiteSpace();
+ p = end+1; /* skip over \n */
+ }
+
+ // may be continued in next line
+ while (isspace(*p)) { /* p points to beginning of line */
+ // skip white space at beginning of line
+ while (isspace(*p))
+ p++;
+
+ // seek end of line
+ end = strchr(p, '\n');
+ if (end == 0)
+ end = p+strlen(p);
+
+ if (strncmp(p, "breakpoint already hit", 22) == 0) {
+ // extract the hit count
+ p += 22;
+ bp.hitCount = strtol(p, &dummy, 10);
+ TRACE(QString("hit count %1").arg(bp.hitCount));
+ } else if (strncmp(p, "stop only if ", 13) == 0) {
+ // extract condition
+ p += 13;
+ bp.condition = QString::fromLatin1(p, end-p).stripWhiteSpace();
+ TRACE("condition: "+bp.condition);
+ } else if (strncmp(p, "ignore next ", 12) == 0) {
+ // extract ignore count
+ p += 12;
+ bp.ignoreCount = strtol(p, &dummy, 10);
+ TRACE(QString("ignore count %1").arg(bp.ignoreCount));
+ } else {
+ // indeed a continuation
+ bp.location += " " + QString::fromLatin1(p, end-p).stripWhiteSpace();
+ }
+ p = end;
+ if (*p != '\0')
+ p++; /* skip '\n' */
+ }
+ brks.push_back(bp);
+ }
+ return true;
+}
+
+std::list<ThreadInfo> GdbDriver::parseThreadList(const char* output)
+{
+ std::list<ThreadInfo> threads;
+ if (strcmp(output, "\n") == 0 || strncmp(output, "No stack.", 9) == 0) {
+ // no threads
+ return threads;
+ }
+
+ const char* p = output;
+ while (*p != '\0') {
+ ThreadInfo thr;
+ // seach look for thread id, watching out for the focus indicator
+ thr.hasFocus = false;
+ while (isspace(*p)) /* may be \n from prev line: see "No stack" below */
+ p++;
+ if (*p == '*') {
+ thr.hasFocus = true;
+ p++;
+ // there follows only whitespace
+ }
+ const char* end;
+ char *temp_end = NULL; /* we need a non-const 'end' for strtol to use...*/
+ thr.id = strtol(p, &temp_end, 10);
+ end = temp_end;
+ if (p == end) {
+ // syntax error: no number found; bail out
+ return threads;
+ }
+ p = end;
+
+ // skip space
+ while (isspace(*p))
+ p++;
+
+ /*
+ * Now follows the thread's SYSTAG. It is terminated by two blanks.
+ */
+ end = strstr(p, " ");
+ if (end == 0) {
+ // syntax error; bail out
+ return threads;
+ }
+ thr.threadName = QString::fromLatin1(p, end-p);
+ p = end+2;
+
+ /*
+ * Now follows a standard stack frame. Sometimes, however, gdb
+ * catches a thread at an instant where it doesn't have a stack.
+ */
+ if (strncmp(p, "[No stack.]", 11) != 0) {
+ ::parseFrameInfo(p, thr.function, thr.fileName, thr.lineNo, thr.address);
+ } else {
+ thr.function = "[No stack]";
+ thr.lineNo = -1;
+ p += 11; /* \n is skipped above */
+ }
+
+ threads.push_back(thr);
+ }
+ return threads;
+}
+
+static bool parseNewBreakpoint(const char* o, int& id,
+ QString& file, int& lineNo, QString& address);
+static bool parseNewWatchpoint(const char* o, int& id,
+ QString& expr);
+
+bool GdbDriver::parseBreakpoint(const char* output, int& id,
+ QString& file, int& lineNo, QString& address)
+{
+ const char* o = output;
+ // skip lines of that begin with "(Cannot find"
+ while (strncmp(o, "(Cannot find", 12) == 0) {
+ o = strchr(o, '\n');
+ if (o == 0)
+ return false;
+ o++; /* skip newline */
+ }
+
+ if (strncmp(o, "Breakpoint ", 11) == 0) {
+ output += 11; /* skip "Breakpoint " */
+ return ::parseNewBreakpoint(output, id, file, lineNo, address);
+ } else if (strncmp(o, "Hardware watchpoint ", 20) == 0) {
+ output += 20;
+ return ::parseNewWatchpoint(output, id, address);
+ } else if (strncmp(o, "Watchpoint ", 11) == 0) {
+ output += 11;
+ return ::parseNewWatchpoint(output, id, address);
+ }
+ return false;
+}
+
+static bool parseNewBreakpoint(const char* o, int& id,
+ QString& file, int& lineNo, QString& address)
+{
+ // breakpoint id
+ char* p;
+ id = strtoul(o, &p, 10);
+ if (p == o)
+ return false;
+
+ // check for the address
+ if (strncmp(p, " at 0x", 6) == 0) {
+ char* start = p+4; /* skip " at ", but not 0x */
+ p += 6;
+ while (isxdigit(*p))
+ ++p;
+ address = QString::fromLatin1(start, p-start);
+ }
+
+ // file name
+ char* fileStart = strstr(p, "file ");
+ if (fileStart == 0)
+ return !address.isEmpty(); /* parse error only if there's no address */
+ fileStart += 5;
+
+ // line number
+ char* numStart = strstr(fileStart, ", line ");
+ QString fileName = QString::fromLatin1(fileStart, numStart-fileStart);
+ numStart += 7;
+ int line = strtoul(numStart, &p, 10);
+ if (numStart == p)
+ return false;
+
+ file = fileName;
+ lineNo = line-1; /* zero-based! */
+ return true;
+}
+
+static bool parseNewWatchpoint(const char* o, int& id,
+ QString& expr)
+{
+ // watchpoint id
+ char* p;
+ id = strtoul(o, &p, 10);
+ if (p == o)
+ return false;
+
+ if (strncmp(p, ": ", 2) != 0)
+ return false;
+ p += 2;
+
+ // all the rest on the line is the expression
+ expr = QString::fromLatin1(p, strlen(p)).stripWhiteSpace();
+ return true;
+}
+
+void GdbDriver::parseLocals(const char* output, std::list<ExprValue*>& newVars)
+{
+ // check for possible error conditions
+ if (strncmp(output, "No symbol table", 15) == 0)
+ {
+ return;
+ }
+
+ while (*output != '\0') {
+ while (isspace(*output))
+ output++;
+ if (*output == '\0')
+ break;
+ // skip occurrences of "No locals" and "No args"
+ if (strncmp(output, "No locals", 9) == 0 ||
+ strncmp(output, "No arguments", 12) == 0)
+ {
+ output = strchr(output, '\n');
+ if (output == 0) {
+ break;
+ }
+ continue;
+ }
+
+ ExprValue* variable = parseVar(output);
+ if (variable == 0) {
+ break;
+ }
+ // do not add duplicates
+ for (std::list<ExprValue*>::iterator o = newVars.begin(); o != newVars.end(); ++o) {
+ if ((*o)->m_name == variable->m_name) {
+ delete variable;
+ goto skipDuplicate;
+ }
+ }
+ newVars.push_back(variable);
+ skipDuplicate:;
+ }
+}
+
+ExprValue* GdbDriver::parsePrintExpr(const char* output, bool wantErrorValue)
+{
+ ExprValue* var = 0;
+ // check for error conditions
+ if (!parseErrorMessage(output, var, wantErrorValue))
+ {
+ // parse the variable
+ var = parseVar(output);
+ }
+ return var;
+}
+
+bool GdbDriver::parseChangeWD(const char* output, QString& message)
+{
+ bool isGood = false;
+ message = QString(output).simplifyWhiteSpace();
+ if (message.isEmpty()) {
+ message = i18n("New working directory: ") + m_programWD;
+ isGood = true;
+ }
+ return isGood;
+}
+
+bool GdbDriver::parseChangeExecutable(const char* output, QString& message)
+{
+ message = output;
+
+ m_haveCoreFile = false;
+
+ /*
+ * Lines starting with the following do not indicate errors:
+ * Using host libthread_db
+ * (no debugging symbols found)
+ */
+ while (strncmp(output, "Using host libthread_db", 23) == 0 ||
+ strncmp(output, "(no debugging symbols found)", 28) == 0)
+ {
+ // this line is good, go to the next one
+ const char* end = strchr(output, '\n');
+ if (end == 0)
+ output += strlen(output);
+ else
+ output = end+1;
+ }
+
+ /*
+ * If we've parsed all lines, there was no error.
+ */
+ return output[0] == '\0';
+}
+
+bool GdbDriver::parseCoreFile(const char* output)
+{
+ // if command succeeded, gdb emits a line starting with "#0 "
+ m_haveCoreFile = strstr(output, "\n#0 ") != 0;
+ return m_haveCoreFile;
+}
+
+uint GdbDriver::parseProgramStopped(const char* output, QString& message)
+{
+ // optionally: "program changed, rereading symbols",
+ // followed by:
+ // "Program exited normally"
+ // "Program terminated with wignal SIGSEGV"
+ // "Program received signal SIGINT" or other signal
+ // "Breakpoint..."
+
+ // go through the output, line by line, checking what we have
+ const char* start = output - 1;
+ uint flags = SFprogramActive;
+ message = QString();
+ do {
+ start++; /* skip '\n' */
+
+ if (strncmp(start, "Program ", 8) == 0 ||
+ strncmp(start, "ptrace: ", 8) == 0) {
+ /*
+ * When we receive a signal, the program remains active.
+ *
+ * Special: If we "stopped" in a corefile, the string "Program
+ * terminated with signal"... is displayed. (Normally, we see
+ * "Program received signal"... when a signal happens.)
+ */
+ if (strncmp(start, "Program exited", 14) == 0 ||
+ (strncmp(start, "Program terminated", 18) == 0 && !m_haveCoreFile) ||
+ strncmp(start, "ptrace: ", 8) == 0)
+ {
+ flags &= ~SFprogramActive;
+ }
+
+ // set message
+ const char* endOfMessage = strchr(start, '\n');
+ if (endOfMessage == 0)
+ endOfMessage = start + strlen(start);
+ message = QString::fromLatin1(start, endOfMessage-start);
+ } else if (strncmp(start, "Breakpoint ", 11) == 0) {
+ /*
+ * We stopped at a (permanent) breakpoint (gdb doesn't tell us
+ * that it stopped at a temporary breakpoint).
+ */
+ flags |= SFrefreshBreak;
+ } else if (strstr(start, "re-reading symbols.") != 0) {
+ flags |= SFrefreshSource;
+ }
+
+ // next line, please
+ start = strchr(start, '\n');
+ } while (start != 0);
+
+ /*
+ * Gdb only notices when new threads have appeared, but not when a
+ * thread finishes. So we always have to assume that the list of
+ * threads has changed.
+ */
+ flags |= SFrefreshThreads;
+
+ return flags;
+}
+
+QStringList GdbDriver::parseSharedLibs(const char* output)
+{
+ QStringList shlibs;
+ if (strncmp(output, "No shared libraries loaded", 26) == 0)
+ return shlibs;
+
+ // parse the table of shared libraries
+
+ // strip off head line
+ output = strchr(output, '\n');
+ if (output == 0)
+ return shlibs;
+ output++; /* skip '\n' */
+ QString shlibName;
+ while (*output != '\0') {
+ // format of a line is
+ // 0x404c5000 0x40580d90 Yes /lib/libc.so.5
+ // 3 blocks of non-space followed by space
+ for (int i = 0; *output != '\0' && i < 3; i++) {
+ while (*output != '\0' && !isspace(*output)) { /* non-space */
+ output++;
+ }
+ while (isspace(*output)) { /* space */
+ output++;
+ }
+ }
+ if (*output == '\0')
+ return shlibs;
+ const char* start = output;
+ output = strchr(output, '\n');
+ if (output == 0)
+ output = start + strlen(start);
+ shlibName = QString::fromLatin1(start, output-start);
+ if (*output != '\0')
+ output++;
+ shlibs.append(shlibName);
+ TRACE("found shared lib " + shlibName);
+ }
+ return shlibs;
+}
+
+bool GdbDriver::parseFindType(const char* output, QString& type)
+{
+ if (strncmp(output, "type = ", 7) != 0)
+ return false;
+
+ /*
+ * Everything else is the type. We strip off any leading "const" and any
+ * trailing "&" on the grounds that neither affects the decoding of the
+ * object. We also strip off all white-space from the type.
+ */
+ output += 7;
+ if (strncmp(output, "const ", 6) == 0)
+ output += 6;
+ type = output;
+ type.replace(QRegExp("\\s+"), "");
+ if (type.endsWith("&"))
+ type.truncate(type.length() - 1);
+ return true;
+}
+
+std::list<RegisterInfo> GdbDriver::parseRegisters(const char* output)
+{
+ std::list<RegisterInfo> regs;
+ if (strncmp(output, "The program has no registers now", 32) == 0) {
+ return regs;
+ }
+
+ QString value;
+
+ // parse register values
+ while (*output != '\0')
+ {
+ RegisterInfo reg;
+ // skip space at the start of the line
+ while (isspace(*output))
+ output++;
+
+ // register name
+ const char* start = output;
+ while (*output != '\0' && !isspace(*output))
+ output++;
+ if (*output == '\0')
+ break;
+ reg.regName = QString::fromLatin1(start, output-start);
+
+ // skip space
+ while (isspace(*output))
+ output++;
+
+ /*
+ * If we find a brace now, this is a vector register. We look for
+ * the closing brace and treat the result as cooked value.
+ */
+ if (*output == '{')
+ {
+ start = output;
+ skipNested(output, '{', '}');
+ value = QString::fromLatin1(start, output-start).simplifyWhiteSpace();
+ // skip space, but not the end of line
+ while (isspace(*output) && *output != '\n')
+ output++;
+ // get rid of the braces at the begining and the end
+ value.remove(0, 1);
+ if (value[value.length()-1] == '}') {
+ value = value.left(value.length()-1);
+ }
+ // gdb 5.3 doesn't print a separate set of raw values
+ if (*output == '{') {
+ // another set of vector follows
+ // what we have so far is the raw value
+ reg.rawValue = value;
+
+ start = output;
+ skipNested(output, '{', '}');
+ value = QString::fromLatin1(start, output-start).simplifyWhiteSpace();
+ } else {
+ // for gdb 5.3
+ // find first type that does not have an array, this is the RAW value
+ const char* end=start;
+ findEnd(end);
+ const char* cur=start;
+ while (cur<end) {
+ while (*cur != '=' && cur<end)
+ cur++;
+ cur++;
+ while (isspace(*cur) && cur<end)
+ cur++;
+ if (isNumberish(*cur)) {
+ end=cur;
+ while (*end && (*end!='}') && (*end!=',') && (*end!='\n'))
+ end++;
+ QString rawValue = QString::fromLatin1(cur, end-cur).simplifyWhiteSpace();
+ reg.rawValue = rawValue;
+
+ if (rawValue.left(2)=="0x") {
+ // ok we have a raw value, now get it's type
+ end=cur-1;
+ while (isspace(*end) || *end=='=') end--;
+ end++;
+ cur=end-1;
+ while (*cur!='{' && *cur!=' ')
+ cur--;
+ cur++;
+ reg.type = QString::fromLatin1(cur, end-cur);
+ }
+
+ // end while loop
+ cur=end;
+ }
+ }
+ // skip to the end of line
+ while (*output != '\0' && *output != '\n')
+ output++;
+ // get rid of the braces at the begining and the end
+ value.remove(0, 1);
+ if (value[value.length()-1] == '}') {
+ value.truncate(value.length()-1);
+ }
+ }
+ reg.cookedValue = value;
+ }
+ else
+ {
+ // the rest of the line is the register value
+ start = output;
+ output = strchr(output,'\n');
+ if (output == 0)
+ output = start + strlen(start);
+ value = QString::fromLatin1(start, output-start).simplifyWhiteSpace();
+
+ /*
+ * We split the raw from the cooked values.
+ * Some modern gdbs explicitly say: "0.1234 (raw 0x3e4567...)".
+ * Here, the cooked value comes first, and the raw value is in
+ * the second part.
+ */
+ int pos = value.find(" (raw ");
+ if (pos >= 0)
+ {
+ reg.cookedValue = value.left(pos);
+ reg.rawValue = value.mid(pos+6);
+ if (reg.rawValue.right(1) == ")") // remove closing bracket
+ reg.rawValue.truncate(reg.rawValue.length()-1);
+ }
+ else
+ {
+ /*
+ * In other cases we split off the first token (separated by
+ * whitespace). It is the raw value. The remainder of the line
+ * is the cooked value.
+ */
+ int pos = value.find(' ');
+ if (pos < 0) {
+ reg.rawValue = value;
+ reg.cookedValue = QString();
+ } else {
+ reg.rawValue = value.left(pos);
+ reg.cookedValue = value.mid(pos+1);
+ }
+ }
+ }
+ if (*output != '\0')
+ output++; /* skip '\n' */
+
+ regs.push_back(reg);
+ }
+ return regs;
+}
+
+bool GdbDriver::parseInfoLine(const char* output, QString& addrFrom, QString& addrTo)
+{
+ // "is at address" or "starts at address"
+ const char* start = strstr(output, "s at address ");
+ if (start == 0)
+ return false;
+
+ start += 13;
+ const char* p = start;
+ while (*p != '\0' && !isspace(*p))
+ p++;
+ addrFrom = QString::fromLatin1(start, p-start);
+
+ start = strstr(p, "and ends at ");
+ if (start == 0) {
+ addrTo = addrFrom;
+ return true;
+ }
+
+ start += 12;
+ p = start;
+ while (*p != '\0' && !isspace(*p))
+ p++;
+ addrTo = QString::fromLatin1(start, p-start);
+
+ return true;
+}
+
+std::list<DisassembledCode> GdbDriver::parseDisassemble(const char* output)
+{
+ std::list<DisassembledCode> code;
+
+ if (strncmp(output, "Dump of assembler", 17) != 0) {
+ // error message?
+ DisassembledCode c;
+ c.code = output;
+ code.push_back(c);
+ return code;
+ }
+
+ // remove first line
+ const char* p = strchr(output, '\n');
+ if (p == 0)
+ return code; /* not a regular output */
+
+ p++;
+
+ // remove last line
+ const char* end = strstr(output, "End of assembler");
+ if (end == 0)
+ end = p + strlen(p);
+
+ // remove function offsets from the lines
+ while (p != end)
+ {
+ DisassembledCode c;
+ const char* start = p;
+ // address
+ while (p != end && !isspace(*p))
+ p++;
+ c.address = QString::fromLatin1(start, p-start);
+
+ // function name (enclosed in '<>', followed by ':')
+ while (p != end && *p != '<')
+ p++;
+ if (*p == '<')
+ skipNestedAngles(p);
+ if (*p == ':')
+ p++;
+
+ // space until code
+ while (p != end && isspace(*p))
+ p++;
+
+ // code until end of line
+ start = p;
+ while (p != end && *p != '\n')
+ p++;
+ if (p != end) /* include '\n' */
+ p++;
+
+ c.code = QString::fromLatin1(start, p-start);
+ code.push_back(c);
+ }
+ return code;
+}
+
+QString GdbDriver::parseMemoryDump(const char* output, std::list<MemoryDump>& memdump)
+{
+ if (isErrorExpr(output)) {
+ // error; strip space
+ QString msg = output;
+ return msg.stripWhiteSpace();
+ }
+
+ const char* p = output; /* save typing */
+
+ // the address
+ while (*p != 0) {
+ MemoryDump md;
+
+ const char* start = p;
+ while (*p != '\0' && *p != ':' && !isspace(*p))
+ p++;
+ md.address = QString::fromLatin1(start, p-start);
+ if (*p != ':') {
+ // parse function offset
+ while (isspace(*p))
+ p++;
+ start = p;
+ while (*p != '\0' && !(*p == ':' && isspace(p[1])))
+ p++;
+ md.address.fnoffs = QString::fromLatin1(start, p-start);
+ }
+ if (*p == ':')
+ p++;
+ // skip space; this may skip a new-line char!
+ while (isspace(*p))
+ p++;
+ // everything to the end of the line is the memory dump
+ const char* end = strchr(p, '\n');
+ if (end != 0) {
+ md.dump = QString::fromLatin1(p, end-p);
+ p = end+1;
+ } else {
+ md.dump = QString::fromLatin1(p, strlen(p));
+ p += strlen(p);
+ }
+ memdump.push_back(md);
+ }
+
+ return QString();
+}
+
+QString GdbDriver::editableValue(VarTree* value)
+{
+ const char* s = value->value().latin1();
+
+ // if the variable is a pointer value that contains a cast,
+ // remove the cast
+ if (*s == '(') {
+ skipNested(s, '(', ')');
+ // skip space
+ while (isspace(*s))
+ ++s;
+ }
+
+repeat:
+ const char* start = s;
+
+ if (strncmp(s, "0x", 2) == 0)
+ {
+ s += 2;
+ while (isxdigit(*s))
+ ++s;
+
+ /*
+ * What we saw so far might have been a reference. If so, edit the
+ * referenced value. Otherwise, edit the pointer.
+ */
+ if (*s == ':') {
+ // a reference
+ ++s;
+ goto repeat;
+ }
+ // a pointer
+ // if it's a pointer to a string, remove the string
+ const char* end = s;
+ while (isspace(*s))
+ ++s;
+ if (*s == '"') {
+ // a string
+ return QString::fromLatin1(start, end-start);
+ } else {
+ // other pointer
+ return QString::fromLatin1(start, strlen(start));
+ }
+ }
+
+ // else leave it unchanged (or stripped of the reference preamble)
+ return s;
+}
+
+QString GdbDriver::parseSetVariable(const char* output)
+{
+ // if there is any output, it is an error message
+ QString msg = output;
+ return msg.stripWhiteSpace();
+}
+
+
+#include "gdbdriver.moc"