Add two new commands to our gdb support: qemu trace-enable eventname qemu trace-disable eventname
which allow dynamically enabling and disabling printing of QEMU trace events during a debugging session. These work with the "null" trace backend, by putting breakpoints on the stub trace_eventname() functions. Signed-off-by: Peter Maydell <peter.mayd...@linaro.org> --- scripts/qemu-gdb.py | 4 +- scripts/qemugdb/trace.py | 188 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 scripts/qemugdb/trace.py diff --git a/scripts/qemu-gdb.py b/scripts/qemu-gdb.py index 1c94b2a..6d27c06 100644 --- a/scripts/qemu-gdb.py +++ b/scripts/qemu-gdb.py @@ -23,7 +23,7 @@ import os, sys sys.path.append(os.path.dirname(__file__)) -from qemugdb import mtree, coroutine +from qemugdb import mtree, coroutine, trace class QemuCommand(gdb.Command): '''Prefix for QEMU debug support commands''' @@ -34,3 +34,5 @@ class QemuCommand(gdb.Command): QemuCommand() coroutine.CoroutineCommand() mtree.MtreeCommand() +trace.TraceEnableCommand() +trace.TraceDisableCommand() diff --git a/scripts/qemugdb/trace.py b/scripts/qemugdb/trace.py new file mode 100644 index 0000000..24543e1 --- /dev/null +++ b/scripts/qemugdb/trace.py @@ -0,0 +1,188 @@ +#!/usr/bin/python + +# GDB debugging support: selecting printing of trace events +# +# Copyright (c) 2015 Linaro Ltd +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see +# <http://www.gnu.org/licenses/gpl-2.0.html> + +# qemu trace-enable trace-event-name +# qemu trace-disable trace-event-name +# * enable/disable printing of tracing for QEMU trace events (works +# even if QEMU was built with the null trace backend; may not work +# with non-debug QEMU builds) + +import gdb +import re, os + +# Assume the trace-events file is at ../../ relative to where we are + +trace_events_filename = os.path.join(os.path.dirname(__file__), + os.pardir,os.pardir, + "trace-events") + +def gdb_bp_list(): + '''Like gdb.breakpoints(), but return empty list if no bps, not None''' + # The point is that this is always iterable + bplist = gdb.breakpoints() + if not bplist: + return [] + return bplist + +class TraceEventInfo: + def __init__(self, name, formatstring, arglist): + self.name = name + self.formatstring = formatstring + self.arglist = arglist + self.argstr = ", ".join(arglist) + if self.argstr != "": + self.argstr = ", " + self.argstr + +# Hash of trace events read in from the 'trace-events' file; +# values are TraceEventInfo objects +trace_events = {} + +def extract_identifier(s): + '''Extract the identifier from a C argument declaration''' + # That is, given "const char *filename" return "filename". + r = re.sub(r'.*?([a-zA-Z_][a-zA-Z_0-9]*)\s*$', r'\1', s) + if r == 'void': + return None + return r + +# Preprocessor symbols which we know about. +# These should work for both 32 bit and 64 bit Linux, at least. +# If we needed to, we could determine whether the target was +# 32 or 64 bit with +# is_64bit = gdb.lookup_type('void').pointer().sizeof == 8 +# but we can get away without it. +fmtstr_dict = { + "PRIu8":"u", + "PRIx32":"x", + "PRIu32":"u", + "PRId32":"d", + "PRIx64":"llx", + "PRIu64":"llu", + "PRId64":"lld", + "PRIxPTR":"llx", +} + +def fixup_fmtstr(s): + # fmtstr needs to have leading space and " removed, + # trailing " removed, and handling of interpolated PRIxfoo + # dealt with (including trailing PRIxfoo) + inquotes = False + inescape = False + new = "" + sym = "" + for c in s: + if inquotes: + if inescape: + new = new + c + inescape = False + elif c == '\\': + inescape = True + new = new + c + elif c == '"': + inquotes = False + sym = "" + else: + new = new + c + else: + if c == '"': + # end of unquoted section + sym = sym.strip() + if sym != "": + new = new + fmtstr_dict[sym] + inquotes = True + else: + sym = sym + c + + # gdb printf doesn't understand the 'z' length modifier, + # so convert to 'l' + return re.sub(r'(?<!%)%z', r'%l', new) + return new + +def read_trace_events_file(filename): + '''Populate the trace_events dictionary from the specified file''' + global trace_events + trace_events = {} + f = open(filename) + for line in iter(f): + try: + line = line.strip() + if line == "" or line.startswith('#'): + continue + + # Very ugly ad-hoc parsing + (name, rest) = line.split('(', 1) + (rest, fmtstr) = rest.split(')', 1) + + fmtstr = fixup_fmtstr(fmtstr) + + arglist = rest.split(',') + arglist = [extract_identifier(x) for x in arglist] + arglist = [x for x in arglist if x is not None] + trace_events[name] = TraceEventInfo(name, fmtstr, arglist) + except: + gdb.write('Warning: ignoring line: %s\n' % line) + raise + +class QemuTraceBreakpoint(gdb.Breakpoint): + def __init__(self, eventname): + self.event = trace_events[eventname] + spec = "trace_" + eventname + # might want to make these internal bps later + gdb.Breakpoint.__init__(self, spec, gdb.BP_BREAKPOINT, False) + + def stop(self): + gdb.write('%s: ' % self.event.name) + gdb.execute('printf "%s\\n"%s' + % (self.event.formatstring, self.event.argstr)) + # Tell gdb not to actually stop here + return False + +class TraceEnableCommand(gdb.Command): + '''Enable in-gdb tracing of the specified QEMU trace event''' + def __init__(self): + gdb.Command.__init__(self, 'qemu trace-enable', gdb.COMMAND_DATA, + gdb.COMPLETE_NONE) + + def invoke(self, arg, from_tty): + # place breakpoint on function trace_<eventname> + # set up breakpoint to print info and continue + # add bp to hash table with key being the event name + if arg not in trace_events: + gdb.write('Unknown trace event %s\n') + return + gdb.write("Enabled trace event %s\n" % arg) + QemuTraceBreakpoint(arg) + +class TraceDisableCommand(gdb.Command): + '''Disable in-gdb tracing of the specified QEMU trace event''' + def __init__(self): + gdb.Command.__init__(self, 'qemu trace-disable', gdb.COMMAND_DATA, + gdb.COMPLETE_NONE) + + def invoke(self, arg, from_tty): + # delete the bp set on trace_<eventname> by the enable command + for bp in gdb_bp_list(): + if isinstance(bp, QemuTraceBreakpoint) and bp.event.name == arg: + bp.delete() + gdb.write("Disabled trace event %s\n" % arg) + return + gdb.write("Can't disable trace event %s: unknown or not enabled\n" % arg) + +read_trace_events_file(trace_events_filename) -- 1.9.1