forked from qt-creator/qt-creator
Add gdb tracepoint support for Linux
Change-Id: Id2e46bae576a730f8c1b64a247aeed12e6d721af Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
committed by
Gustav Johansson
parent
874029809f
commit
2081038953
@@ -942,7 +942,7 @@ class DumperBase():
|
||||
for (k, v) in list(value.items())]) + '}'
|
||||
if isinstance(value, list):
|
||||
return '[' + ','.join([self.resultToMi(k)
|
||||
for k in list(value.items())]) + ']'
|
||||
for k in value]) + ']'
|
||||
return '"%s"' % value
|
||||
|
||||
def variablesToMi(self, value, prefix):
|
||||
|
@@ -38,6 +38,8 @@ import tempfile
|
||||
|
||||
from dumper import DumperBase, Children, toInteger, TopLevelItem
|
||||
from utils import TypeCode
|
||||
from gdbtracepoint import *
|
||||
|
||||
|
||||
#######################################################################
|
||||
#
|
||||
@@ -1466,7 +1468,55 @@ class Dumper(DumperBase):
|
||||
print(timeit.repeat('theDumper.fetchVariables(%s)' % args,
|
||||
'from __main__ import theDumper', number=10))
|
||||
|
||||
def tracepointModified(self, tp):
|
||||
self.tpExpressions = {}
|
||||
self.tpExpressionWarnings = []
|
||||
s = self.resultToMi(tp.dicts())
|
||||
def handler():
|
||||
print("tracepointmodified=%s" % s)
|
||||
gdb.post_event(handler)
|
||||
|
||||
def tracepointHit(self, tp, result):
|
||||
expressions = '{' + ','.join(["%s=%s" % (k,v) for k,v in self.tpExpressions.items()]) + '}'
|
||||
warnings = []
|
||||
if 'warning' in result.keys():
|
||||
warnings.append(result.pop('warning'))
|
||||
warnings += self.tpExpressionWarnings
|
||||
r = self.resultToMi(result)
|
||||
w = self.resultToMi(warnings)
|
||||
def handler():
|
||||
print("tracepointhit={result=%s,expressions=%s,warnings=%s}" % (r, expressions, w))
|
||||
gdb.post_event(handler)
|
||||
|
||||
def tracepointExpression(self, tp, expression, value, args):
|
||||
key = "x" + str(len(self.tpExpressions))
|
||||
if (isinstance(value, gdb.Value)):
|
||||
try:
|
||||
val = self.fromNativeValue(value)
|
||||
self.prepare(args)
|
||||
with TopLevelItem(self, expression):
|
||||
self.putItem(val)
|
||||
self.tpExpressions[key] = self.output
|
||||
except Exception as e:
|
||||
self.tpExpressions[key] = '"<N/A>"'
|
||||
self.tpExpressionWarnings.append(str(e))
|
||||
elif (isinstance(value, Exception)):
|
||||
self.tpExpressions[key] = '"<N/A>"'
|
||||
self.tpExpressionWarnings.append(str(value))
|
||||
else:
|
||||
self.tpExpressions[key] = '"<N/A>"'
|
||||
self.tpExpressionWarnings.append('Unknown expression value type')
|
||||
return key
|
||||
|
||||
def createTracepoint(self, args):
|
||||
"""
|
||||
Creates a tracepoint
|
||||
"""
|
||||
tp = GDBTracepoint.create(args,
|
||||
onModified=self.tracepointModified,
|
||||
onHit=self.tracepointHit,
|
||||
onExpression=lambda tp, expr, val: self.tracepointExpression(tp, expr, val, args))
|
||||
self.reportResult("tracepoint=%s" % self.resultToMi(tp.dicts()), args)
|
||||
class CliDumper(Dumper):
|
||||
def __init__(self):
|
||||
Dumper.__init__(self)
|
||||
|
399
share/qtcreator/debugger/gdbtracepoint.py
Normal file
399
share/qtcreator/debugger/gdbtracepoint.py
Normal file
@@ -0,0 +1,399 @@
|
||||
############################################################################
|
||||
#
|
||||
# Copyright (C) 2021 The Qt Company Ltd.
|
||||
# Contact: https://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 https://www.qt.io/terms-conditions. For further
|
||||
# information use the contact form at https://www.qt.io/contact-us.
|
||||
#
|
||||
# GNU General Public License Usage
|
||||
# Alternatively, this file may be used under the terms of the GNU
|
||||
# General Public License version 3 as published by the Free Software
|
||||
# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
# included in the packaging of this file. Please review the following
|
||||
# information to ensure the GNU General Public License requirements will
|
||||
# be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
import gdb
|
||||
import sys
|
||||
import time
|
||||
|
||||
# for ProcessName capture
|
||||
try:
|
||||
import psutil
|
||||
except:
|
||||
psutil = None
|
||||
|
||||
# Caps types
|
||||
Address, \
|
||||
Caller, \
|
||||
Callstack, \
|
||||
FilePos, \
|
||||
Function, \
|
||||
Pid, \
|
||||
ProcessName, \
|
||||
Tick, \
|
||||
Tid, \
|
||||
ThreadName, \
|
||||
Expression, \
|
||||
= range(0, 11)
|
||||
|
||||
class GDBTracepoint(gdb.Breakpoint):
|
||||
"""
|
||||
Python Breakpoint extension for "tracepoints", breakpoints that do not stop the inferior
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def create(args, onModified, onHit, onExpression):
|
||||
"""
|
||||
Static creator function
|
||||
"""
|
||||
tp_kwargs = {}
|
||||
if 'temporary' in args.keys():
|
||||
tp_kwargs['temporary'] = args['temporary']
|
||||
spec = args['spec']
|
||||
tp = GDBTracepoint(spec, **tp_kwargs)
|
||||
tp.onModified = onModified
|
||||
tp.onHit = onHit
|
||||
tp.onExpression = onExpression
|
||||
if 'ignore_count' in args.keys():
|
||||
tp.ignore_count = args['ignore_count']
|
||||
if 'enabled' in args.keys():
|
||||
tp.enabled = args['enabled']
|
||||
if 'thread' in args.keys():
|
||||
tp.thread = args['thread']
|
||||
if 'condition' in args.keys():
|
||||
tp.condition = args['condition']
|
||||
if 'caps' in args.keys():
|
||||
for ce in args['caps']:
|
||||
tp.addCaps(ce[0], ce[1])
|
||||
return tp
|
||||
|
||||
def __init__(self, spec, **kwargs):
|
||||
"""
|
||||
Constructor
|
||||
"""
|
||||
kwargs['internal'] = True
|
||||
super(GDBTracepoint, self).__init__(spec, **kwargs)
|
||||
self.caps = []
|
||||
|
||||
_hexSize = 8 if sys.maxsize > 2**32 else 4
|
||||
_hasMonotonicTime = False if sys.version_info[0] <= 2 or (sys.version_info[0] == 3 and sys.version_info[1] < 3) else True
|
||||
|
||||
def dicts(self):
|
||||
"""
|
||||
Returns dictionareis for mi representation
|
||||
"""
|
||||
results = []
|
||||
result = {}
|
||||
result['number'] = str(self.number)
|
||||
result['enabled'] = 'y' if self.enabled else 'n'
|
||||
result['type'] = 'pseudo_tracepoint'
|
||||
result['disp'] = 'del' if self.temporary else 'keep'
|
||||
result['times'] = str(self.hit_count)
|
||||
result['original-location'] = self.location
|
||||
try:
|
||||
d = gdb.decode_line(self.location)
|
||||
if d[1] is None:
|
||||
result['addr'] = '<PENDING>'
|
||||
result['pending'] = self.location
|
||||
results.append(result)
|
||||
else:
|
||||
if len(d[1]) > 1:
|
||||
result['addr'] = '<MULTIPLE>'
|
||||
results.append(result)
|
||||
for i, sl in enumerate(d[1]):
|
||||
result_ = {}
|
||||
result_['number'] = result['number'] + "." + str(i + 1)
|
||||
result_['enabled'] = 'y' if self.enabled else 'n'
|
||||
if sl.pc is None:
|
||||
result_['addr'] = '<no address>'
|
||||
else:
|
||||
result_['addr'] = '{0:#0{1}x}'.format(sl.pc, self._hexSize + 2)
|
||||
if sl.symtab and sl.symtab.is_valid():
|
||||
func = self._getFunctionFromAddr(sl.pc)
|
||||
if func:
|
||||
result_['func'] = func.print_name
|
||||
result_['file'] = sl.symtab.filename
|
||||
result_['fullname'] = sl.symtab.fullname()
|
||||
result_['line'] = sl.line
|
||||
results.append(result_)
|
||||
else:
|
||||
sl = d[1][0]
|
||||
if sl.pc is None:
|
||||
result['addr'] = '<no address>'
|
||||
else:
|
||||
result['addr'] = '{0:#0{1}x}'.format(sl.pc, self._hexSize + 2)
|
||||
if sl.symtab and sl.symtab.is_valid():
|
||||
func = self._getFunctionFromAddr(sl.pc)
|
||||
if func:
|
||||
result['func'] = func.print_name
|
||||
result['file'] = sl.symtab.filename
|
||||
result['fullname'] = sl.symtab.fullname()
|
||||
result['line'] = sl.line
|
||||
results.append(result)
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
result['addr'] = '<PENDING>'
|
||||
result['pending'] = self.location
|
||||
results.append(result)
|
||||
return results
|
||||
|
||||
def addCaps(self, capsType, expression=None):
|
||||
"""
|
||||
Adds capture expressions for a tracepoint
|
||||
|
||||
:param caps_type: Type of capture
|
||||
:param expression: Expression for Expression caps type
|
||||
"""
|
||||
if capsType != Expression:
|
||||
expression = None
|
||||
else:
|
||||
if expression is None:
|
||||
expression = ''
|
||||
self.caps.append((self.capsMap[capsType], expression))
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Overridden stop function, this evaluates conditions and captures data from the inferior
|
||||
|
||||
:return: Always False
|
||||
"""
|
||||
try:
|
||||
self.onModified(self)
|
||||
result = {}
|
||||
result['number'] = self.number
|
||||
try:
|
||||
if self.condition:
|
||||
try:
|
||||
result = gdb.parse_and_eval(self.condition)
|
||||
if result.type.code == gdb.TYPE_CODE_BOOL and str(result) == 'false':
|
||||
return False
|
||||
except:
|
||||
pass
|
||||
if self.ignore_count > 0:
|
||||
return False
|
||||
if self.thread and gdb.selected_thread().global_num != self.thread:
|
||||
return False
|
||||
except Exception as e:
|
||||
result['warning'] = str(e)
|
||||
self.onHit(self, result)
|
||||
return False
|
||||
if len(self.caps) > 0:
|
||||
caps = []
|
||||
try:
|
||||
for func, expr in self.caps:
|
||||
if expr is None:
|
||||
caps.append(func(self))
|
||||
else:
|
||||
caps.append(func(self, expr))
|
||||
except Exception as e:
|
||||
result['warning'] = str(e)
|
||||
self.onHit(self, result)
|
||||
return False
|
||||
result['caps'] = caps
|
||||
self.onHit(self, result)
|
||||
return False
|
||||
except:
|
||||
# Always return false, regardless...
|
||||
return False
|
||||
|
||||
def _getFunctionFromAddr(self, addr):
|
||||
try:
|
||||
block = gdb.block_for_pc(addr)
|
||||
while block and not block.function:
|
||||
block = block.superblock
|
||||
if block is None:
|
||||
return None
|
||||
return block.function
|
||||
except:
|
||||
return None
|
||||
|
||||
def _getAddress(self):
|
||||
"""
|
||||
Capture function for Address
|
||||
"""
|
||||
try:
|
||||
frame = gdb.selected_frame()
|
||||
if not (frame is None) and (frame.is_valid()):
|
||||
return '{0:#0{1}x}'.format(frame.pc(), self._hexSize + 2)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return '<null address>'
|
||||
|
||||
def _getCaller(self):
|
||||
"""
|
||||
Capture function for Caller
|
||||
"""
|
||||
try:
|
||||
frame = gdb.selected_frame()
|
||||
if not (frame is None) and (frame.is_valid()):
|
||||
frame = frame.older()
|
||||
if not (frame is None) and (frame.is_valid()):
|
||||
name = frame.name()
|
||||
if name is None:
|
||||
return '<unknown caller>'
|
||||
return name
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return '<unknown caller>'
|
||||
|
||||
def _getCallstack(self):
|
||||
"""
|
||||
Capture function for Callstack
|
||||
"""
|
||||
try:
|
||||
frames = []
|
||||
frame = gdb.selected_frame()
|
||||
if (frame is None) or (not frame.is_valid()):
|
||||
frames.append('<unknown frame>')
|
||||
return str(frames)
|
||||
while not (frame is None):
|
||||
func = frame.function()
|
||||
if func is None:
|
||||
frames.append('{0:#0{1}x}'.format(frame.pc(), self._hexSize + 2))
|
||||
else:
|
||||
sl = frame.find_sal()
|
||||
if sl is None:
|
||||
frames.append(func.symtab.filename)
|
||||
else:
|
||||
frames.append(func.symtab.filename + ':' + str(sl.line))
|
||||
frame = frame.older()
|
||||
return frames
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
def _getFilePos(self):
|
||||
"""
|
||||
Capture function for FilePos
|
||||
"""
|
||||
try:
|
||||
frame = gdb.selected_frame()
|
||||
if (frame is None) or (not frame.is_valid()):
|
||||
return '<unknown file pos>'
|
||||
sl = frame.find_sal()
|
||||
if sl is None:
|
||||
return '<unknown file pos>'
|
||||
return sl.symtab.filename + ':' + str(sl.line)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
def _getFunction(self):
|
||||
"""
|
||||
Capture function for Function
|
||||
"""
|
||||
try:
|
||||
frame = gdb.selected_frame()
|
||||
if not (frame is None):
|
||||
return str(frame.name())
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return '<unknown function>'
|
||||
|
||||
def _getPid(self):
|
||||
"""
|
||||
Capture function for Pid
|
||||
"""
|
||||
try:
|
||||
thread = gdb.selected_thread()
|
||||
if not (thread is None):
|
||||
(pid, lwpid, tid) = thread.ptid
|
||||
return str(pid)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return '<unknown pid>'
|
||||
|
||||
def _getProcessName(slef):
|
||||
"""
|
||||
Capture for ProcessName
|
||||
"""
|
||||
# gdb does not expose process name, neither does (standard) python
|
||||
# You can use for example psutil, but it might not be present.
|
||||
# Default to name of thread with ID 1
|
||||
inf = gdb.selected_inferior()
|
||||
if psutil is None:
|
||||
try:
|
||||
if inf is None:
|
||||
return '<unknown process name>'
|
||||
threads = filter(lambda t: t.num == 1, list(inf.threads()))
|
||||
if len(threads) < 1:
|
||||
return '<unknown process name>'
|
||||
thread = threads[0]
|
||||
# use thread name
|
||||
return thread.name
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
else:
|
||||
return psutil.Process(inf.pid).name()
|
||||
|
||||
def _getTick(self):
|
||||
"""
|
||||
Capture function for Tick
|
||||
"""
|
||||
if self._hasMonotonicTime:
|
||||
return str(int(time.monotonic() * 1000))
|
||||
else:
|
||||
return '<monotonic time not available>'
|
||||
|
||||
def _getTid(self):
|
||||
"""
|
||||
Capture function for Tid
|
||||
"""
|
||||
try:
|
||||
thread = gdb.selected_thread()
|
||||
if not (thread is None):
|
||||
(pid, lwpid, tid) = thread.ptid
|
||||
if tid == 0:
|
||||
return str(lwpid)
|
||||
else:
|
||||
return str(tid)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return '<unknown tid>'
|
||||
|
||||
def _getThreadName(self):
|
||||
"""
|
||||
Capture function for ThreadName
|
||||
"""
|
||||
try:
|
||||
thread = gdb.selected_thread()
|
||||
if not (thread is None):
|
||||
return str(thread.name)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return '<unknown thread name>'
|
||||
|
||||
def _getExpression(self, expression):
|
||||
"""
|
||||
Capture function for Expression
|
||||
|
||||
:param expr: The expression to evaluate
|
||||
"""
|
||||
try:
|
||||
value = gdb.parse_and_eval(expression)
|
||||
if value:
|
||||
return self.onExpression(self, expression, value)
|
||||
except Exception as e:
|
||||
return self.onExpression(self, expression, e)
|
||||
|
||||
capsMap = {Address: _getAddress,
|
||||
Caller: _getCaller,
|
||||
Callstack: _getCallstack,
|
||||
FilePos: _getFilePos,
|
||||
Function: _getFunction,
|
||||
Pid: _getPid,
|
||||
ProcessName: _getProcessName,
|
||||
Tid: _getTid,
|
||||
Tick: _getTick,
|
||||
ThreadName: _getThreadName,
|
||||
Expression: _getExpression}
|
@@ -611,17 +611,13 @@ void BreakpointDialog::setPartsEnabled(unsigned partsMask)
|
||||
m_lineEditModule->setEnabled(partsMask & ModulePart);
|
||||
|
||||
m_labelTracepoint->setEnabled(partsMask & TracePointPart);
|
||||
m_labelTracepoint->hide();
|
||||
m_checkBoxTracepoint->setEnabled(partsMask & TracePointPart);
|
||||
m_checkBoxTracepoint->hide();
|
||||
|
||||
m_labelCommands->setEnabled(partsMask & CommandPart);
|
||||
m_textEditCommands->setEnabled(partsMask & CommandPart);
|
||||
|
||||
m_labelMessage->setEnabled(partsMask & TracePointPart);
|
||||
m_labelMessage->hide();
|
||||
m_lineEditMessage->setEnabled(partsMask & TracePointPart);
|
||||
m_lineEditMessage->hide();
|
||||
}
|
||||
|
||||
void BreakpointDialog::clearOtherParts(unsigned partsMask)
|
||||
@@ -2106,6 +2102,8 @@ QString BreakpointItem::msgBreakpointTriggered(const QString &threadId) const
|
||||
QVariant SubBreakpointItem::data(int column, int role) const
|
||||
{
|
||||
if (role == Qt::DecorationRole && column == 0) {
|
||||
if (params.tracepoint)
|
||||
return Icons::TRACEPOINT.icon();
|
||||
return params.enabled ? Icons::BREAKPOINT.icon()
|
||||
: Icons::BREAKPOINT_DISABLED.icon();
|
||||
}
|
||||
|
@@ -176,6 +176,7 @@ public:
|
||||
void setIgnoreCount(int count) { m_parameters.ignoreCount = count; }
|
||||
void setCommand(const QString &command) { m_parameters.command = command; }
|
||||
void setCondition(const QString &condition) { m_parameters.condition = condition; }
|
||||
void setMessage(const QString& message) { m_parameters.message = message; }
|
||||
|
||||
QString msgWatchpointByAddressTriggered(quint64 address) const;
|
||||
QString msgWatchpointByAddressTriggered(quint64 address, const QString &threadId) const;
|
||||
|
@@ -506,6 +506,12 @@ DebuggerSettings::DebuggerSettings()
|
||||
item->setDefaultValue(true);
|
||||
insertItem(UseAnnotationsInMainEditor, item);
|
||||
|
||||
item = new SavedAction;
|
||||
item->setSettingsKey(debugModeGroup, "UsePseudoTracepoints");
|
||||
item->setCheckable(true);
|
||||
item->setDefaultValue(true);
|
||||
insertItem(UsePseudoTracepoints, item);
|
||||
|
||||
item = new SavedAction;
|
||||
item->setSettingsKey(debugModeGroup, "UseToolTips");
|
||||
item->setText(tr("Use tooltips in main editor when debugging"));
|
||||
|
@@ -134,6 +134,7 @@ enum DebuggerActionCode
|
||||
WarnOnReleaseBuilds,
|
||||
MultiInferior,
|
||||
IntelFlavor,
|
||||
UsePseudoTracepoints,
|
||||
|
||||
// Stack
|
||||
MaximalStackDepth,
|
||||
|
@@ -115,6 +115,31 @@ static QMessageBox *showMessageBox(QMessageBox::Icon icon,
|
||||
return mb;
|
||||
}
|
||||
|
||||
enum class TracepointCaptureType
|
||||
{
|
||||
Address,
|
||||
Caller,
|
||||
Callstack,
|
||||
FilePos,
|
||||
Function,
|
||||
Pid,
|
||||
ProcessName,
|
||||
Tick,
|
||||
Tid,
|
||||
ThreadName,
|
||||
Expression
|
||||
};
|
||||
|
||||
struct TracepointCaptureData
|
||||
{
|
||||
TracepointCaptureType type;
|
||||
QVariant expression;
|
||||
int start;
|
||||
int end;
|
||||
};
|
||||
|
||||
const char tracepointCapturePropertyName[] = "GDB.TracepointCapture";
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// GdbEngine
|
||||
@@ -307,6 +332,18 @@ void GdbEngine::handleResponse(const QString &buff)
|
||||
handleInterpreterBreakpointModified(allData["interpreterasync"]);
|
||||
break;
|
||||
}
|
||||
if (data.startsWith("tracepointhit={")) {
|
||||
GdbMi allData;
|
||||
allData.fromStringMultiple(data);
|
||||
handleTracepointHit(allData["tracepointhit"]);
|
||||
break;
|
||||
}
|
||||
if (data.startsWith("tracepointmodified=")) {
|
||||
GdbMi allData;
|
||||
allData.fromStringMultiple(data);
|
||||
handleTracepointModified(allData["tracepointmodified"]);
|
||||
break;
|
||||
}
|
||||
m_pendingConsoleStreamOutput += data;
|
||||
|
||||
// Fragile, but it's all we have.
|
||||
@@ -2162,6 +2199,7 @@ void GdbEngine::handleCatchInsert(const DebuggerResponse &response, const Breakp
|
||||
void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp)
|
||||
{
|
||||
QTC_ASSERT(bp, return);
|
||||
bool usePseudoTracepoints = boolSetting(UsePseudoTracepoints);
|
||||
const QString nr = bkpt["number"].data();
|
||||
if (nr.contains('.')) {
|
||||
// A sub-breakpoint.
|
||||
@@ -2169,6 +2207,10 @@ void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp)
|
||||
QTC_ASSERT(sub, return);
|
||||
sub->params.updateFromGdbOutput(bkpt);
|
||||
sub->params.type = bp->type();
|
||||
if (usePseudoTracepoints && bp->isTracepoint()) {
|
||||
sub->params.tracepoint = true;
|
||||
sub->params.message = bp->message();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2183,12 +2225,18 @@ void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp)
|
||||
QTC_ASSERT(sub, return);
|
||||
sub->params.updateFromGdbOutput(location);
|
||||
sub->params.type = bp->type();
|
||||
if (usePseudoTracepoints && bp->isTracepoint()) {
|
||||
sub->params.tracepoint = true;
|
||||
sub->params.message = bp->message();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A (the?) primary breakpoint.
|
||||
bp->setResponseId(nr);
|
||||
bp->updateFromGdbOutput(bkpt);
|
||||
if (usePseudoTracepoints && bp->isTracepoint())
|
||||
bp->setMessage(bp->requestedParameters().message);
|
||||
}
|
||||
|
||||
void GdbEngine::handleBreakInsert1(const DebuggerResponse &response, const Breakpoint &bp)
|
||||
@@ -2334,6 +2382,176 @@ void GdbEngine::handleBreakCondition(const DebuggerResponse &, const Breakpoint
|
||||
updateBreakpoint(bp); // Maybe there's more to do.
|
||||
}
|
||||
|
||||
void GdbEngine::updateTracepointCaptures(const Breakpoint &bp)
|
||||
{
|
||||
static QRegularExpression capsRegExp(
|
||||
"(^|[^\\\\])(\\$(ADDRESS|CALLER|CALLSTACK|FILEPOS|FUNCTION|PID|PNAME|TICK|TID|TNAME)"
|
||||
"|{[^}]+})");
|
||||
QString message = bp->globalBreakpoint()->requestedParameters().message;
|
||||
if (message.isEmpty()) {
|
||||
bp->setProperty(tracepointCapturePropertyName, {});
|
||||
return;
|
||||
}
|
||||
QVariantList caps;
|
||||
QRegularExpressionMatch match = capsRegExp.match(message, 0);
|
||||
while (match.hasMatch()) {
|
||||
QString t = match.captured(2);
|
||||
if (t[0] == '$') {
|
||||
TracepointCaptureType type;
|
||||
if (t == "$ADDRESS")
|
||||
type = TracepointCaptureType::Address;
|
||||
else if (t == "$CALLER")
|
||||
type = TracepointCaptureType::Caller;
|
||||
else if (t == "$CALLSTACK")
|
||||
type = TracepointCaptureType::Callstack;
|
||||
else if (t == "$FILEPOS")
|
||||
type = TracepointCaptureType::FilePos;
|
||||
else if (t == "$FUNCTION")
|
||||
type = TracepointCaptureType::Function;
|
||||
else if (t == "$PID")
|
||||
type = TracepointCaptureType::Pid;
|
||||
else if (t == "$PNAME")
|
||||
type = TracepointCaptureType::ProcessName;
|
||||
else if (t == "$TICK")
|
||||
type = TracepointCaptureType::Tick;
|
||||
else if (t == "$TID")
|
||||
type = TracepointCaptureType::Tid;
|
||||
else if (t == "$TNAME")
|
||||
type = TracepointCaptureType::ThreadName;
|
||||
else
|
||||
QTC_ASSERT(false, continue);
|
||||
caps << QVariant::fromValue<TracepointCaptureData>(
|
||||
{type, {}, match.capturedStart(2), match.capturedEnd(2)});
|
||||
} else {
|
||||
QString expression = t.mid(1, t.length() - 2);
|
||||
caps << QVariant::fromValue<TracepointCaptureData>(
|
||||
{TracepointCaptureType::Expression, expression, match.capturedStart(2), match.capturedEnd(2)});
|
||||
}
|
||||
match = capsRegExp.match(message, match.capturedEnd());
|
||||
}
|
||||
bp->setProperty(tracepointCapturePropertyName, caps);
|
||||
}
|
||||
|
||||
void GdbEngine::handleTracepointInsert(const DebuggerResponse &response, const Breakpoint &bp)
|
||||
{
|
||||
QTC_ASSERT(bp, return);
|
||||
if (bp->state() == BreakpointRemoveRequested) {
|
||||
if (response.resultClass == ResultDone) {
|
||||
// This delete was deferred. Act now.
|
||||
const GdbMi mainbkpt = response.data["tracepoint"][0];
|
||||
notifyBreakpointRemoveProceeding(bp);
|
||||
DebuggerCommand cmd("-break-delete " + mainbkpt["number"].data());
|
||||
cmd.flags = NeedsTemporaryStop;
|
||||
runCommand(cmd);
|
||||
notifyBreakpointRemoveOk(bp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (response.resultClass == ResultDone) {
|
||||
for (const GdbMi &bkpt : response.data["tracepoint"])
|
||||
handleBkpt(bkpt, bp);
|
||||
if (bp->needsChange()) {
|
||||
bp->gotoState(BreakpointUpdateRequested, BreakpointInsertionProceeding);
|
||||
updateBreakpoint(bp);
|
||||
} else {
|
||||
notifyBreakpointInsertOk(bp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GdbEngine::handleTracepointHit(const GdbMi &data)
|
||||
{
|
||||
const GdbMi &result = data["result"];
|
||||
const QString rid = result["number"].data();
|
||||
Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid);
|
||||
QTC_ASSERT(bp, return);
|
||||
const GdbMi &warnings = data["warnings"];
|
||||
if (warnings.childCount() > 0) {
|
||||
for (const GdbMi &warning: warnings) {
|
||||
emit appendMessageRequested(warning.toString(), ErrorMessageFormat, true);
|
||||
}
|
||||
}
|
||||
QString message = bp->message();
|
||||
QVariant caps = bp->property(tracepointCapturePropertyName);
|
||||
if (caps.isValid()) {
|
||||
QList<QVariant> capsList = caps.toList();
|
||||
const GdbMi &miCaps = result["caps"];
|
||||
if (capsList.length() == miCaps.childCount()) {
|
||||
// reverse iterate to make start/end correct
|
||||
for (int i = capsList.length() - 1; i >= 0; --i) {
|
||||
TracepointCaptureData cap = capsList.at(i).value<TracepointCaptureData>();
|
||||
const GdbMi &miCap = miCaps.childAt(i);
|
||||
switch (cap.type) {
|
||||
case TracepointCaptureType::Callstack: {
|
||||
QStringList frames;
|
||||
for (const GdbMi &frame: miCap) {
|
||||
frames.append(frame.data());
|
||||
}
|
||||
message.replace(cap.start, cap.end - cap.start, frames.join(" <- "));
|
||||
break;
|
||||
}
|
||||
case TracepointCaptureType::Expression: {
|
||||
QString key = miCap.data();
|
||||
const GdbMi &expression = data["expressions"][key.toLatin1().data()];
|
||||
if (expression.isValid()) {
|
||||
QString s = expression.toString();
|
||||
// remove '<key>='
|
||||
s = s.right(s.length() - key.length() - 1);
|
||||
message.replace(cap.start, cap.end - cap.start, s);
|
||||
} else {
|
||||
QTC_CHECK(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
message.replace(cap.start, cap.end - cap.start, miCap.data());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QTC_CHECK(false);
|
||||
}
|
||||
}
|
||||
showMessage(message);
|
||||
emit appendMessageRequested(message, NormalMessageFormat, true);
|
||||
}
|
||||
|
||||
void GdbEngine::handleTracepointModified(const GdbMi &data)
|
||||
{
|
||||
QString ba = data.toString();
|
||||
// remove original-location
|
||||
const int pos1 = ba.indexOf("original-location=");
|
||||
const int pos2 = ba.indexOf(":", pos1 + 17);
|
||||
int pos3 = ba.indexOf('"', pos2 + 1);
|
||||
if (ba[pos3 + 1] == ',')
|
||||
++pos3;
|
||||
ba.remove(pos1, pos3 - pos1 + 1);
|
||||
GdbMi res;
|
||||
res.fromString(ba);
|
||||
BreakHandler *handler = breakHandler();
|
||||
Breakpoint bp;
|
||||
for (const GdbMi &bkpt : res) {
|
||||
const QString nr = bkpt["number"].data();
|
||||
if (nr.contains('.')) {
|
||||
// A sub-breakpoint.
|
||||
QTC_ASSERT(bp, continue);
|
||||
SubBreakpoint loc = bp->findOrCreateSubBreakpoint(nr);
|
||||
loc->params.updateFromGdbOutput(bkpt);
|
||||
loc->params.type = bp->type();
|
||||
if (bp->isTracepoint()) {
|
||||
loc->params.tracepoint = true;
|
||||
loc->params.message = bp->message();
|
||||
}
|
||||
} else {
|
||||
// A primary breakpoint.
|
||||
bp = handler->findBreakpointByResponseId(nr);
|
||||
if (bp)
|
||||
bp->updateFromGdbOutput(bkpt);
|
||||
}
|
||||
}
|
||||
QTC_ASSERT(bp, return);
|
||||
bp->adjustMarker();
|
||||
}
|
||||
|
||||
bool GdbEngine::acceptsBreakpoint(const BreakpointParameters &bp) const
|
||||
{
|
||||
if (runParameters().startMode == AttachToCore)
|
||||
@@ -2387,31 +2605,100 @@ void GdbEngine::insertBreakpoint(const Breakpoint &bp)
|
||||
cmd.function = "catch syscall";
|
||||
cmd.callback = handleCatch;
|
||||
} else {
|
||||
int spec = requested.threadSpec;
|
||||
if (requested.isTracepoint()) {
|
||||
cmd.function = "-break-insert -a -f ";
|
||||
|
||||
if (boolSetting(UsePseudoTracepoints)) {
|
||||
cmd.function = "createTracepoint";
|
||||
|
||||
if (requested.oneShot)
|
||||
cmd.arg("temporary", true);
|
||||
|
||||
if (int ignoreCount = requested.ignoreCount)
|
||||
cmd.arg("ignore_count", ignoreCount);
|
||||
|
||||
QString condition = requested.condition;
|
||||
if (!condition.isEmpty())
|
||||
cmd.arg("condition", condition.replace('"', "\\\""));
|
||||
|
||||
if (spec >= 0)
|
||||
cmd.arg("thread", spec);
|
||||
|
||||
updateTracepointCaptures(bp);
|
||||
QVariant tpCaps = bp->property(tracepointCapturePropertyName);
|
||||
if (tpCaps.isValid()) {
|
||||
QJsonArray caps;
|
||||
foreach (const auto &tpCap, tpCaps.toList()) {
|
||||
TracepointCaptureData data = tpCap.value<TracepointCaptureData>();
|
||||
QJsonArray cap;
|
||||
cap.append(static_cast<int>(data.type));
|
||||
if (data.expression.isValid())
|
||||
cap.append(data.expression.toString());
|
||||
else
|
||||
cap.append(QJsonValue::Null);
|
||||
caps.append(cap);
|
||||
}
|
||||
cmd.arg("caps", caps);
|
||||
}
|
||||
|
||||
// for dumping of expressions
|
||||
const static bool alwaysVerbose = qEnvironmentVariableIsSet("QTC_DEBUGGER_PYTHON_VERBOSE");
|
||||
cmd.arg("passexceptions", alwaysVerbose);
|
||||
cmd.arg("fancy", boolSetting(UseDebuggingHelpers));
|
||||
cmd.arg("autoderef", boolSetting(AutoDerefPointers));
|
||||
cmd.arg("dyntype", boolSetting(UseDynamicType));
|
||||
cmd.arg("qobjectnames", boolSetting(ShowQObjectNames));
|
||||
cmd.arg("nativemixed", isNativeMixedActive());
|
||||
cmd.arg("stringcutoff", action(MaximalStringLength)->value().toString());
|
||||
cmd.arg("displaystringlimit", action(DisplayStringLimit)->value().toString());
|
||||
|
||||
cmd.arg("spec", breakpointLocation2(requested));
|
||||
cmd.callback = [this, bp](const DebuggerResponse &r) { handleTracepointInsert(r, bp); };
|
||||
|
||||
} else {
|
||||
cmd.function = "-break-insert -a -f ";
|
||||
|
||||
if (requested.oneShot)
|
||||
cmd.function += "-t ";
|
||||
|
||||
if (!requested.enabled)
|
||||
cmd.function += "-d ";
|
||||
|
||||
if (int ignoreCount = requested.ignoreCount)
|
||||
cmd.function += "-i " + QString::number(ignoreCount) + ' ';
|
||||
|
||||
QString condition = requested.condition;
|
||||
if (!condition.isEmpty())
|
||||
cmd.function += " -c \"" + condition.replace('"', "\\\"") + "\" ";
|
||||
|
||||
cmd.function += breakpointLocation(requested);
|
||||
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert1(r, bp); };
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
int spec = requested.threadSpec;
|
||||
cmd.function = "-break-insert ";
|
||||
if (spec >= 0)
|
||||
cmd.function += "-p " + QString::number(spec);
|
||||
cmd.function += " -f ";
|
||||
}
|
||||
|
||||
if (requested.oneShot)
|
||||
cmd.function += "-t ";
|
||||
if (requested.oneShot)
|
||||
cmd.function += "-t ";
|
||||
|
||||
if (!requested.enabled)
|
||||
cmd.function += "-d ";
|
||||
if (!requested.enabled)
|
||||
cmd.function += "-d ";
|
||||
|
||||
if (int ignoreCount = requested.ignoreCount)
|
||||
cmd.function += "-i " + QString::number(ignoreCount) + ' ';
|
||||
if (int ignoreCount = requested.ignoreCount)
|
||||
cmd.function += "-i " + QString::number(ignoreCount) + ' ';
|
||||
|
||||
QString condition = requested.condition;
|
||||
if (!condition.isEmpty())
|
||||
cmd.function += " -c \"" + condition.replace('"', "\\\"") + "\" ";
|
||||
QString condition = requested.condition;
|
||||
if (!condition.isEmpty())
|
||||
cmd.function += " -c \"" + condition.replace('"', "\\\"") + "\" ";
|
||||
|
||||
cmd.function += breakpointLocation(requested);
|
||||
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert1(r, bp); };
|
||||
cmd.function += breakpointLocation(requested);
|
||||
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert1(r, bp); };
|
||||
|
||||
}
|
||||
}
|
||||
cmd.flags = NeedsTemporaryStop;
|
||||
runCommand(cmd);
|
||||
@@ -4831,3 +5118,4 @@ DebuggerEngine *createGdbEngine()
|
||||
} // namespace Debugger
|
||||
|
||||
Q_DECLARE_METATYPE(Debugger::Internal::GdbMi)
|
||||
Q_DECLARE_METATYPE(Debugger::Internal::TracepointCaptureData)
|
||||
|
@@ -223,6 +223,9 @@ private: ////////// General Interface //////////
|
||||
void handleBreakCondition(const DebuggerResponse &response, const Breakpoint &bp);
|
||||
void handleBreakThreadSpec(const DebuggerResponse &response, const Breakpoint &bp);
|
||||
void handleBreakLineNumber(const DebuggerResponse &response, const Breakpoint &bp);
|
||||
void handleTracepointInsert(const DebuggerResponse &response, const Breakpoint &bp);
|
||||
void handleTracepointHit(const GdbMi &data);
|
||||
void handleTracepointModified(const GdbMi &data);
|
||||
void handleInsertInterpreterBreakpoint(const DebuggerResponse &response, const Breakpoint &bp);
|
||||
void handleInterpreterBreakpointModified(const GdbMi &data);
|
||||
void handleWatchInsert(const DebuggerResponse &response, const Breakpoint &bp);
|
||||
@@ -231,6 +234,7 @@ private: ////////// General Interface //////////
|
||||
QString breakpointLocation(const BreakpointParameters &data); // For gdb/MI.
|
||||
QString breakpointLocation2(const BreakpointParameters &data); // For gdb/CLI fallback.
|
||||
QString breakLocation(const QString &file) const;
|
||||
void updateTracepointCaptures(const Breakpoint &bp);
|
||||
|
||||
//
|
||||
// Modules specific stuff
|
||||
|
@@ -153,6 +153,11 @@ GdbOptionsPageWidget::GdbOptionsPageWidget()
|
||||
"<html><head/><body>GDB shows by default AT&&T style disassembly."
|
||||
"</body></html>"));
|
||||
|
||||
auto checkBoxUsePseudoTracepoints = new QCheckBox(groupBoxGeneral);
|
||||
checkBoxUsePseudoTracepoints->setText(GdbOptionsPage::tr("Use pseudo message tracepoints"));
|
||||
checkBoxUsePseudoTracepoints->setToolTip(GdbOptionsPage::tr(
|
||||
"Uses python to extend the ordinary GDB breakpoint class."));
|
||||
|
||||
QString howToUsePython = GdbOptionsPage::tr(
|
||||
"<p>To execute simple Python commands, prefix them with \"python\".</p>"
|
||||
"<p>To execute sequences of Python commands spanning multiple lines "
|
||||
@@ -225,6 +230,7 @@ GdbOptionsPageWidget::GdbOptionsPageWidget()
|
||||
formLayout->addRow(checkBoxLoadGdbInit);
|
||||
formLayout->addRow(checkBoxLoadGdbDumpers);
|
||||
formLayout->addRow(checkBoxIntelFlavor);
|
||||
formLayout->addRow(checkBoxUsePseudoTracepoints);
|
||||
|
||||
auto startLayout = new QGridLayout(groupBoxStartupCommands);
|
||||
startLayout->addWidget(textEditStartupCommands, 0, 0, 1, 1);
|
||||
@@ -248,6 +254,7 @@ GdbOptionsPageWidget::GdbOptionsPageWidget()
|
||||
group.insert(action(IntelFlavor), checkBoxIntelFlavor);
|
||||
group.insert(action(UseMessageBoxForSignals), checkBoxUseMessageBoxForSignals);
|
||||
group.insert(action(SkipKnownFrames), checkBoxSkipKnownFrames);
|
||||
group.insert(action(UsePseudoTracepoints), checkBoxUsePseudoTracepoints);
|
||||
|
||||
//lineEditSelectedPluginBreakpointsPattern->
|
||||
// setEnabled(action(SelectedPluginBreakpoints)->value().toBool());
|
||||
|
Reference in New Issue
Block a user