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.
2572 lines
64 KiB
2572 lines
64 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 "gdbdriver.h"
|
|
#include "exprwnd.h"
|
|
#include <tqregexp.h>
|
|
#include <tqstringlist.h>
|
|
#include <tdelocale.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, TQString& 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 TQString data print statement generally looks like.
|
|
// It is set by KDebugger via setPrintTQStringDataCmd().
|
|
|
|
static const char printTQStringStructFmt[] =
|
|
// 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 printTQStringStructFmt[] = "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 },
|
|
#ifdef __FreeBSD__
|
|
{ DCcorefile, "target FreeBSD-core %s\n", GdbCmdInfo::argString },
|
|
#else
|
|
{ DCcorefile, "target core %s\n", GdbCmdInfo::argString },
|
|
#endif
|
|
{ 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 },
|
|
{ DCprintTQStringStruct, printTQStringStructFmt, 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()
|
|
{
|
|
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(printTQStringStructFmt) <= MAX_FMTLEN);
|
|
#endif
|
|
}
|
|
|
|
GdbDriver::~GdbDriver()
|
|
{
|
|
}
|
|
|
|
|
|
TQString GdbDriver::driverName() const
|
|
{
|
|
return "GDB";
|
|
}
|
|
|
|
TQString GdbDriver::defaultGdb()
|
|
{
|
|
return
|
|
"gdb"
|
|
" --fullname" /* to get standard file names each time the prog stops */
|
|
" --nx"; /* do not execute initialization files */
|
|
}
|
|
|
|
TQString GdbDriver::defaultInvocation() const
|
|
{
|
|
if (m_defaultCmd.isEmpty()) {
|
|
return defaultGdb();
|
|
} else {
|
|
return m_defaultCmd;
|
|
}
|
|
}
|
|
|
|
TQStringList GdbDriver::boolOptionList() const
|
|
{
|
|
// no options
|
|
return TQStringList();
|
|
}
|
|
|
|
bool GdbDriver::startup(TQString 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 TQString::null is ok
|
|
cmds[DCprintTQStringStruct].fmt = printTQStringStructFmt;
|
|
|
|
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:
|
|
{
|
|
/*
|
|
* 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).
|
|
*/
|
|
TQRegExp 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(TQString("found marker: ") + startMarker);
|
|
char* endMarker = strchr(startMarker, '\n');
|
|
if (endMarker == 0)
|
|
return;
|
|
|
|
*endMarker = '\0';
|
|
|
|
// extract filename and line number
|
|
static TQRegExp MarkerRE(":[0-9]+:[0-9]+:[begmidl]+:0x");
|
|
|
|
int lineNoStart = MarkerRE.search(startMarker, 0);
|
|
if (lineNoStart >= 0) {
|
|
int len = MarkerRE.matchedLength();
|
|
int lineNo = atoi(startMarker + lineNoStart+1);
|
|
|
|
// get address
|
|
const char* addrStart = startMarker + lineNoStart + len - 2;
|
|
DbgAddr address = TQString(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(TQString& 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);
|
|
}
|
|
}
|
|
|
|
|
|
TQString GdbDriver::makeCmdString(DbgCommand cmd, TQString 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;
|
|
}
|
|
|
|
TQString cmdString;
|
|
cmdString.sprintf(cmds[cmd].fmt, strArg.latin1());
|
|
return cmdString;
|
|
}
|
|
|
|
TQString GdbDriver::makeCmdString(DbgCommand cmd, int intArg)
|
|
{
|
|
assert(cmd >= 0 && cmd < NUM_CMDS);
|
|
assert(cmds[cmd].argsNeeded == GdbCmdInfo::argNum);
|
|
|
|
TQString cmdString;
|
|
cmdString.sprintf(cmds[cmd].fmt, intArg);
|
|
return cmdString;
|
|
}
|
|
|
|
TQString GdbDriver::makeCmdString(DbgCommand cmd, TQString 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);
|
|
|
|
TQString 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;
|
|
}
|
|
TQString 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;
|
|
}
|
|
|
|
TQString GdbDriver::makeCmdString(DbgCommand cmd, TQString strArg1, TQString strArg2)
|
|
{
|
|
assert(cmd >= 0 && cmd < NUM_CMDS);
|
|
assert(cmds[cmd].argsNeeded == GdbCmdInfo::argString2);
|
|
|
|
normalizeStringArg(strArg1);
|
|
normalizeStringArg(strArg2);
|
|
|
|
TQString cmdString;
|
|
cmdString.sprintf(cmds[cmd].fmt, strArg1.latin1(), strArg2.latin1());
|
|
return cmdString;
|
|
}
|
|
|
|
TQString GdbDriver::makeCmdString(DbgCommand cmd, int intArg1, int intArg2)
|
|
{
|
|
assert(cmd >= 0 && cmd < NUM_CMDS);
|
|
assert(cmds[cmd].argsNeeded == GdbCmdInfo::argNum2);
|
|
|
|
TQString 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, TQString 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, TQString strArg, int intArg,
|
|
bool clearLow)
|
|
{
|
|
return executeCmdString(cmd, makeCmdString(cmd, strArg, intArg), clearLow);
|
|
}
|
|
|
|
CmdQueueItem* GdbDriver::executeCmd(DbgCommand cmd, TQString strArg1, TQString 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, TQString 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, TQString strArg, int intArg,
|
|
QueueMode mode)
|
|
{
|
|
return queueCmdString(cmd, makeCmdString(cmd, strArg, intArg), mode);
|
|
}
|
|
|
|
CmdQueueItem* GdbDriver::queueCmd(DbgCommand cmd, TQString strArg1, TQString 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(TQString(), VarTree::NKplain);
|
|
const char* endMsg = strchr(output, '\n');
|
|
if (endMsg == 0)
|
|
endMsg = output + strlen(output);
|
|
variable->m_value = TQString::fromLatin1(output, endMsg-output);
|
|
} else {
|
|
variable = 0;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if TQT_VERSION >= 300
|
|
union TQt2TQChar {
|
|
short s;
|
|
struct {
|
|
uchar row;
|
|
uchar cell;
|
|
} qch;
|
|
};
|
|
#endif
|
|
|
|
void GdbDriver::setPrintTQStringDataCmd(const char* cmd)
|
|
{
|
|
// don't accept the command if it is empty
|
|
if (cmd == 0 || *cmd == '\0')
|
|
return;
|
|
assert(strlen(cmd) <= MAX_FMTLEN);
|
|
cmds[DCprintTQStringStruct].fmt = cmd;
|
|
}
|
|
|
|
ExprValue* GdbDriver::parseTQCharArray(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(TQString(), 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
|
|
TQString result;
|
|
TQString 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 = TQString::fromLatin1(start, p-start);
|
|
while (isspace(*p) || *p == ',')
|
|
p++;
|
|
}
|
|
// p is now at the next char (or the end)
|
|
|
|
// interpret the value as a TQChar
|
|
// TODO: make cross-architecture compatible
|
|
TQChar ch;
|
|
if (qt3like) {
|
|
ch = TQChar(value);
|
|
} else {
|
|
#if TQT_VERSION < 300
|
|
(unsigned short&)ch = value;
|
|
#else
|
|
TQt2TQChar 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(TQString(), VarTree::NKplain);
|
|
variable->m_value = result;
|
|
}
|
|
else if (strncmp(p, "true", 4) == 0)
|
|
{
|
|
variable = new ExprValue(TQString(), VarTree::NKplain);
|
|
variable->m_value = "TQString::null";
|
|
}
|
|
else if (strncmp(p, "false", 5) == 0)
|
|
{
|
|
variable = new ExprValue(TQString(), VarTree::NKplain);
|
|
variable->m_value = "(null)";
|
|
}
|
|
else
|
|
goto error;
|
|
return variable;
|
|
|
|
error:
|
|
if (wantErrorValue) {
|
|
variable = new ExprValue(TQString(), 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++;
|
|
|
|
TQString 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(TQString("parse error: = not found after %1").arg(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(TQString("parse error: mismatching %1%2 at %3").arg(opening).arg(closing).arg(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(TQString("parse error: mismatching <> at %1").arg(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;
|
|
}
|
|
|
|
// some strings can end in <incomplete sequence ...>
|
|
if (strncmp(q, "<incomplete sequence", 20) == 0)
|
|
{
|
|
p = q+20;
|
|
while (*p != '\0' && *p != '>')
|
|
p++;
|
|
if (*p != '\0') {
|
|
p++; /* skip the '>' */
|
|
}
|
|
}
|
|
}
|
|
/* 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(TQString("parse error: mismatching %1%2 at %3").arg(opening).arg(closing).arg(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, TQString& 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 = TQString::fromLatin1(s, p - s);
|
|
kind = VarTree::NKtype;
|
|
}
|
|
else
|
|
{
|
|
// name, which might be "static"; allow dot for "_vtbl."
|
|
skipName(p);
|
|
if (p == s) {
|
|
TRACE(TQString("parse error: not a name %1").arg(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(TQString("parse error: not a name after static %1").arg(s));
|
|
return false;
|
|
}
|
|
len = p - s;
|
|
}
|
|
name = TQString::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 = TQString::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
|
|
TQString type;
|
|
if (*p == '(') {
|
|
skipNested(p, '(', ')');
|
|
|
|
while (isspace(*p))
|
|
p++;
|
|
variable->m_value = TQString::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 += TQString::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 += TQString::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 (;;) {
|
|
TQString 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(TQString("found <repeats %1 times> in array").arg(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, TQString& func,
|
|
TQString& 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 = TQString::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 = TQString::fromLatin1(start, 23);
|
|
file = TQString();
|
|
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 = TQString();
|
|
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 = TQString::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 = TQString::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 TQString::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, TQString& func,
|
|
TQString& 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)
|
|
{
|
|
TQString 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,
|
|
TQString& file, int& lineNo, DbgAddr& address)
|
|
{
|
|
TQString 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 = TQString::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 = TQString::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(TQString("hit count %1").arg(bp.hitCount));
|
|
} else if (strncmp(p, "stop only if ", 13) == 0) {
|
|
// extract condition
|
|
p += 13;
|
|
bp.condition = TQString::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(TQString("ignore count %1").arg(bp.ignoreCount));
|
|
} else {
|
|
// indeed a continuation
|
|
bp.location += " " + TQString::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 = TQString::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,
|
|
TQString& file, int& lineNo, TQString& address);
|
|
static bool parseNewWatchpoint(const char* o, int& id,
|
|
TQString& expr);
|
|
|
|
bool GdbDriver::parseBreakpoint(const char* output, int& id,
|
|
TQString& file, int& lineNo, TQString& 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,
|
|
TQString& file, int& lineNo, TQString& 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 = TQString::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 ");
|
|
TQString fileName = TQString::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,
|
|
TQString& 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 = TQString::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, TQString& message)
|
|
{
|
|
bool isGood = false;
|
|
message = TQString(output).simplifyWhiteSpace();
|
|
if (message.isEmpty()) {
|
|
message = i18n("New working directory: ") + m_programWD;
|
|
isGood = true;
|
|
}
|
|
return isGood;
|
|
}
|
|
|
|
bool GdbDriver::parseChangeExecutable(const char* output, TQString& 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, "Reading symbols from", 20) == 0 ||
|
|
strncmp(output, "done.", 5) == 0 ||
|
|
strncmp(output, "Missing separate debuginfo", 26) == 0 ||
|
|
strncmp(output, "Try: ", 5) == 0 ||
|
|
strncmp(output, "Using host libthread_db", 23) == 0 ||
|
|
strncmp(output, "(no debugging symbols found", 27) == 0 ||
|
|
strncmp(output, "(No debugging symbols found", 27) == 0 ||
|
|
strncmp(output, "warning: Loadable section", 25) == 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, TQString& 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 = TQString();
|
|
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 = TQString::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;
|
|
}
|
|
|
|
TQStringList GdbDriver::parseSharedLibs(const char* output)
|
|
{
|
|
TQStringList 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' */
|
|
TQString 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 = TQString::fromLatin1(start, output-start);
|
|
if (*output != '\0')
|
|
output++;
|
|
shlibs.append(shlibName);
|
|
TRACE("found shared lib " + shlibName);
|
|
}
|
|
return shlibs;
|
|
}
|
|
|
|
bool GdbDriver::parseFindType(const char* output, TQString& 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(TQRegExp("\\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;
|
|
}
|
|
|
|
TQString 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 = TQString::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 = TQString::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 = TQString::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++;
|
|
TQString rawValue = TQString::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 = TQString::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 = TQString::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 = TQString();
|
|
} 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, TQString& addrFrom, TQString& 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 = TQString::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 = TQString::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;
|
|
// skip initial space or PC pointer ("=>", since gdb 7.1)
|
|
while (p != end) {
|
|
if (isspace(*p))
|
|
++p;
|
|
else if (p[0] == '=' && p[1] == '>')
|
|
p += 2;
|
|
else
|
|
break;
|
|
}
|
|
const char* start = p;
|
|
// address
|
|
while (p != end && !isspace(*p))
|
|
p++;
|
|
c.address = TQString::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 = TQString::fromLatin1(start, p-start);
|
|
code.push_back(c);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
TQString GdbDriver::parseMemoryDump(const char* output, std::list<MemoryDump>& memdump)
|
|
{
|
|
if (isErrorExpr(output)) {
|
|
// error; strip space
|
|
TQString 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 = TQString::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 = TQString::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 = TQString::fromLatin1(p, end-p);
|
|
p = end+1;
|
|
} else {
|
|
md.dump = TQString::fromLatin1(p, strlen(p));
|
|
p += strlen(p);
|
|
}
|
|
memdump.push_back(md);
|
|
}
|
|
|
|
return TQString();
|
|
}
|
|
|
|
TQString 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 TQString::fromLatin1(start, end-start);
|
|
} else {
|
|
// other pointer
|
|
return TQString::fromLatin1(start, strlen(start));
|
|
}
|
|
}
|
|
|
|
// else leave it unchanged (or stripped of the reference preamble)
|
|
return s;
|
|
}
|
|
|
|
TQString GdbDriver::parseSetVariable(const char* output)
|
|
{
|
|
// if there is any output, it is an error message
|
|
TQString msg = output;
|
|
return msg.stripWhiteSpace();
|
|
}
|
|
|
|
|
|
#include "gdbdriver.moc"
|