Files
qt-creator/src/plugins/debugger/watchdata.cpp
hjk 525c33f999 Debugger: Infrastructure for reworked native mixed debugging
- Remove old experimental native mixed approach.
- Move some common stack parsing to Stackhandler.
- Mark gdbbridge.py debug output explicitly to remove it
  from actual reponse handling

New native mixed needs QtDeclarative changes and
QTC_DEBUGGER_NATIVE_MIXED=1 for now.

Change-Id: I09eed1da51cea878636d36756015b7bfaed34203
Reviewed-by: Christian Stenger <christian.stenger@theqtcompany.com>
2015-10-09 05:19:45 +00:00

701 lines
22 KiB
C++

/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms and
** conditions see http://www.qt.io/terms-conditions. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
// NOTE: Don't add dependencies to other files.
// This is used in the debugger auto-tests.
#include "watchdata.h"
#include "watchutils.h"
#include "debuggerprotocol.h"
#include <QDebug>
////////////////////////////////////////////////////////////////////
//
// WatchData
//
////////////////////////////////////////////////////////////////////
namespace Debugger {
namespace Internal {
bool isPointerType(const QByteArray &type)
{
return type.endsWith('*') || type.endsWith("* const");
}
bool isCharPointerType(const QByteArray &type)
{
return type == "char *" || type == "const char *" || type == "char const *";
}
bool isIntType(const QByteArray &type)
{
if (type.isEmpty())
return false;
switch (type.at(0)) {
case 'b':
return type == "bool";
case 'c':
return type == "char";
case 'i':
return type == "int";
case 'l':
return type == "long"
|| type == "long int"
|| type == "long unsigned int";
case 'p':
return type == "ptrdiff_t";
case 'q':
return type == "qint16" || type == "quint16"
|| type == "qint32" || type == "quint32"
|| type == "qint64" || type == "quint64"
|| type == "qlonglong" || type == "qulonglong";
case 's':
return type == "short"
|| type == "signed"
|| type == "size_t"
|| type == "std::size_t"
|| type == "std::ptrdiff_t"
|| (type.startsWith("signed") &&
( type == "signed char"
|| type == "signed short"
|| type == "signed short int"
|| type == "signed long"
|| type == "signed long int"
|| type == "signed long long"
|| type == "signed long long int"));
case 'u':
return type == "unsigned"
|| (type.startsWith("unsigned") &&
( type == "unsigned char"
|| type == "unsigned short"
|| type == "unsigned short int"
|| type == "unsigned int"
|| type == "unsigned long"
|| type == "unsigned long int"
|| type == "unsigned long long"
|| type == "unsigned long long int"));
default:
return false;
}
}
bool isFloatType(const QByteArray &type)
{
return type == "float" || type == "double" || type == "qreal";
}
bool isIntOrFloatType(const QByteArray &type)
{
return isIntType(type) || isFloatType(type);
}
WatchData::WatchData() :
id(0),
state(InitialState),
editformat(StopDisplay),
editencoding(Unencoded8Bit),
address(0),
origaddr(0),
size(0),
bitpos(0),
bitsize(0),
elided(0),
wantsChildren(false),
valueEnabled(true),
valueEditable(true),
outdated(false)
{
}
bool WatchData::isAncestorOf(const QByteArray &childIName) const
{
if (iname.size() >= childIName.size())
return false;
if (!childIName.startsWith(iname))
return false;
return childIName.at(iname.size()) == '.';
}
bool WatchData::isVTablePointer() const
{
// First case: Cdb only. No user type can be named like this, this is safe.
// Second case: Python dumper only.
return type.startsWith("__fptr()")
|| (type.isEmpty() && name == QLatin1String("[vptr]"));
}
void WatchData::setError(const QString &msg)
{
setAllUnneeded();
value = msg;
wantsChildren = false;
valueEnabled = false;
valueEditable = false;
}
void WatchData::setValue(const QString &value0)
{
value = value0;
if (value == QLatin1String("{...}")) {
value.clear();
wantsChildren = true; // at least one...
}
// strip off quoted characters for chars.
if (value.endsWith(QLatin1Char('\'')) && type.endsWith("char")) {
const int blankPos = value.indexOf(QLatin1Char(' '));
if (blankPos != -1)
value.truncate(blankPos);
}
// avoid duplicated information
if (value.startsWith(QLatin1Char('(')) && value.contains(QLatin1String(") 0x")))
value.remove(0, value.lastIndexOf(QLatin1String(") 0x")) + 2);
// doubles are sometimes displayed as "@0x6141378: 1.2".
// I don't want that.
if (/*isIntOrFloatType(type) && */ value.startsWith(QLatin1String("@0x"))
&& value.contains(QLatin1Char(':'))) {
value.remove(0, value.indexOf(QLatin1Char(':')) + 2);
setHasChildren(false);
}
// "numchild" is sometimes lying
//MODEL_DEBUG("\n\n\nPOINTER: " << type << value);
if (isPointerType(type))
setHasChildren(value != QLatin1String("0x0") && value != QLatin1String("<null>")
&& !isCharPointerType(type));
// pointer type information is available in the 'type'
// column. No need to duplicate it here.
if (value.startsWith(QLatin1Char('(') + QLatin1String(type) + QLatin1String(") 0x")))
value = value.section(QLatin1Char(' '), -1, -1);
setValueUnneeded();
}
enum GuessChildrenResult { HasChildren, HasNoChildren, HasPossiblyChildren };
static GuessChildrenResult guessChildren(const QByteArray &type)
{
if (isIntOrFloatType(type))
return HasNoChildren;
if (isCharPointerType(type))
return HasNoChildren;
if (isPointerType(type))
return HasChildren;
if (type.endsWith("QString"))
return HasNoChildren;
return HasPossiblyChildren;
}
void WatchData::setType(const QByteArray &str, bool guessChildrenFromType)
{
type = str.trimmed();
bool changed = true;
while (changed) {
if (type.endsWith("const"))
type.chop(5);
else if (type.endsWith(' '))
type.chop(1);
else if (type.startsWith("const "))
type = type.mid(6);
else if (type.startsWith("volatile "))
type = type.mid(9);
else if (type.startsWith("class "))
type = type.mid(6);
else if (type.startsWith("struct "))
type = type.mid(6);
else if (type.startsWith(' '))
type = type.mid(1);
else
changed = false;
}
if (guessChildrenFromType) {
switch (guessChildren(type)) {
case HasChildren:
setHasChildren(true);
break;
case HasNoChildren:
setHasChildren(false);
break;
case HasPossiblyChildren:
setHasChildren(true); // FIXME: bold assumption
break;
}
}
}
QString WatchData::toString() const
{
const char *doubleQuoteComma = "\",";
QString res;
QTextStream str(&res);
str << QLatin1Char('{');
if (!iname.isEmpty())
str << "iname=\"" << iname << doubleQuoteComma;
if (!name.isEmpty() && name != QLatin1String(iname))
str << "name=\"" << name << doubleQuoteComma;
if (address) {
str.setIntegerBase(16);
str << "addr=\"0x" << address << doubleQuoteComma;
str.setIntegerBase(10);
}
if (origaddr) {
str.setIntegerBase(16);
str << "referencingaddr=\"0x" << origaddr << doubleQuoteComma;
str.setIntegerBase(10);
}
if (!exp.isEmpty())
str << "exp=\"" << exp << doubleQuoteComma;
if (isValueNeeded())
str << "value=<needed>,";
if (!value.isEmpty())
str << "value=\"" << value << doubleQuoteComma;
if (elided)
str << "valueelided=\"" << elided << doubleQuoteComma;
if (!editvalue.isEmpty())
str << "editvalue=\"<...>\",";
str << "type=\"" << type << doubleQuoteComma;
str << "wantsChildren=\"" << (wantsChildren ? "true" : "false") << doubleQuoteComma;
if (isChildrenNeeded())
str << "children=<needed>,";
str.flush();
if (res.endsWith(QLatin1Char(',')))
res.truncate(res.size() - 1);
return res + QLatin1Char('}');
}
// Format a tooltip row with aligned colon.
static void formatToolTipRow(QTextStream &str, const QString &category, const QString &value)
{
QString val = value.toHtmlEscaped();
val.replace(QLatin1Char('\n'), QLatin1String("<br>"));
str << "<tr><td>" << category << "</td><td>";
if (!category.isEmpty())
str << ':';
str << "</td><td>" << val << "</td></tr>";
}
QString WatchData::toToolTip() const
{
QString res;
QTextStream str(&res);
str << "<html><body><table>";
formatToolTipRow(str, tr("Name"), name);
formatToolTipRow(str, tr("Expression"), QLatin1String(exp));
formatToolTipRow(str, tr("Internal Type"), QLatin1String(type));
if (!displayedType.isEmpty())
formatToolTipRow(str, tr("Displayed Type"), displayedType);
bool ok;
const quint64 intValue = value.toULongLong(&ok);
if (ok && intValue) {
formatToolTipRow(str, tr("Value"), QLatin1String("(dec) ") + value);
formatToolTipRow(str, QString(), QLatin1String("(hex) ") + QString::number(intValue, 16));
formatToolTipRow(str, QString(), QLatin1String("(oct) ") + QString::number(intValue, 8));
formatToolTipRow(str, QString(), QLatin1String("(bin) ") + QString::number(intValue, 2));
} else {
QString val = value;
if (val.size() > 1000) {
val.truncate(1000);
val += QLatin1Char(' ');
val += tr("... <cut off>");
}
formatToolTipRow(str, tr("Value"), val);
}
if (address)
formatToolTipRow(str, tr("Object Address"), formatToolTipAddress(address));
if (origaddr)
formatToolTipRow(str, tr("Pointer Address"), formatToolTipAddress(origaddr));
if (size)
formatToolTipRow(str, tr("Static Object Size"), tr("%n bytes", 0, size));
formatToolTipRow(str, tr("Internal ID"), QLatin1String(iname));
str << "</table></body></html>";
return res;
}
QString WatchData::msgNotInScope()
{
//: Value of variable in Debugger Locals display for variables out
//: of scope (stopped above initialization).
static const QString rc =
QCoreApplication::translate("Debugger::Internal::WatchData", "<not in scope>");
return rc;
}
const QString &WatchData::shadowedNameFormat()
{
//: Display of variables shadowed by variables of the same name
//: in nested scopes: Variable %1 is the variable name, %2 is a
//: simple count.
static const QString format =
QCoreApplication::translate("Debugger::Internal::WatchData", "%1 <shadowed %2>");
return format;
}
QString WatchData::shadowedName(const QString &name, int seen)
{
if (seen <= 0)
return name;
return shadowedNameFormat().arg(name).arg(seen);
}
QByteArray WatchData::hexAddress() const
{
if (address)
return QByteArray("0x") + QByteArray::number(address, 16);
return QByteArray();
}
////////////////////////////////////////////////////
//
// Protocol convienience
//
////////////////////////////////////////////////////
void WatchData::updateValue(const GdbMi &item)
{
GdbMi value = item["value"];
if (value.isValid()) {
DebuggerEncoding encoding = debuggerEncoding(item["valueencoded"].data());
setValue(decodeData(value.data(), encoding));
} else {
setValueNeeded();
}
}
void WatchData::updateChildCount(const GdbMi &mi)
{
if (mi.isValid())
setHasChildren(mi.toInt() > 0);
}
static void setWatchDataValueEnabled(WatchData &data, const GdbMi &mi)
{
if (mi.data() == "true")
data.valueEnabled = true;
else if (mi.data() == "false")
data.valueEnabled = false;
}
static void setWatchDataValueEditable(WatchData &data, const GdbMi &mi)
{
if (mi.data() == "true")
data.valueEditable = true;
else if (mi.data() == "false")
data.valueEditable = false;
}
static void setWatchDataAddress(WatchData &data, quint64 address)
{
data.address = address;
if (data.exp.isEmpty()) {
if (data.iname.startsWith("local.") && data.iname.count('.') == 1)
// Solve one common case of adding 'class' in
// *(class X*)0xdeadbeef for gdb.
data.exp = data.name.toLatin1();
else
data.exp = "*(" + gdbQuoteTypes(data.type) + "*)" + data.hexAddress();
}
}
static void setWatchDataSize(WatchData &data, const GdbMi &mi)
{
if (mi.isValid()) {
bool ok = false;
const unsigned size = mi.data().toUInt(&ok);
if (ok)
data.size = size;
}
}
// Find the "type" and "displayedtype" children of root and set up type.
void WatchData::updateType(const GdbMi &item)
{
if (item.isValid())
setType(item.data());
}
void WatchData::updateDisplayedType(const GdbMi &item)
{
if (item.isValid())
displayedType = QString::fromLatin1(item.data());
}
// Utilities to decode string data returned by the dumper helpers.
template <class T>
QString decodeItemHelper(const T &t)
{
return QString::number(t);
}
QString decodeItemHelper(const double &t)
{
return QString::number(t, 'g', 16);
}
template <class T>
void decodeArrayHelper(std::function<void(const WatchData &)> itemHandler, const WatchData &tmplate,
const QByteArray &rawData)
{
const QByteArray ba = QByteArray::fromHex(rawData);
const T *p = (const T *) ba.data();
WatchData data;
const QByteArray exp = "*(" + gdbQuoteTypes(tmplate.type) + "*)0x";
for (int i = 0, n = ba.size() / sizeof(T); i < n; ++i) {
data = tmplate;
data.iname += QByteArray::number(i);
data.name = QString::fromLatin1("[%1]").arg(i);
data.value = decodeItemHelper(p[i]);
data.address += i * sizeof(T);
data.exp = exp + QByteArray::number(data.address, 16);
data.setAllUnneeded();
itemHandler(data);
}
}
void decodeArrayData(std::function<void(const WatchData &)> itemHandler, const WatchData &tmplate,
const QByteArray &rawData, int encoding)
{
switch (encoding) {
case Hex2EncodedInt1:
decodeArrayHelper<signed char>(itemHandler, tmplate, rawData);
break;
case Hex2EncodedInt2:
decodeArrayHelper<short>(itemHandler, tmplate, rawData);
break;
case Hex2EncodedInt4:
decodeArrayHelper<int>(itemHandler, tmplate, rawData);
break;
case Hex2EncodedInt8:
decodeArrayHelper<qint64>(itemHandler, tmplate, rawData);
break;
case Hex2EncodedUInt1:
decodeArrayHelper<uchar>(itemHandler, tmplate, rawData);
break;
case Hex2EncodedUInt2:
decodeArrayHelper<ushort>(itemHandler, tmplate, rawData);
break;
case Hex2EncodedUInt4:
decodeArrayHelper<uint>(itemHandler, tmplate, rawData);
break;
case Hex2EncodedUInt8:
decodeArrayHelper<quint64>(itemHandler, tmplate, rawData);
break;
case Hex2EncodedFloat4:
decodeArrayHelper<float>(itemHandler, tmplate, rawData);
break;
case Hex2EncodedFloat8:
decodeArrayHelper<double>(itemHandler, tmplate, rawData);
break;
default:
qDebug() << "ENCODING ERROR: " << encoding;
}
}
void parseChildrenData(const WatchData &data0, const GdbMi &item,
std::function<void(const WatchData &)> itemHandler,
std::function<void(const WatchData &, const GdbMi &)> childHandler,
std::function<void(const WatchData &childTemplate, const QByteArray &encodedData, int encoding)> arrayDecoder)
{
WatchData data = data0;
data.setChildrenUnneeded();
GdbMi children = item["children"];
data.updateType(item["type"]);
data.editvalue = item["editvalue"].data();
data.editformat = DebuggerDisplay(item["editformat"].toInt());
data.editencoding = DebuggerEncoding(item["editencoding"].toInt());
GdbMi mi = item["valueelided"];
if (mi.isValid())
data.elided = mi.toInt();
mi = item["bitpos"];
if (mi.isValid())
data.bitpos = mi.toInt();
mi = item["bitsize"];
if (mi.isValid())
data.bitsize = mi.toInt();
mi = item["origaddr"];
if (mi.isValid())
data.origaddr = mi.toAddress();
mi = item["addr"];
if (mi.isValid())
setWatchDataAddress(data, mi.toAddress());
data.updateValue(item);
setWatchDataSize(data, item["size"]);
mi = item["exp"];
if (mi.isValid())
data.exp = mi.data();
setWatchDataValueEnabled(data, item["valueenabled"]);
setWatchDataValueEditable(data, item["valueeditable"]);
data.updateChildCount(item["numchild"]);
itemHandler(data);
bool ok = false;
qulonglong addressBase = item["addrbase"].data().toULongLong(&ok, 0);
qulonglong addressStep = item["addrstep"].data().toULongLong(&ok, 0);
// Try not to repeat data too often.
WatchData childtemplate;
childtemplate.updateType(item["childtype"]);
childtemplate.updateChildCount(item["childnumchild"]);
mi = item["arraydata"];
if (mi.isValid()) {
int encoding = item["arrayencoding"].toInt();
childtemplate.iname = data.iname + '.';
childtemplate.address = addressBase;
arrayDecoder(childtemplate, mi.data(), encoding);
} else {
for (int i = 0, n = int(children.children().size()); i != n; ++i) {
const GdbMi &child = children.children().at(i);
WatchData data1 = childtemplate;
GdbMi name = child["name"];
if (name.isValid())
data1.name = QString::fromLatin1(name.data());
else
data1.name = QString::number(i);
GdbMi iname = child["iname"];
if (iname.isValid()) {
data1.iname = iname.data();
} else {
data1.iname = data.iname;
data1.iname += '.';
data1.iname += data1.name.toLatin1();
}
if (!data1.name.isEmpty() && data1.name.at(0).isDigit())
data1.name = QLatin1Char('[') + data1.name + QLatin1Char(']');
if (addressStep) {
setWatchDataAddress(data1, addressBase);
addressBase += addressStep;
}
QByteArray key = child["key"].data();
if (!key.isEmpty()) {
int encoding = child["keyencoded"].toInt();
data1.name = decodeData(key, DebuggerEncoding(encoding));
}
childHandler(data1, child);
}
}
}
void parseWatchData(const WatchData &data0, const GdbMi &input,
QList<WatchData> *list)
{
auto itemHandler = [list](const WatchData &data) {
list->append(data);
};
auto childHandler = [list](const WatchData &innerData, const GdbMi &innerInput) {
parseWatchData(innerData, innerInput, list);
};
auto arrayDecoder = [itemHandler](const WatchData &childTemplate,
const QByteArray &encodedData, int encoding) {
decodeArrayData(itemHandler, childTemplate, encodedData, encoding);
};
parseChildrenData(data0, input, itemHandler, childHandler, arrayDecoder);
}
template <class T>
void readNumericVectorHelper(std::vector<double> *v, const QByteArray &ba)
{
const T *p = (const T *) ba.data();
const int n = ba.size() / sizeof(T);
v->resize(n);
// Losing precision in case of 64 bit ints is ok here, as the result
// is only used to plot data.
for (int i = 0; i != n; ++i)
(*v)[i] = static_cast<double>(p[i]);
}
void readNumericVector(std::vector<double> *v, const QByteArray &rawData, DebuggerEncoding encoding)
{
switch (encoding) {
case Hex2EncodedInt1:
readNumericVectorHelper<signed char>(v, rawData);
break;
case Hex2EncodedInt2:
readNumericVectorHelper<short>(v, rawData);
break;
case Hex2EncodedInt4:
readNumericVectorHelper<int>(v, rawData);
break;
case Hex2EncodedInt8:
readNumericVectorHelper<qint64>(v, rawData);
break;
case Hex2EncodedUInt1:
readNumericVectorHelper<uchar>(v, rawData);
break;
case Hex2EncodedUInt2:
readNumericVectorHelper<ushort>(v, rawData);
break;
case Hex2EncodedUInt4:
readNumericVectorHelper<uint>(v, rawData);
break;
case Hex2EncodedUInt8:
readNumericVectorHelper<quint64>(v, rawData);
break;
case Hex2EncodedFloat4:
readNumericVectorHelper<float>(v, rawData);
break;
case Hex2EncodedFloat8:
readNumericVectorHelper<double>(v, rawData);
break;
default:
qDebug() << "ENCODING ERROR: " << encoding;
}
}
} // namespace Internal
} // namespace Debugger