Commit 191b7e0b authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Debugger[new CDB]: Work on locals.

Add optional code model scope checking. Remove need to call
to 'expandlocals' command by giving 'locals' options for expanded
and uninitialized variables, saving one roundtrip.
Handle shadowed variables and __formal parameters. Differentiate
between name and iname in SymbolGroup.
parent 40548df8
......@@ -134,7 +134,11 @@ extern "C" HRESULT CALLBACK expandlocals(CIDebugClient *client, PCSTR args)
static inline std::string msgLocalsUsage(PCSTR args)
{
std::ostringstream str;
str << "Invalid parameter: '" << args << "' (usage: locals [-d] <frame> [iname]).";
str << "Invalid parameter: '" << args
<< "'\nUsage: locals [-t token] [-h] [-d] [-e expandset] [-u uninitializedset] <frame> [iname]).\n"
"-h human-readable ouput\n"
"-d debug output\n-e expandset Comma-separated list of expanded inames\n"
"-u uninitializedset Comma-separated list of uninitialized inames\n";
return str.str();
}
......@@ -150,18 +154,38 @@ static std::string commmandLocals(ExtensionCommandContext &exc,PCSTR args, int *
std::string iname;
StringList tokens = commandTokens<StringList>(args, token);
StringVector expandedInames;
StringVector uninitializedInames;
// Parse away options
while (!tokens.empty() && tokens.front().size() == 2 && tokens.front().at(0) == '-') {
// Parse options -d (debug)/- humanreadable GDBMI
switch (tokens.front().at(1)) {
const char option = tokens.front().at(1);
tokens.pop_front();
switch (option) {
case 'd':
debugOutput++;
break;
case 'h':
humanReadableGdbmi = true;
break;
case 'u':
if (tokens.empty()) {
*errorMessage = msgLocalsUsage(args);
return std::string();
}
split(tokens.front(), ',', std::back_inserter(uninitializedInames));
tokens.pop_front();
break;
case 'e':
if (tokens.empty()) {
*errorMessage = msgLocalsUsage(args);
return std::string();
}
split(tokens.front(), ',', std::back_inserter(expandedInames));
tokens.pop_front();
break;
}
tokens.pop_front();
}
// Frame and iname
unsigned frame;
if (tokens.empty() || sscanf(tokens.front().c_str(), "%u", &frame) != 1) {
*errorMessage = msgLocalsUsage(args);
......@@ -175,7 +199,10 @@ static std::string commmandLocals(ExtensionCommandContext &exc,PCSTR args, int *
SymbolGroup * const symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), frame, errorMessage);
if (!symGroup)
return std::string();
if (!expandedInames.empty())
symGroup->expandList(expandedInames, errorMessage);
if (!uninitializedInames.empty())
symGroup->markUninitialized(uninitializedInames);
// Complete dump
if (iname.empty())
return debugOutput ? symGroup->debug(debugOutput - 1) : symGroup->dump(humanReadableGdbmi);
......
......@@ -40,6 +40,7 @@
enum { debug = 0 };
typedef std::vector<int>::size_type VectorIndexType;
typedef std::vector<std::string> StringVector;
const char rootNameC[] = "local";
......@@ -207,9 +208,10 @@ SymbolGroup *SymbolGroup::create(CIDebugControl *control, CIDebugSymbols *debugS
// ------- SymbolGroupNode
SymbolGroupNode::SymbolGroupNode(CIDebugSymbolGroup *symbolGroup,
const std::string &n,
const std::string &name,
const std::string &iname,
SymbolGroupNode *parent) :
m_symbolGroup(symbolGroup), m_parent(parent), m_name(n)
m_symbolGroup(symbolGroup), m_parent(parent), m_name(name), m_iname(iname), m_flags(0)
{
memset(&m_parameters, 0, sizeof(DEBUG_SYMBOL_PARAMETERS));
m_parameters.ParentSymbol = DEBUG_ANY_ID;
......@@ -230,23 +232,6 @@ bool SymbolGroupNode::isArrayElement() const
return m_parent && (m_parent->m_parameters.Flags & DEBUG_SYMBOL_IS_ARRAY);
}
// iName: Fix array elements to be named 'array.0' instead of 'array.[0]' so
// that sorting in Qt Creator works.
std::string SymbolGroupNode::iName() const
{
std::string rc = m_name;
//rc += isArrayElement() ? 'a' : 'n';
if (isArrayElement() && !rc.empty() && rc.at(0) == '[') {
const std::string::size_type last = rc.size() - 1;
if (rc.at(last) == ']') {
rc.erase(last, 1);
rc.erase(0, 1);
}
}
return rc;
}
// Return full iname as 'locals.this.m_sth'.
std::string SymbolGroupNode::fullIName() const
{
......@@ -258,6 +243,58 @@ std::string SymbolGroupNode::fullIName() const
return rc;
}
// Fix an array iname "[0]" -> "0" for sorting to work correctly
static inline void fixArrayIname(std::string *iname)
{
if (!iname->empty() && iname->at(0) == '[') {
const std::string::size_type last = iname->size() - 1;
if (iname->at(last) == ']') {
iname->erase(last, 1);
iname->erase(0, 1);
}
}
}
// Fix up names and inames
static inline void fixNames(bool isTopLevel, StringVector *names, StringVector *inames)
{
if (names->empty())
return;
unsigned unnamedId = 1;
/* 1) Fix name="__formal", which occurs when someone writes "void foo(int /* x * /)..."
* 2) Fix array inames for sorting: "[6]" -> name="[6]",iname="6"
* 3) For toplevels: Fix shadowed variables in the order the debugger expects them:
\code
int x; // Occurrence (1), should be reported as name="x <shadowed 1>"/iname="x#1"
if (true) {
int x = 5; (2) // Occurrence (2), should be reported as name="x"/iname="x"
}
\endcode */
StringVector::iterator nameIt = names->begin();
const StringVector::iterator namesEnd = names->end();
for (StringVector::iterator iNameIt = inames->begin(); nameIt != namesEnd ; ++nameIt, ++iNameIt) {
std::string &name = *nameIt;
std::string &iname = *iNameIt;
if (name.empty() || name == "__formal") {
const std::string number = toString(unnamedId++);
name = "<unnamed " + number + '>';
iname = "unnamed#" + number;
} else {
fixArrayIname(&iname);
}
if (isTopLevel) {
if (const StringVector::size_type shadowCount = std::count(nameIt + 1, namesEnd, name)) {
const std::string number = toString(shadowCount);
name += " <shadowed ";
name += number;
name += '>';
iname += '#';
iname += number;
}
}
}
}
// Index: Index of symbol, parameterOffset: Looking only at a part of the symbol array, offset
void SymbolGroupNode::parseParameters(VectorIndexType index,
VectorIndexType parameterOffset,
......@@ -279,15 +316,31 @@ void SymbolGroupNode::parseParameters(VectorIndexType index,
const VectorIndexType size = vec.size();
// Scan the top level elements
StringVector names;
names.reserve(size);
// Pass 1) Determine names. We need the complete set first in order to do some corrections.
const VectorIndexType startIndex = isTopLevel ? 0 : index + 1;
for (VectorIndexType pos = startIndex - parameterOffset; pos < size ; pos++ ) {
if (vec.at(pos).ParentSymbol == index) {
const VectorIndexType symbolGroupIndex = pos + parameterOffset;
HRESULT hr = m_symbolGroup->GetSymbolName(ULONG(symbolGroupIndex), buf, BufSize, &obtainedSize);
const std::string name = SUCCEEDED(hr) ? std::string(buf) : std::string("unnamed");
SymbolGroupNode *child = new SymbolGroupNode(m_symbolGroup, name, this);
if (FAILED(m_symbolGroup->GetSymbolName(ULONG(symbolGroupIndex), buf, BufSize, &obtainedSize)))
buf[0] = '\0';
names.push_back(std::string(buf));
}
}
// 2) Fix names
StringVector inames = names;
fixNames(isTopLevel, &names, &inames);
// Pass 3): Add nodes with fixed names
StringVector::size_type nameIndex = 0;
for (VectorIndexType pos = startIndex - parameterOffset; pos < size ; pos++ ) {
if (vec.at(pos).ParentSymbol == index) {
const VectorIndexType symbolGroupIndex = pos + parameterOffset;
SymbolGroupNode *child = new SymbolGroupNode(m_symbolGroup, names.at(nameIndex),
inames.at(nameIndex), this);
child->parseParameters(symbolGroupIndex, parameterOffset, vec);
m_children.push_back(child);
nameIndex++;
}
}
if (isTopLevel)
......@@ -296,7 +349,7 @@ void SymbolGroupNode::parseParameters(VectorIndexType index,
SymbolGroupNode *SymbolGroupNode::create(CIDebugSymbolGroup *sg, const std::string &name, const SymbolGroup::SymbolParameterVector &vec)
{
SymbolGroupNode *rc = new SymbolGroupNode(sg, name);
SymbolGroupNode *rc = new SymbolGroupNode(sg, name, name);
rc->parseParameters(DEBUG_ANY_ID, 0, vec);
return rc;
}
......@@ -436,28 +489,40 @@ void SymbolGroupNode::dump(std::ostream &str, unsigned child, unsigned depth,
str << ",addr=\"" << std::hex << std::showbase << addr << std::noshowbase << std::dec
<< '"';
ULONG obtainedSize = 0;
if (const wchar_t *wbuf = getValue(index, &obtainedSize)) {
const ULONG valueSize = obtainedSize - 1;
// ASCII or base64?
if (isSevenBitClean(wbuf, valueSize)) {
std::wstring value = wbuf;
fixValue(type, &value);
str << ",valueencoded=\"0\",value=\"" << gdbmiWStringFormat(value) << '"';
} else {
str << ",valueencoded=\"2\",value=\"";
base64Encode(str, reinterpret_cast<const unsigned char *>(wbuf), valueSize * sizeof(wchar_t));
str << '"';
bool valueEditable = true;
bool valueEnabled = true;
const bool uninitialized = m_flags & Uninitialized;
if (uninitialized) {
valueEditable = valueEnabled = false;
str << ",valueencoded=\"0\",value=\"<not in scope>\"";
} else {
ULONG obtainedSize = 0;
if (const wchar_t *wbuf = getValue(index, &obtainedSize)) {
const ULONG valueSize = obtainedSize - 1;
// ASCII or base64?
if (isSevenBitClean(wbuf, valueSize)) {
std::wstring value = wbuf;
fixValue(type, &value);
str << ",valueencoded=\"0\",value=\"" << gdbmiWStringFormat(value) << '"';
} else {
str << ",valueencoded=\"2\",value=\"";
base64Encode(str, reinterpret_cast<const unsigned char *>(wbuf), valueSize * sizeof(wchar_t));
str << '"';
}
delete [] wbuf;
}
delete [] wbuf;
}
// Children: Dump all known or subelements (guess).
const VectorIndexType childCountGuess = m_children.empty() ? m_parameters.SubElements : m_children.size();
const VectorIndexType childCountGuess = uninitialized ? 0 :
(m_children.empty() ? m_parameters.SubElements : m_children.size());
// No children..suppose we are editable and enabled
if (childCountGuess == 0)
str << ",valueenabled=\"true\",valueeditable=\"true\"";
str << ",numchild=\"" << childCountGuess << '"';
if (!m_children.empty()) {
if (childCountGuess != 0)
valueEditable = false;
str << ",valueenabled=\"" << (valueEnabled ? "true" : "false") << '"'
<< ",valueeditable=\"" << (valueEditable ? "true" : "false") << '"'
<< ",numchild=\"" << childCountGuess << '"';
if (!uninitialized && !m_children.empty()) {
str << ",children=[";
if (humanReadable)
str << '\n';
......@@ -501,9 +566,14 @@ bool SymbolGroupNode::accept(SymbolGroupNodeVisitor &visitor, unsigned child, un
void SymbolGroupNode::debug(std::ostream &str, unsigned verbosity, unsigned depth, ULONG index) const
{
indentStream(str, depth);
str << '"' << m_name << "\" Children=" << m_children.size() << ' ' << m_parameters;
if (verbosity)
str << " Address=0x" << std::hex << address(index) << std::dec << " Type=\"" << getType(index) << "\" Value=\"" << gdbmiWStringFormat(rawValue(index)) << '"';
str << '"' << m_name << "\" Children=" << m_children.size() << ' ' << m_parameters
<< " flags=" << m_flags;
if (verbosity) {
str << " Address=0x" << std::hex << address(index) << std::dec
<< " Type=\"" << getType(index) << '"';
if (!(m_flags & Uninitialized))
str << "\" Value=\"" << gdbmiWStringFormat(rawValue(index)) << '"';
}
str << '\n';
}
......@@ -531,6 +601,10 @@ bool SymbolGroupNode::expand(ULONG index, std::string *errorMessage)
*errorMessage = "No subelements to expand in node: " + fullIName();
return false;
}
if (m_flags & Uninitialized) {
*errorMessage = "Refusing to expand uninitialized node: " + fullIName();
return false;
}
const HRESULT hr = m_symbolGroup->ExpandSymbol(index, TRUE);
......@@ -626,7 +700,7 @@ unsigned SymbolGroup::expandList(const std::vector<std::string> &nodes, std::str
{
if (nodes.empty())
return 0;
// Create a set with a key <level, name>. Also required for 1 node.
// Create a set with a key <level, name>. Also required for 1 node (see above).
InamePathEntrySet pathEntries;
const VectorIndexType nodeCount = nodes.size();
for (VectorIndexType i= 0; i < nodeCount; i++) {
......@@ -670,6 +744,21 @@ bool SymbolGroup::expand(const std::string &nodeName, std::string *errorMessage)
return node->expand(index, errorMessage);
}
// Mark uninitialized (top level only)
void SymbolGroup::markUninitialized(const std::vector<std::string> &uniniNodes)
{
if (m_root && !m_root->children().empty() && !uniniNodes.empty()) {
const std::vector<std::string>::const_iterator unIniNodesBegin = uniniNodes.begin();
const std::vector<std::string>::const_iterator unIniNodesEnd = uniniNodes.end();
const SymbolGroupNodePtrVector::const_iterator childrenEnd = m_root->children().end();
for (SymbolGroupNodePtrVector::const_iterator it = m_root->children().begin(); it != childrenEnd; ++it) {
if (std::find(unIniNodesBegin, unIniNodesEnd, (*it)->fullIName()) != unIniNodesEnd)
(*it)->setFlags((*it)->flags() | SymbolGroupNode::Uninitialized);
}
}
}
static inline std::string msgAssignError(const std::string &nodeName,
const std::string &value,
const std::string &why)
......
......@@ -45,6 +45,9 @@ class SymbolGroupNode {
SymbolGroupNode(const SymbolGroupNode&);
SymbolGroupNode& operator=(const SymbolGroupNode&);
public:
enum Flags {
Uninitialized = 0x1
};
typedef std::vector<DEBUG_SYMBOL_PARAMETERS> SymbolParameterVector;
typedef std::vector<SymbolGroupNode *> SymbolGroupNodePtrVector;
typedef SymbolGroupNodePtrVector::iterator SymbolGroupNodePtrVectorIterator;
......@@ -52,6 +55,7 @@ public:
explicit SymbolGroupNode(CIDebugSymbolGroup *symbolGroup,
const std::string &name,
const std::string &iname,
SymbolGroupNode *parent = 0);
~SymbolGroupNode() { removeChildren(); }
......@@ -65,7 +69,7 @@ public:
const std::string &name() const { return m_name; }
std::string fullIName() const;
std::string iName() const;
const std::string &iName() const { return m_iname; }
const SymbolGroupNodePtrVector &children() const { return m_children; }
SymbolGroupNode *childAt(unsigned) const;
......@@ -91,6 +95,9 @@ public:
ULONG subElements() const { return m_parameters.SubElements; }
unsigned flags() const { return m_flags; }
void setFlags(unsigned f) { m_flags = f; }
private:
// Return allocated wide string array of value
wchar_t *getValue(ULONG index, ULONG *obtainedSize = 0) const;
......@@ -102,6 +109,8 @@ private:
DEBUG_SYMBOL_PARAMETERS m_parameters; // Careful when using ParentSymbol. It might not be correct.
SymbolGroupNodePtrVector m_children;
const std::string m_name;
const std::string m_iname;
unsigned m_flags;
};
/* Visitor that takes care of iterating over the nodes and the index bookkeeping.
......@@ -165,6 +174,8 @@ public:
// Expand a node list "locals.i1,locals.i2", expanding all nested child nodes
// (think mkdir -p).
unsigned expandList(const std::vector<std::string> &nodes, std::string *errorMessage);
// Mark uninitialized (top level only)
void markUninitialized(const std::vector<std::string> &nodes);
// Expand a single node "locals.A.B" requiring that "locals.A.B" is already visible
// (think mkdir without -p).
......
......@@ -37,6 +37,12 @@ ByteArrayInputStream::ByteArrayInputStream(QByteArray &ba) :
{
}
void ByteArrayInputStream::appendSeparator(char c)
{
if (!m_target.isEmpty() && !m_target.endsWith(c))
m_target.append(c);
}
void hexPrefixOn(ByteArrayInputStream &bs)
{
bs.setHexPrefix(true);
......@@ -57,6 +63,11 @@ void dec(ByteArrayInputStream &bs)
bs.setIntegerBase(10);
}
void blankSeparator(ByteArrayInputStream &bs)
{
bs.appendSeparator();
}
QByteArray trimFront(QByteArray in)
{
if (in.isEmpty())
......
......@@ -61,6 +61,8 @@ public:
bool hexPrefix() const { return m_hexPrefix; }
void setIntegerBase(int b) { m_integerBase = b; }
int integerBase() const { return m_integerBase; }
// Append a separator if required (target does not end with it)
void appendSeparator(char c = ' ');
private:
template <class IntType> void appendInt(IntType i);
......@@ -93,6 +95,7 @@ void hexPrefixOn(ByteArrayInputStream &bs);
void hexPrefixOff(ByteArrayInputStream &bs);
void hex(ByteArrayInputStream &bs);
void dec(ByteArrayInputStream &bs);
void blankSeparator(ByteArrayInputStream &bs);
// Bytearray parse helpers
QByteArray trimFront(QByteArray in);
......
......@@ -61,6 +61,7 @@
#include <QtCore/QDebug>
#include <QtCore/QTextStream>
#include <QtCore/QDateTime>
#include <QtGui/QToolTip>
#ifdef Q_OS_WIN
# include <utils/winutils.h>
......@@ -73,6 +74,7 @@ Q_DECLARE_METATYPE(Debugger::Internal::DisassemblerViewAgent*)
Q_DECLARE_METATYPE(Debugger::Internal::MemoryViewAgent*)
enum { debug = 0 };
enum { debugLocals = 0 };
enum { debugBreakpoints = 0 };
#if 0
......@@ -107,6 +109,8 @@ enum { debugBreakpoints = 0 };
namespace Debugger {
namespace Cdb {
static const char localsPrefixC[] = "local.";
using namespace Debugger::Internal;
struct MemoryViewCookie {
......@@ -346,10 +350,11 @@ void CdbEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEd
// No numerical or any other expressions [yet]
if (!(exp.at(0).isLetter() || exp.at(0) == QLatin1Char('_')))
return;
const QByteArray iname = QByteArray("local.") + exp.toAscii();
const QModelIndex index = watchHandler()->itemIndex(iname);
Q_UNUSED(index)
Q_UNUSED(mousePos)
const QByteArray iname = QByteArray(localsPrefixC) + exp.toAscii();
if (const WatchData *data = watchHandler()->findItem(iname)) {
QToolTip::hideText();
QToolTip::showText(mousePos, data->toToolTip());
}
}
void CdbEngine::setupEngine()
......@@ -903,8 +908,6 @@ void CdbEngine::postExtensionCommand(const QByteArray &cmd,
showMessage(msg, LogError);
return;
}
if (!flags & QuietCommand)
showMessage(QString::fromLocal8Bit(cmd), LogInput);
const int token = m_nextCommandToken++;
......@@ -915,6 +918,9 @@ void CdbEngine::postExtensionCommand(const QByteArray &cmd,
if (!arguments.isEmpty())
str << ' ' << arguments;
if (!flags & QuietCommand)
showMessage(QString::fromLocal8Bit(fullCmd), LogInput);
CdbExtensionCommandPtr pendingCommand(new CdbExtensionCommand(fullCmd, token, flags, handler, nextCommandFlag, cookie));
m_extensionCommandQueue.push_back(pendingCommand);
......@@ -934,9 +940,10 @@ void CdbEngine::activateFrame(int index)
const Debugger::Internal::StackFrames &frames = stackHandler()->frames();
QTC_ASSERT(index < frames.size(), return; )
if (debug)
const StackFrame frame = frames.at(index);
if (debug || debugLocals)
qDebug("activateFrame idx=%d '%s' %d", index,
qPrintable(frames.at(index).file), frames.at(index).line);
qPrintable(frame.file), frame.line);
stackHandler()->setCurrentIndex(index);
const bool showAssembler = !frames.at(index).isUsable();
if (showAssembler) { // Assembly code: Clean out model and force instruction mode.
......@@ -944,30 +951,46 @@ void CdbEngine::activateFrame(int index)
watchHandler()->endCycle();
QAction *assemblerAction = theAssemblerAction();
if (assemblerAction->isChecked()) {
gotoLocation(frames.at(index), true);
gotoLocation(frame, true);
} else {
assemblerAction->trigger(); // Seems to trigger update
}
return;
}
gotoLocation(frames.at(index), true);
// Watchers: Initial expand and query
gotoLocation(frame, true);
// Watchers: Initial expand, get uninitialized and query
QByteArray arguments;
ByteArrayInputStream str(arguments);
// Pre-expand
const QSet<QByteArray> expanded = watchHandler()->expandedINames();
if (!expanded.isEmpty()) {
QByteArray expandArguments;
ByteArrayInputStream expandStr(expandArguments);
expandStr << index << ' ';
str << blankSeparator << "-e ";
int i = 0;
foreach(const QByteArray &e, expanded) {
if (i++)
expandStr << ',';
expandStr << e;
str << ',';
str << e;
}
postExtensionCommand("expandlocals", expandArguments, 0, &CdbEngine::handleExpandLocals);
}
// Uninitialized variables if desired
if (debuggerCore()->boolSetting(UseCodeModel)) {
QStringList uninitializedVariables;
getUninitializedVariables(debuggerCore()->cppCodeModelSnapshot(),
frame.function, frame.file, frame.line, &uninitializedVariables);
if (!uninitializedVariables.isEmpty()) {
str << blankSeparator << "-u ";
int i = 0;
foreach(const QString &u, uninitializedVariables) {
if (i++)
str << ',';
str << localsPrefixC << u;
}
}
}
// Required arguments: frame
str << blankSeparator << index;
watchHandler()->beginCycle();
postExtensionCommand("locals", QByteArray::number(index), 0, &CdbEngine::handleLocals);
postExtensionCommand("locals", arguments, 0, &CdbEngine::handleLocals);
}
void CdbEngine::selectThread(int index)
......
......@@ -1409,9 +1409,16 @@ static bool gdbMiGetBoolValue(bool *target,
struct GdbMiRecursionContext
{
GdbMiRecursionContext(int recursionLevelIn = 0) :
recursionLevel(recursionLevelIn), childNumChild(-1), childIndex(0) {}
enum Type
{
Debugger, // Debugger symbol dump, recursive/symmetrical
GdbMacrosCpp // old gdbmacros.cpp format, unsymmetrical
};
GdbMiRecursionContext(Type t, int recursionLevelIn = 0) :
type(t), recursionLevel(recursionLevelIn), childNumChild(-1), childIndex(0) {}
const Type type;
int recursionLevel;
int childNumChild;
int childIndex;
......@@ -1429,31 +1436,41 @@ static void gbdMiToWatchData(const GdbMi &root,
QString v;
QByteArray b;
// Check for name/iname and use as expression default
if (ctx.recursionLevel == 0) {
// parents have only iname, from which name is derived
QString iname;
if (!gdbMiGetStringValue(&iname, root, "iname"))
qWarning("Internal error: iname missing");
w.iname = iname.toLatin1();
w.name = iname;
const int lastDotPos = w.name.lastIndexOf(QLatin1Char('.'));
if (lastDotPos != -1)
w.name.remove(0, lastDotPos + 1);
w.exp = w.name.toLatin1();
w.sortId = ctx.childIndex;
// Fully symmetrical
if (ctx.type == GdbMiRecursionContext::Debugger) {
gdbMiGetByteArrayValue(&w.iname, root, "iname");