diff mbox

[3/3] scripts/qemu-gdb: Add support for printing trace events

Message ID 1431621835-7565-4-git-send-email-peter.maydell@linaro.org
State Rejected
Headers show

Commit Message

Peter Maydell May 14, 2015, 4:43 p.m. UTC
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.maydell@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

Comments

Paolo Bonzini Sept. 22, 2015, 2:52 p.m. UTC | #1
On 14/05/2015 18:43, Peter Maydell wrote:
> 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.maydell@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)
> 

Hi Peter,

what happened to this patch?

Paolo
Peter Maydell Sept. 22, 2015, 3:39 p.m. UTC | #2
On 22 September 2015 at 07:52, Paolo Bonzini <pbonzini@redhat.com> wrote:
>
>
> On 14/05/2015 18:43, Peter Maydell wrote:
>> 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.
>>

> what happened to this patch?

Stefan suggested that it was basically a manual reimplementation
of gdb's support for static probepoints via systemtap/dtrace,
so I put it to one side til I had time to investigate that gdb
functionality. I committed the patches which were refactoring
the script into modules, though.

thanks
-- PMM
diff mbox

Patch

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)