forked from qt-creator/qt-creator
The CDB Engine cannot report the hit count. Instead of showing "0", we don't show the hit count at all. Task-number: QTCREATORBUG-20069 Change-Id: Ie8b5478ba85c7255b13070cb488599a2b2ca4537 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: David Schulz <david.schulz@qt.io>
380 lines
12 KiB
C++
380 lines
12 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include "breakpoint.h"
|
|
|
|
#include "debuggerprotocol.h"
|
|
|
|
#include <projectexplorer/abi.h>
|
|
|
|
#include <utils/environment.h>
|
|
#include <utils/fileutils.h>
|
|
#include <utils/qtcassert.h>
|
|
#include <utils/stringutils.h>
|
|
|
|
#include <QDebug>
|
|
#include <QFileInfo>
|
|
#include <QDir>
|
|
|
|
namespace Debugger {
|
|
namespace Internal {
|
|
|
|
/*!
|
|
\class Debugger::Internal::BreakpointParameters
|
|
|
|
Data type holding the parameters of a breakpoint.
|
|
*/
|
|
|
|
BreakpointParameters::BreakpointParameters(BreakpointType t)
|
|
: type(t), enabled(true), pathUsage(BreakpointPathUsageEngineDefault),
|
|
ignoreCount(0), address(0), size(0),
|
|
bitpos(0), bitsize(0), threadSpec(-1),
|
|
tracepoint(false), oneShot(false)
|
|
{}
|
|
|
|
BreakpointParts BreakpointParameters::differencesTo
|
|
(const BreakpointParameters &rhs) const
|
|
{
|
|
BreakpointParts parts = BreakpointParts();
|
|
if (type != rhs.type)
|
|
parts |= TypePart;
|
|
if (enabled != rhs.enabled)
|
|
parts |= EnabledPart;
|
|
if (pathUsage != rhs.pathUsage)
|
|
parts |= PathUsagePart;
|
|
if (fileName != rhs.fileName)
|
|
parts |= FileAndLinePart;
|
|
if (!conditionsMatch(rhs.condition))
|
|
parts |= ConditionPart;
|
|
if (ignoreCount != rhs.ignoreCount)
|
|
parts |= IgnoreCountPart;
|
|
if (textPosition != rhs.textPosition)
|
|
parts |= FileAndLinePart;
|
|
if (address != rhs.address)
|
|
parts |= AddressPart;
|
|
if (threadSpec != rhs.threadSpec)
|
|
parts |= ThreadSpecPart;
|
|
if (functionName != rhs.functionName)
|
|
parts |= FunctionPart;
|
|
if (tracepoint != rhs.tracepoint)
|
|
parts |= TracePointPart;
|
|
if (module != rhs.module)
|
|
parts |= ModulePart;
|
|
if (command != rhs.command)
|
|
parts |= CommandPart;
|
|
if (message != rhs.message)
|
|
parts |= MessagePart;
|
|
if (oneShot != rhs.oneShot)
|
|
parts |= OneShotPart;
|
|
return parts;
|
|
}
|
|
|
|
bool BreakpointParameters::isValid() const
|
|
{
|
|
switch (type) {
|
|
case BreakpointByFileAndLine:
|
|
return !fileName.isEmpty() && textPosition.line > 0;
|
|
case BreakpointByFunction:
|
|
return !functionName.isEmpty();
|
|
case WatchpointAtAddress:
|
|
case BreakpointByAddress:
|
|
return address != 0;
|
|
case BreakpointAtThrow:
|
|
case BreakpointAtCatch:
|
|
case BreakpointAtMain:
|
|
case BreakpointAtFork:
|
|
case BreakpointAtExec:
|
|
case BreakpointAtSysCall:
|
|
case BreakpointOnQmlSignalEmit:
|
|
case BreakpointAtJavaScriptThrow:
|
|
break;
|
|
case WatchpointAtExpression:
|
|
return !expression.isEmpty();
|
|
case UnknownBreakpointType:
|
|
case LastBreakpointType:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BreakpointParameters::equals(const BreakpointParameters &rhs) const
|
|
{
|
|
return !differencesTo(rhs);
|
|
}
|
|
|
|
bool BreakpointParameters::conditionsMatch(const QString &other) const
|
|
{
|
|
// Some versions of gdb "beautify" the passed condition.
|
|
QString s1 = condition;
|
|
s1.replace(' ', "");
|
|
QString s2 = other;
|
|
s2.replace(' ', "");
|
|
return s1 == s2;
|
|
}
|
|
|
|
void BreakpointParameters::updateLocation(const QString &location)
|
|
{
|
|
if (!location.isEmpty()) {
|
|
int pos = location.indexOf(':');
|
|
textPosition.line = location.mid(pos + 1).toInt(); // FIXME: Handle column
|
|
QString file = location.left(pos);
|
|
if (file.startsWith('"') && file.endsWith('"'))
|
|
file = file.mid(1, file.size() - 2);
|
|
QFileInfo fi(file);
|
|
if (fi.isReadable())
|
|
fileName = Utils::FilePath::fromFileInfo(fi);
|
|
}
|
|
}
|
|
|
|
bool BreakpointParameters::isQmlFileAndLineBreakpoint() const
|
|
{
|
|
if (type != BreakpointByFileAndLine)
|
|
return false;
|
|
|
|
QString qmlExtensionString = Utils::qtcEnvironmentVariable("QTC_QMLDEBUGGER_FILEEXTENSIONS");
|
|
if (qmlExtensionString.isEmpty())
|
|
qmlExtensionString = ".qml;.js;.mjs";
|
|
|
|
const auto qmlFileExtensions = qmlExtensionString.split(';', Qt::SkipEmptyParts);
|
|
const QString file = fileName.toString();
|
|
for (const QString &extension : qmlFileExtensions) {
|
|
if (file.endsWith(extension, Qt::CaseInsensitive))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BreakpointParameters::isCppBreakpoint() const
|
|
{
|
|
// Qml specific breakpoint types.
|
|
if (type == BreakpointAtJavaScriptThrow || type == BreakpointOnQmlSignalEmit)
|
|
return false;
|
|
|
|
// Qml is currently only file.
|
|
if (type == BreakpointByFileAndLine)
|
|
return !isQmlFileAndLineBreakpoint();
|
|
|
|
return true;
|
|
}
|
|
|
|
QString BreakpointParameters::toString() const
|
|
{
|
|
QString result;
|
|
QTextStream ts(&result);
|
|
ts << "Type: " << type;
|
|
switch (type) {
|
|
case BreakpointByFileAndLine:
|
|
ts << " FileName: " << fileName << ':' << textPosition.line;
|
|
ts << " PathUsage: " << pathUsage;
|
|
break;
|
|
case BreakpointByFunction:
|
|
case BreakpointOnQmlSignalEmit:
|
|
ts << " FunctionName: " << functionName;
|
|
break;
|
|
case BreakpointByAddress:
|
|
case WatchpointAtAddress:
|
|
ts << " Address: " << address;
|
|
break;
|
|
case WatchpointAtExpression:
|
|
ts << " Expression: " << expression;
|
|
break;
|
|
case BreakpointAtThrow:
|
|
case BreakpointAtCatch:
|
|
case BreakpointAtMain:
|
|
case BreakpointAtFork:
|
|
case BreakpointAtExec:
|
|
case BreakpointAtSysCall:
|
|
case BreakpointAtJavaScriptThrow:
|
|
case UnknownBreakpointType:
|
|
case LastBreakpointType:
|
|
break;
|
|
}
|
|
ts << (enabled ? " [enabled]" : " [disabled]");
|
|
if (!condition.isEmpty())
|
|
ts << " Condition: " << condition;
|
|
if (ignoreCount)
|
|
ts << " IgnoreCount: " << ignoreCount;
|
|
if (tracepoint)
|
|
ts << " [tracepoint]";
|
|
if (!module.isEmpty())
|
|
ts << " Module: " << module;
|
|
if (!command.isEmpty())
|
|
ts << " Command: " << command;
|
|
if (!message.isEmpty())
|
|
ts << " Message: " << message;
|
|
|
|
if (pending)
|
|
ts << " [pending]";
|
|
if (!functionName.isEmpty())
|
|
ts << " Function: " << functionName;
|
|
if (hitCount)
|
|
ts << " Hit: " << *hitCount << " times";
|
|
ts << ' ';
|
|
|
|
return result;
|
|
}
|
|
|
|
static QString cleanupFullName(const QString &fileName)
|
|
{
|
|
QString cleanFilePath = fileName;
|
|
|
|
// Gdb running on windows often delivers "fullnames" which
|
|
// (a) have no drive letter and (b) are not normalized.
|
|
if (ProjectExplorer::Abi::hostAbi().os() == ProjectExplorer::Abi::WindowsOS) {
|
|
if (fileName.isEmpty())
|
|
return QString();
|
|
QFileInfo fi(fileName);
|
|
if (fi.isReadable())
|
|
cleanFilePath = QDir::cleanPath(fi.absoluteFilePath());
|
|
}
|
|
|
|
// if (!boolSetting(AutoEnrichParameters))
|
|
// return cleanFilePath;
|
|
|
|
// const QString sysroot = runParameters().sysRoot;
|
|
// if (QFileInfo(cleanFilePath).isReadable())
|
|
// return cleanFilePath;
|
|
// if (!sysroot.isEmpty() && fileName.startsWith('/')) {
|
|
// cleanFilePath = sysroot + fileName;
|
|
// if (QFileInfo(cleanFilePath).isReadable())
|
|
// return cleanFilePath;
|
|
// }
|
|
// if (m_baseNameToFullName.isEmpty()) {
|
|
// QString debugSource = sysroot + "/usr/src/debug";
|
|
// if (QFileInfo(debugSource).isDir()) {
|
|
// QDirIterator it(debugSource, QDirIterator::Subdirectories);
|
|
// while (it.hasNext()) {
|
|
// it.next();
|
|
// QString name = it.fileName();
|
|
// if (!name.startsWith('.')) {
|
|
// QString path = it.filePath();
|
|
// m_baseNameToFullName.insert(name, path);
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// cleanFilePath.clear();
|
|
// const QString base = FileName::fromString(fileName).fileName();
|
|
|
|
// QMap<QString, QString>::const_iterator jt = m_baseNameToFullName.constFind(base);
|
|
// while (jt != m_baseNameToFullName.constEnd() && jt.key() == base) {
|
|
// // FIXME: Use some heuristics to find the "best" match.
|
|
// return jt.value();
|
|
// //++jt;
|
|
// }
|
|
|
|
return cleanFilePath;
|
|
}
|
|
|
|
void BreakpointParameters::updateFromGdbOutput(const GdbMi &bkpt, const Utils::FilePath &fileRoot)
|
|
{
|
|
QTC_ASSERT(bkpt.isValid(), return);
|
|
|
|
QString originalLocation;
|
|
QString file;
|
|
QString fullName;
|
|
|
|
enabled = true;
|
|
pending = false;
|
|
condition.clear();
|
|
for (const GdbMi &child : bkpt) {
|
|
if (child.hasName("number")) {
|
|
// Handled on caller side.
|
|
} else if (child.hasName("func")) {
|
|
functionName = child.data();
|
|
} else if (child.hasName("addr")) {
|
|
// <MULTIPLE> happens in constructors, inline functions, and
|
|
// at other places like 'foreach' lines. In this case there are
|
|
// fields named "addr" in the response and/or the address
|
|
// is called <MULTIPLE>.
|
|
//qDebug() << "ADDR: " << child.data() << (child.data() == "<MULTIPLE>");
|
|
if (child.data().startsWith("0x"))
|
|
address = child.toAddress();
|
|
} else if (child.hasName("file")) {
|
|
file = child.data();
|
|
} else if (child.hasName("fullname")) {
|
|
fullName = child.data();
|
|
} else if (child.hasName("line")) {
|
|
textPosition.line = child.toInt();
|
|
} else if (child.hasName("cond")) {
|
|
// gdb 6.3 likes to "rewrite" conditions. Just accept that fact.
|
|
condition = child.data();
|
|
} else if (child.hasName("enabled")) {
|
|
enabled = (child.data() == "y");
|
|
} else if (child.hasName("disp")) {
|
|
oneShot = child.data() == "del";
|
|
} else if (child.hasName("pending")) {
|
|
// Any content here would be interesting only if we did accept
|
|
// spontaneously appearing breakpoints (user using gdb commands).
|
|
if (file.isEmpty())
|
|
file = child.data();
|
|
pending = true;
|
|
} else if (child.hasName("at")) {
|
|
// Happens with gdb 6.4 symbianelf.
|
|
QString ba = child.data();
|
|
if (ba.startsWith('<') && ba.endsWith('>'))
|
|
ba = ba.mid(1, ba.size() - 2);
|
|
functionName = ba;
|
|
} else if (child.hasName("thread")) {
|
|
threadSpec = child.toInt();
|
|
} else if (child.hasName("type")) {
|
|
// "breakpoint", "hw breakpoint", "tracepoint", "hw watchpoint"
|
|
// {bkpt={number="2",type="hw watchpoint",disp="keep",enabled="y",
|
|
// what="*0xbfffed48",times="0",original-location="*0xbfffed48"}}
|
|
if (child.data().contains("tracepoint")) {
|
|
tracepoint = true;
|
|
} else if (child.data() == "hw watchpoint" || child.data() == "watchpoint") {
|
|
QString what = bkpt["what"].data();
|
|
if (what.startsWith("*0x")) {
|
|
type = WatchpointAtAddress;
|
|
address = what.mid(1).toULongLong(nullptr, 0);
|
|
} else {
|
|
type = WatchpointAtExpression;
|
|
expression = what;
|
|
}
|
|
} else if (child.data() == "breakpoint") {
|
|
QString catchType = bkpt["catch-type"].data();
|
|
if (catchType == "throw")
|
|
type = BreakpointAtThrow;
|
|
else if (catchType == "catch")
|
|
type = BreakpointAtCatch;
|
|
else if (catchType == "fork")
|
|
type = BreakpointAtFork;
|
|
else if (catchType == "exec")
|
|
type = BreakpointAtExec;
|
|
else if (catchType == "syscall")
|
|
type = BreakpointAtSysCall;
|
|
}
|
|
} else if (child.hasName("times")) {
|
|
hitCount = child.toInt();
|
|
} else if (child.hasName("original-location")) {
|
|
originalLocation = child.data();
|
|
}
|
|
// This field is not present. Contents needs to be parsed from
|
|
// the plain "ignore"
|
|
//else if (child.hasName("ignore"))
|
|
// ignoreCount = child.data();
|
|
}
|
|
|
|
QString name;
|
|
if (!fullName.isEmpty()) {
|
|
name = cleanupFullName(fullName);
|
|
fileName = fileRoot.withNewPath(name);
|
|
//if (data->markerFileName().isEmpty())
|
|
// data->setMarkerFileName(name);
|
|
} else {
|
|
name = file;
|
|
// Use fullName() once we have a mapping which is more complete than
|
|
// gdb's own. No point in assigning markerFileName for now.
|
|
}
|
|
if (!name.isEmpty())
|
|
fileName = fileRoot.withNewPath(name);
|
|
|
|
if (fileName.isEmpty())
|
|
updateLocation(originalLocation);
|
|
}
|
|
|
|
} // namespace Internal
|
|
} // namespace Debugger
|