• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# GStreamer
2# Copyright (C) 2018 Pengutronix, Michael Olbrich <m.olbrich@pengutronix.de>
3#
4# gst_gdb.py: gdb extension for GStreamer
5#
6# This library is free software; you can redistribute it and/or
7# modify it under the terms of the GNU Library General Public
8# License as published by the Free Software Foundation; either
9# version 2 of the License, or (at your option) any later version.
10#
11# This library is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14# Library General Public License for more details.
15#
16# You should have received a copy of the GNU Library General Public
17# License along with this library; if not, write to the
18# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19# Boston, MA 02110-1301, USA.
20
21import gdb
22import sys
23import re
24
25from glib_gobject_helper import g_type_to_name, g_type_name_from_instance, \
26    g_type_to_typenode, g_quark_to_string
27
28if sys.version_info[0] >= 3:
29    long = int
30
31
32def is_gst_type(val, klass):
33    def _is_gst_type(type):
34        if str(type) == klass:
35            return True
36
37        while type.code == gdb.TYPE_CODE_TYPEDEF:
38            type = type.target()
39
40        if type.code != gdb.TYPE_CODE_STRUCT:
41            return False
42
43        fields = type.fields()
44        if len(fields) < 1:
45            return False
46
47        first_field = fields[0]
48        return _is_gst_type(first_field.type)
49
50    type = val.type
51    if type.code != gdb.TYPE_CODE_PTR:
52        return False
53    type = type.target()
54    return _is_gst_type(type)
55
56
57class GstMiniObjectPrettyPrinter:
58    "Prints a GstMiniObject instance pointer"
59
60    def __init__(self, val):
61        self.val = val
62
63    def to_string(self):
64        try:
65            inst = self.val.cast(gdb.lookup_type("GstMiniObject").pointer())
66            gtype = inst["type"]
67            name = g_type_to_name(gtype)
68            return "0x%x [%s]" % (long(self.val), name)
69        except RuntimeError:
70            return "0x%x" % long(self.val)
71
72
73class GstObjectPrettyPrinter:
74    "Prints a GstObject instance"
75
76    def __init__(self, val):
77        self.val = val
78
79    def to_string(self):
80        try:
81            name = g_type_name_from_instance(self.val)
82            if not name:
83                name = str(self.val.type.target())
84            if long(self.val) != 0:
85                inst = self.val.cast(gdb.lookup_type("GstObject").pointer())
86                inst_name = inst["name"].string()
87                if inst_name:
88                    name += "|" + inst_name
89            return ("0x%x [%s]") % (long(self.val), name)
90        except RuntimeError:
91            return "0x%x" % long(self.val)
92
93
94GST_SECOND = 1000000000
95GST_CLOCK_TIME_NONE = 2**64-1
96GST_CLOCK_STIME_NONE = -2**63
97
98
99def format_time(n, signed=False):
100    prefix = ""
101    invalid = False
102    if signed:
103        if n == GST_CLOCK_STIME_NONE:
104            invalid = True
105        prefix = "+" if n >= 0 else "-"
106        n = abs(n)
107    else:
108        if n == GST_CLOCK_TIME_NONE:
109            invalid = True
110
111    if invalid:
112        return "99:99:99.999999999"
113
114    return "%s%u:%02u:%02u.%09u" % (
115        prefix,
116        n / (GST_SECOND * 60 * 60),
117        (n / (GST_SECOND * 60)) % 60,
118        (n / GST_SECOND) % 60,
119        n % GST_SECOND)
120
121
122def format_time_value(val):
123    return format_time(int(val), str(val.type) == "GstClockTimeDiff")
124
125
126class GstClockTimePrinter:
127    "Prints a GstClockTime / GstClockTimeDiff"
128
129    def __init__(self, val):
130        self.val = val
131
132    def to_string(self):
133        return "%d [%s]" % (int(self.val), format_time_value(self.val))
134
135
136def gst_pretty_printer_lookup(val):
137    if is_gst_type(val, "GstMiniObject"):
138        return GstMiniObjectPrettyPrinter(val)
139    if is_gst_type(val, "GstObject"):
140        return GstObjectPrettyPrinter(val)
141    if str(val.type) == "GstClockTime" or str(val.type) == "GstClockTimeDiff":
142        return GstClockTimePrinter(val)
143    return None
144
145
146def save_memory_access(fallback):
147    def _save_memory_access(func):
148        def wrapper(*args, **kwargs):
149            try:
150                return func(*args, **kwargs)
151            except gdb.MemoryError:
152                return fallback
153        return wrapper
154    return _save_memory_access
155
156
157def save_memory_access_print(message):
158    def _save_memory_access_print(func):
159        def wrapper(*args, **kwargs):
160            try:
161                func(*args, **kwargs)
162            except gdb.MemoryError:
163                _gdb_write(args[1], message)
164        return wrapper
165    return _save_memory_access_print
166
167
168def _g_type_from_instance(instance):
169    if long(instance) != 0:
170        try:
171            inst = instance.cast(gdb.lookup_type("GTypeInstance").pointer())
172            klass = inst["g_class"]
173            gtype = klass["g_type"]
174            return gtype
175        except RuntimeError:
176            pass
177    return None
178
179
180def g_inherits_type(val, typename):
181    if is_gst_type(val, "GstObject"):
182        gtype = _g_type_from_instance(val)
183        if gtype is None:
184            return False
185        typenode = g_type_to_typenode(gtype)
186    elif is_gst_type(val, "GstMiniObject"):
187        mini = val.cast(gdb.lookup_type("GstMiniObject").pointer())
188        try:
189            typenode = mini["type"].cast(gdb.lookup_type("TypeNode").pointer())
190        except gdb.MemoryError:
191            return False
192    else:
193        return False
194
195    for i in range(typenode["n_supers"]):
196        if g_type_to_name(typenode["supers"][i]) == typename:
197            return True
198    return False
199
200
201def gst_is_bin(val):
202    return g_inherits_type(val, "GstBin")
203
204
205def _g_array_iter(array, element_type):
206    if array == 0:
207        return
208    try:
209        item = array["data"].cast(element_type.pointer())
210        for i in range(int(array["len"])):
211            yield item[i]
212    except gdb.MemoryError:
213        pass
214
215
216def _g_value_get_value(val):
217    typenode = g_type_to_typenode(val["g_type"])
218    if not typenode:
219        return None
220    tname = g_quark_to_string(typenode["qname"])
221    fname = g_type_to_name(typenode["supers"][int(typenode["n_supers"])])
222    if fname in ("gchar", "guchar", "gboolean", "gint", "guint", "glong",
223                 "gulong", "gint64", "guint64", "gfloat", "gdouble",
224                 "gpointer", "GFlags"):
225        try:
226            t = gdb.lookup_type(tname).pointer()
227        except RuntimeError:
228            t = gdb.lookup_type(fname).pointer()
229    elif fname == "gchararray":
230        t = gdb.lookup_type("char").pointer().pointer()
231    elif fname == "GstBitmask":
232        t = gdb.lookup_type("guint64").pointer()
233    elif fname == "GstFlagSet":
234        t = gdb.lookup_type("guint").pointer().pointer()
235        return val["data"].cast(t)
236    elif fname == "GstFraction":
237        t = gdb.lookup_type("gint").pointer().pointer()
238        return val["data"].cast(t)
239    elif fname == "GstFractionRange":
240        t = gdb.lookup_type("GValue").pointer().pointer()
241    elif fname == "GstValueList":
242        t = gdb.lookup_type("GArray").pointer().pointer()
243    elif fname in ("GBoxed", "GObject"):
244        try:
245            t = gdb.lookup_type(tname).pointer().pointer()
246        except RuntimeError:
247            t = gdb.lookup_type(tname).pointer().pointer()
248    else:
249        return val["data"]
250
251    return val["data"].cast(t).dereference()
252
253
254def gst_object_from_value(value):
255    if value.type.code != gdb.TYPE_CODE_PTR:
256        value = value.address
257
258    if not is_gst_type(value, "GstObject"):
259        raise Exception("'%s' is not a GstObject" % args[0])
260
261    return value.cast(gdb.lookup_type("GstObject").pointer())
262
263
264def gst_object_pipeline(obj):
265    try:
266        while obj["parent"] != 0:
267            tmp = obj["parent"]
268            # sanity checks to handle memory corruption
269            if g_inherits_type(obj, "GstElement") and \
270               GdbGstElement(obj) not in GdbGstElement(tmp).children():
271                break
272            if g_inherits_type(obj, "GstPad"):
273                pad = GdbGstPad(obj)
274                if g_inherits_type(tmp, "GstElement"):
275                    if pad not in GdbGstElement(tmp).pads():
276                        break
277                elif g_inherits_type(tmp, "GstProxyPad"):
278                    t = gdb.lookup_type("GstProxyPad").pointer()
279                    if pad != GdbGstPad(tmp.cast(t)["priv"]["internal"]):
280                        break
281            obj = tmp
282    except gdb.MemoryError:
283        pass
284
285    if not g_inherits_type(obj, "GstElement"):
286        raise Exception("Toplevel parent is not a GstElement")
287    return obj.cast(gdb.lookup_type("GstElement").pointer())
288
289
290def element_state_to_name(state):
291    names = [
292        "VOID_PENDING",
293        "NULL",
294        "READY",
295        "PAUSED",
296        "PLAYING"]
297    return names[state] if state < len(names) else "UNKNOWN"
298
299
300def task_state_to_name(state):
301    names = [
302        "STARTED",
303        "STOPPED",
304        "PAUSED"]
305    return names[state] if state < len(names) else "UNKNOWN"
306
307
308def _gdb_write(indent, text):
309    gdb.write("%s%s\n" % ("  " * indent, text))
310
311
312class GdbCapsFeatures:
313    def __init__(self, val):
314        self.val = val
315
316    def size(self):
317        if long(self.val) == 0:
318            return 0
319        return int(self.val["array"]["len"])
320
321    def items(self):
322        if long(self.val) == 0:
323            return
324        for q in _g_array_iter(self.val["array"], gdb.lookup_type("GQuark")):
325            yield q
326
327    def __eq__(self, other):
328        if self.size() != other.size():
329            return False
330        a1 = list(self.items())
331        a2 = list(other.items())
332        for item in a1:
333            if item not in a2:
334                return False
335        return True
336
337    def __str__(self):
338        if long(self.val) == 0:
339            return ""
340        count = self.size()
341        if int(self.val["is_any"]) == 1 and count == 0:
342            return "(ANY)"
343        if count == 0:
344            return ""
345        s = ""
346        for f in self.items():
347            ss = g_quark_to_string(f)
348            if ss != "memory:SystemMemory" or count > 1:
349                s += ", " if s else ""
350                s += ss
351        return s
352
353
354class GdbGstCaps:
355    def __init__(self, val):
356        self.val = val.cast(gdb.lookup_type("GstCapsImpl").pointer())
357
358    def size(self):
359        return int(self.val["array"]["len"])
360
361    def items(self):
362        gdb_type = gdb.lookup_type("GstCapsArrayElement")
363        for f in _g_array_iter(self.val["array"], gdb_type):
364            yield(GdbCapsFeatures(f["features"]),
365                  GdbGstStructure(f["structure"]))
366
367    def __eq__(self, other):
368        if self.size() != other.size():
369            return False
370        a1 = list(self.items())
371        a2 = list(other.items())
372        for i in range(self.size()):
373            if a1[i] != a2[i]:
374                return False
375        return True
376
377    def dot(self):
378        if self.size() == 0:
379            return "ANY"
380        s = ""
381        for (features, structure) in self.items():
382            s += structure.name()
383            tmp = str(features)
384            if tmp:
385                s += "(" + tmp + ")"
386            s += "\\l"
387            if structure.size() > 0:
388                s += "\\l".join(structure.value_strings("  %18s: %s")) + "\\l"
389        return s
390
391    @save_memory_access_print("<inaccessible memory>")
392    def print(self, indent, prefix=""):
393        items = list(self.items())
394        if len(items) != 1:
395            _gdb_write(indent, prefix)
396            prefix = ""
397        for (features, structure) in items:
398            s = "%s %s" % (prefix, structure.name())
399            tmp = str(features)
400            if tmp:
401                s += "(" + tmp + ")"
402            _gdb_write(indent, s)
403            for val in structure.value_strings("%s: %s", False):
404                _gdb_write(indent+1, val)
405        return s
406
407
408class GdbGValue:
409    def __init__(self, val):
410        self.val = val
411
412    def fundamental_typename(self):
413        typenode = g_type_to_typenode(self.val["g_type"])
414        if not typenode:
415            return None
416        return g_type_to_name(typenode["supers"][int(typenode["n_supers"])])
417
418    def value(self):
419        return _g_value_get_value(self.val)
420
421    def __str__(self):
422        try:
423            value = self.value()
424            tname = self.fundamental_typename()
425            gvalue_type = gdb.lookup_type("GValue")
426            if tname == "GstFraction":
427                v = "%d/%d" % (value[0], value[1])
428            elif tname == "GstBitmask":
429                v = "0x%016x" % long(value)
430            elif tname == "gboolean":
431                v = "false" if int(value) == 0 else "true"
432            elif tname == "GstFlagSet":
433                v = "%x:%x" % (value[0], value[1])
434            elif tname == "GstIntRange":
435                rmin = int(value[0]["v_uint64"]) >> 32
436                rmax = int(value[0]["v_uint64"]) & 0xffffffff
437                step = int(value[1]["v_int"])
438                if step == 1:
439                    v = "[ %d, %d ]" % (rmin, rmax)
440                else:
441                    v = "[ %d, %d, %d ]" % (rmin*step, rmax*step, step)
442            elif tname == "GstFractionRange":
443                v = "[ %s, %s ]" % (GdbGValue(value[0]), GdbGValue(value[1]))
444            elif tname in ("GstValueList", "GstValueArray"):
445                if gvalue_type.fields()[1].type == value.type:
446                    gdb_type = gdb.lookup_type("GArray").pointer()
447                    value = value[0]["v_pointer"].cast(gdb_type)
448                v = "<"
449                for l in _g_array_iter(value, gvalue_type):
450                    v += " " if v == "<" else ", "
451                    v += str(GdbGValue(l))
452                v += " >"
453            elif tname in ("GEnum"):
454                v = "%s(%s)" % (
455                    g_type_to_name(g_type_to_typenode(self.val["g_type"])),
456                    value["v_int"])
457            else:
458                try:
459                    v = value.string()
460                except RuntimeError:
461                    # it is not a string-like type
462                    if gvalue_type.fields()[1].type == value.type:
463                        # don't print the raw GValue union
464                        v = "<unknown type: %s>" % tname
465                    else:
466                        v = str(value)
467        except gdb.MemoryError:
468            v = "<inaccessible memory at 0x%x>" % int(self.val)
469        return v
470
471    def __eq__(self, other):
472        return self.val == other.val
473
474
475class GdbGstStructure:
476    def __init__(self, val):
477        self.val = val.cast(gdb.lookup_type("GstStructureImpl").pointer())
478
479    @save_memory_access("<inaccessible memory>")
480    def name(self):
481        return g_quark_to_string(self.val["s"]["name"])
482
483    @save_memory_access(0)
484    def size(self):
485        return int(self.val["fields_len"])
486
487    def values(self):
488        item = self.val["fields"].cast(gdb.lookup_type("GstStructureField").pointer())
489        for i in range(self.size()):
490            f = item[i]
491            key = g_quark_to_string(f["name"])
492            value = GdbGValue(f["value"])
493            yield(key, value)
494
495    def value(self, key):
496        for (k, value) in self.values():
497            if k == key:
498                return value
499        raise KeyError(key)
500
501    def __eq__(self, other):
502        if self.size() != other.size():
503            return False
504        a1 = list(self.values())
505        a2 = list(other.values())
506        for (key, value) in a1:
507            if (key, value) not in a2:
508                return False
509        return True
510
511    def value_strings(self, pattern, elide=True):
512        s = []
513        for (key, value) in self.values():
514            v = str(value)
515            if elide and len(v) > 25:
516                if v[0] in "[(<\"":
517                    v = v[:20] + "... " + v[-1:]
518                else:
519                    v = v[:22] + "..."
520            s.append(pattern % (key, v))
521        return s
522
523    @save_memory_access_print("<inaccessible memory>")
524    def print(self, indent, prefix=None):
525        if prefix is not None:
526            _gdb_write(indent, "%s: %s" % (prefix, self.name()))
527        else:
528            _gdb_write(indent, "%s:" % (self.name()))
529        for (key, value) in self.values():
530            _gdb_write(indent+1, "%s: %s" % (key, str(value)))
531
532
533class GdbGstSegment:
534    def __init__(self, val):
535        self.val = val
536        self.fmt = str(self.val["format"]).split("_")[-1].lower()
537
538    def format_value(self, n):
539        if self.fmt == "time":
540            return format_time(n, False)
541        else:
542            return str(n)
543
544    def print_optional(self, indent, key, skip=None):
545        value = int(self.val[key])
546        if skip is None or value != skip:
547            _gdb_write(indent, "%s:%s %s" %
548                       (key, (8-len(key))*" ", self.format_value(value)))
549
550    def print(self, indent, seqnum=None):
551        s = "segment:"
552        if seqnum:
553            s += "(seqnum: %s)" % seqnum
554        _gdb_write(indent, s)
555        rate = float(self.val["rate"])
556        applied_rate = float(self.val["applied_rate"])
557        if applied_rate != 1.0:
558            applied = "(applied rate: %g)" % applied_rate
559        else:
560            applied = ""
561        _gdb_write(indent+1, "rate: %g%s" % (rate, applied))
562        self.print_optional(indent+1, "base", 0)
563        self.print_optional(indent+1, "offset", 0)
564        self.print_optional(indent+1, "start")
565        self.print_optional(indent+1, "stop", GST_CLOCK_TIME_NONE)
566        self.print_optional(indent+1, "time")
567        self.print_optional(indent+1, "position")
568        self.print_optional(indent+1, "duration", GST_CLOCK_TIME_NONE)
569
570
571class GdbGstEvent:
572    def __init__(self, val):
573        self.val = val.cast(gdb.lookup_type("GstEventImpl").pointer())
574
575    @save_memory_access("<inaccessible memory>")
576    def typestr(self):
577        t = self.val["event"]["type"]
578        (event_quarks, _) = gdb.lookup_symbol("event_quarks")
579        event_quarks = event_quarks.value()
580        i = 0
581        while event_quarks[i]["name"] != 0:
582            if t == event_quarks[i]["type"]:
583                return event_quarks[i]["name"].string()
584            i += 1
585        return None
586
587    def structure(self):
588        return GdbGstStructure(self.val["structure"])
589
590    @save_memory_access_print("<inaccessible memory>")
591    def print(self, indent):
592        typestr = self.typestr()
593        seqnum = self.val["event"]["seqnum"]
594        if typestr == "caps":
595            caps = GdbGstCaps(self.structure().value("caps").value())
596            caps.print(indent, "caps (seqnum: %s):" % seqnum)
597        elif typestr == "stream-start":
598            stream_id = self.structure().value("stream-id").value()
599            _gdb_write(indent, "stream-start: (seqnum %s)"  % seqnum)
600            _gdb_write(indent + 1, "stream-id: %s" % stream_id.string())
601        elif typestr == "segment":
602            segment = self.structure().value("segment").value()
603            GdbGstSegment(segment).print(indent, seqnum)
604        elif typestr == "tag":
605            struct = self.structure()
606            # skip 'GstTagList-'
607            name = struct.name()[11:]
608            t = gdb.lookup_type("GstTagListImpl").pointer()
609            s = struct.value("taglist").value().cast(t)["structure"]
610            structure = GdbGstStructure(s)
611            _gdb_write(indent, "tag: %s (seqnum: %s)" % (name, seqnum))
612            for (key, value) in structure.values():
613                _gdb_write(indent+1, "%s: %s" % (key, str(value)))
614        else:
615            self.structure().print(indent, "%s (seqnum: %s)" % (typestr, seqnum))
616
617
618class GdbGstBuffer:
619    def __init__(self, val):
620        self.val = val.cast(gdb.lookup_type("GstBuffer").pointer())
621
622    def print_optional(self, indent, key, skip=None, format_func=str):
623        value = int(self.val[key])
624        if skip is None or value != skip:
625            _gdb_write(indent, "%s:%s %s" %
626                       (key, (8-len(key))*" ", format_func(value)))
627
628    @save_memory_access_print("<inaccessible memory>")
629    def print(self, indent):
630        _gdb_write(indent, "GstBuffer: (0x%x)" % self.val)
631        indent += 1
632        self.print_optional(indent, "pool", 0)
633        self.print_optional(indent, "pts", GST_CLOCK_TIME_NONE, format_time)
634        self.print_optional(indent, "dts", GST_CLOCK_TIME_NONE, format_time)
635        self.print_optional(indent, "duration", GST_CLOCK_TIME_NONE, format_time)
636        self.print_optional(indent, "offset", GST_CLOCK_TIME_NONE)
637        self.print_optional(indent, "offset_end", GST_CLOCK_TIME_NONE)
638
639        impl = self.val.cast(gdb.lookup_type("GstBufferImpl").pointer())
640        meta_item = impl['item']
641        if meta_item:
642            _gdb_write(indent, "Metas:")
643            indent += 1
644            while meta_item:
645                meta = meta_item['meta']
646                meta_type_name = g_type_to_name(meta['info']['type'])
647                _gdb_write(indent, "%s:" % meta_type_name)
648                indent += 1
649                meta_info = str(meta.cast(gdb.lookup_type(meta_type_name)))
650                for l in meta_info.split('\n'):
651                    _gdb_write(indent, l)
652                indent -= 1
653                meta_item = meta_item['next']
654        else:
655            _gdb_write(indent, "(No meta)")
656
657
658class GdbGstQuery:
659    def __init__(self, val):
660        self.val = val.cast(gdb.lookup_type("GstQueryImpl").pointer())
661
662    @save_memory_access("<inaccessible memory>")
663    def typestr(self):
664        t = self.val["query"]["type"]
665        (query_quarks, _) = gdb.lookup_symbol("query_quarks")
666        query_quarks = query_quarks.value()
667        i = 0
668        while query_quarks[i]["name"] != 0:
669            if t == query_quarks[i]["type"]:
670                return query_quarks[i]["name"].string()
671            i += 1
672        return None
673
674    def structure(self):
675        return GdbGstStructure(self.val["structure"])
676
677    @save_memory_access_print("<inaccessible memory>")
678    def print(self, indent):
679        typestr = self.typestr()
680        self.structure().print(indent, typestr)
681
682
683class GdbGstObject:
684    def __init__(self, klass, val):
685        self.val = val.cast(klass)
686
687    @save_memory_access("<inaccessible memory>")
688    def name(self):
689        obj = self.val.cast(gdb.lookup_type("GstObject").pointer())
690        return obj["name"].string()
691
692    def full_name(self):
693        parent = self.parent_element()
694        return "%s%s" % (parent.name() + ":" if parent else "", self.name())
695
696    def dot_name(self):
697        ptr = self.val.cast(gdb.lookup_type("void").pointer())
698        return re.sub('[^a-zA-Z0-9<>]', '_', "%s_%s" % (self.name(), str(ptr)))
699
700    def parent(self):
701        obj = self.val.cast(gdb.lookup_type("GstObject").pointer())
702        return obj["parent"]
703
704    def parent_element(self):
705        p = self.parent()
706        if p != 0 and g_inherits_type(p, "GstElement"):
707            element = p.cast(gdb.lookup_type("GstElement").pointer())
708            return GdbGstElement(element)
709        return None
710
711    def parent_pad(self):
712        p = self.parent()
713        if p != 0 and g_inherits_type(p, "GstPad"):
714            pad = p.cast(gdb.lookup_type("GstPad").pointer())
715            return GdbGstPad(pad)
716        return None
717
718
719class GdbGstPad(GdbGstObject):
720    def __init__(self, val):
721        gdb_type = gdb.lookup_type("GstPad").pointer()
722        super(GdbGstPad, self).__init__(gdb_type, val)
723
724    def __eq__(self, other):
725        return self.val == other.val
726
727    def is_linked(self):
728        return long(self.val["peer"]) != 0
729
730    def peer(self):
731        return GdbGstPad(self.val["peer"])
732
733    def direction(self):
734        return str(self.val["direction"])
735
736    def events(self):
737        if long(self.val["priv"]) == 0:
738            return
739        array = self.val["priv"]["events"]
740        for ev in _g_array_iter(array, gdb.lookup_type("PadEvent")):
741            yield GdbGstEvent(ev["event"])
742
743    def caps(self):
744        for ev in self.events():
745            if ev.typestr() != "caps":
746                continue
747            return GdbGstCaps(ev.structure().value("caps").value())
748        return None
749
750    def template_caps(self):
751        tmp = self.val["padtemplate"]
752        return GdbGstCaps(tmp["caps"]) if int(tmp) != 0 else None
753
754    def mode(self):
755        m = str(self.val["mode"]).split("_")[-1].lower()
756        if m in ("push", "pull"):
757            return m
758        return None
759
760    def pad_type(self):
761        s = str(self.val["direction"]).split("_")[-1].capitalize()
762        if g_inherits_type(self.val, "GstGhostPad"):
763            s += "Ghost"
764        return s + "Pad"
765
766    @save_memory_access_print("Pad(<inaccessible memory>)")
767    def print(self, indent):
768        m = ", " + self.mode() if self.mode() else ""
769        _gdb_write(indent, "%s(%s%s) {" % (self.pad_type(), self.name(), m))
770        first = True
771        for ev in self.events():
772            if first:
773                _gdb_write(indent+1, "events:")
774                first = False
775            ev.print(indent+2)
776
777        if self.is_linked():
778            real = self.peer().parent_pad()
779            _gdb_write(indent+1, "peer: %s" %
780                       (real.full_name() if real else self.peer().full_name()))
781
782        if g_inherits_type(self.val, "GstGhostPad"):
783            t = gdb.lookup_type("GstProxyPad").pointer()
784            internal = GdbGstPad(self.val.cast(t)["priv"]["internal"])
785            if internal and internal.peer():
786                _gdb_write(indent+1, "inner peer: %s" %
787                           internal.peer().full_name())
788
789        task = self.val["task"]
790        if long(task) != 0:
791            _gdb_write(indent+1, "task: %s" %
792                       task_state_to_name(int(task["state"])))
793
794        offset = long(self.val["offset"])
795        if offset != 0:
796            _gdb_write(indent+1, "offset: %d [%s]" %
797                       (offset, format_time(offset, True)))
798
799        _gdb_write(indent, "}")
800
801    def _dot(self, color, pname, indent):
802        spc = "  " * indent
803        activation_mode = "-><"
804        style = "filled,solid"
805        template = self.val["padtemplate"]
806        if template != 0:
807            presence = template["presence"]
808            if str(presence) == "GST_PAD_SOMETIMES":
809                style = "filled,dotted"
810            if str(presence) == "GST_PAD_REQUEST":
811                style = "filled,dashed"
812        task_mode = ""
813        task = self.val["task"]
814        if long(task) != 0:
815            task_state = int(task["state"])
816            if task_state == 0:  # started
817                task_mode = "[T]"
818            if task_state == 2:  # paused
819                task_mode = "[t]"
820        f = int(self.val["object"]["flags"])
821        flags = "B" if f & 16 else "b"  # GST_PAD_FLAG_BLOCKED
822        flags += "F" if f & 32 else "f"  # GST_PAD_FLAG_FLUSHING
823        flags += "B" if f & 16 else "b"  # GST_PAD_FLAG_BLOCKING
824
825        s = "%s  %s_%s [color=black, fillcolor=\"%s\", " \
826            "label=\"%s%s\\n[%c][%s]%s\", height=\"0.2\", style=\"%s\"];\n" % \
827            (spc, pname, self.dot_name(), color, self.name(), "",
828             activation_mode[int(self.val["mode"])], flags, task_mode, style)
829        return s
830
831    def dot(self, indent):
832        spc = "  " * indent
833        direction = self.direction()
834        element = self.parent_element()
835        ename = element.dot_name() if element else ""
836        s = ""
837        if g_inherits_type(self.val, "GstGhostPad"):
838            if direction == "GST_PAD_SRC":
839                color = "#ffdddd"
840            elif direction == "GST_PAD_SINK":
841                color = "#ddddff"
842            else:
843                color = "#ffffff"
844
845            t = gdb.lookup_type("GstProxyPad").pointer()
846            other = GdbGstPad(self.val.cast(t)["priv"]["internal"])
847            if other:
848                s += other._dot(color, "", indent)
849                pname = self.dot_name()
850                other_element = other.parent_element()
851                other_ename = other_element.dot_name() if other_element else ""
852                other_pname = other.dot_name()
853                if direction == "GST_PAD_SRC":
854                    s += "%s%s_%s -> %s_%s [style=dashed, minlen=0]\n" % \
855                       (spc, other_ename, other_pname, ename, pname)
856                else:
857                    s += "%s%s_%s -> %s_%s [style=dashed, minlen=0]\n" % \
858                       (spc, ename, pname, other_ename, other_pname)
859        else:
860            if direction == "GST_PAD_SRC":
861                color = "#ffaaaa"
862            elif direction == "GST_PAD_SINK":
863                color = "#aaaaff"
864            else:
865                color = "#cccccc"
866
867        s += self._dot(color, ename, indent)
868        return s
869
870    def link_dot(self, indent, element):
871        spc = "  " * indent
872
873        peer = self.peer()
874        peer_element = peer.parent_element()
875
876        caps = self.caps()
877        if not caps:
878            caps = self.template_caps()
879        peer_caps = peer.caps()
880        if not peer_caps:
881            peer_caps = peer.template_caps()
882
883        pname = self.dot_name()
884        ename = element.dot_name() if element else ""
885        peer_pname = peer.dot_name()
886        peer_ename = peer_element.dot_name() if peer_element else ""
887
888        if caps and peer_caps and caps == peer_caps:
889            s = "%s%s_%s -> %s_%s [label=\"%s\"]\n" % \
890               (spc, ename, pname, peer_ename, peer_pname, caps.dot())
891        elif caps and peer_caps and caps != peer_caps:
892            s = "%s%s_%s -> %s_%s [labeldistance=\"10\", labelangle=\"0\", " \
893                % (spc, ename, pname, peer_ename, peer_pname)
894            s += "label=\"" + " "*50 + "\", "
895            if self.direction() == "GST_PAD_SRC":
896                media_src = caps.dot()
897                media_dst = peer_caps.dot()
898            else:
899                media_src = peer_caps.dot()
900                media_dst = caps.dot()
901            s += "taillabel=\"%s\", headlabel=\"%s\"]\n" % \
902                 (media_src, media_dst)
903        else:
904            s = "%s%s_%s -> %s_%s\n" % \
905                (spc, ename, pname, peer_ename, peer_pname)
906        return s
907
908
909class GdbGstElement(GdbGstObject):
910    def __init__(self, val):
911        gdb_type = gdb.lookup_type("GstElement").pointer()
912        super(GdbGstElement, self).__init__(gdb_type, val)
913        self.is_bin = gst_is_bin(self.val)
914
915    def __eq__(self, other):
916        return self.val == other.val
917
918    def children(self):
919        if not self.is_bin:
920            return
921        b = self.val.cast(gdb.lookup_type("GstBin").pointer())
922        link = b["children"]
923        while link != 0:
924            yield GdbGstElement(link["data"])
925            link = link["next"]
926
927    def has_pads(self, pad_group="pads"):
928        return self.val[pad_group] != 0
929
930    def pads(self, pad_group="pads"):
931        link = self.val[pad_group]
932        while link != 0:
933            yield GdbGstPad(link["data"])
934            link = link["next"]
935
936    def _state_dot(self):
937        icons = "~0-=>"
938        current = int(self.val["current_state"])
939        pending = int(self.val["pending_state"])
940        if pending == 0:
941            # GST_ELEMENT_FLAG_LOCKED_STATE == 16
942            locked = (int(self.val["object"]["flags"]) & 16) != 0
943            return "\\n[%c]%s" % (icons[current], "(locked)" if locked else "")
944        return "\\n[%c] -> [%c]" % (icons[current], icons[pending])
945
946    @save_memory_access_print("Element(<inaccessible memory>)")
947    def print(self, indent):
948        _gdb_write(indent, "%s(%s) {" %
949                   (g_type_name_from_instance(self.val), self.name()))
950        for p in self.pads():
951            p.print(indent+2)
952
953        first = True
954        for child in self.children():
955            if first:
956                _gdb_write(indent+2, "children:")
957                first = False
958            _gdb_write(indent+3, child.name())
959
960        current_state = self.val["current_state"]
961        s = "state: %s" % element_state_to_name(current_state)
962        for var in ("pending", "target"):
963            state = self.val[var + "_state"]
964            if state > 0 and state != current_state:
965                s += ", %s: %s" % (var, element_state_to_name(state))
966        _gdb_write(indent+2, s)
967
968        _gdb_write(indent+2, "base_time: %s" %
969                   format_time_value(self.val["base_time"]))
970        _gdb_write(indent+2, "start_time: %s" %
971                   format_time_value(self.val["start_time"]))
972
973        _gdb_write(indent, "}")
974
975    @save_memory_access_print("<inaccessible memory>")
976    def print_tree(self, indent):
977        _gdb_write(indent, "%s(%s)" % (self.name(), self.val))
978        for child in self.children():
979            child.print_tree(indent+1)
980
981    def _dot(self, indent=0):
982        spc = "  " * indent
983
984        s = "%ssubgraph cluster_%s {\n" % (spc, self.dot_name())
985        s += "%s  fontname=\"Bitstream Vera Sans\";\n" % spc
986        s += "%s  fontsize=\"8\";\n" % spc
987        s += "%s  style=\"filled,rounded\";\n" % spc
988        s += "%s  color=black;\n" % spc
989        s += "%s  label=\"%s\\n%s%s%s\";\n" % \
990             (spc, g_type_name_from_instance(self.val), self.name(),
991              self._state_dot(), "")
992
993        sink_name = None
994        if self.has_pads("sinkpads"):
995            (ss, sink_name) = self._dot_pads(indent+1, "sinkpads",
996                                             self.dot_name() + "_sink")
997            s += ss
998        src_name = None
999        if self.has_pads("srcpads"):
1000            (ss, src_name) = self._dot_pads(indent+1, "srcpads",
1001                                            self.dot_name() + "_src")
1002            s += ss
1003        if sink_name and src_name:
1004            name = self.dot_name()
1005            s += "%s  %s_%s -> %s_%s [style=\"invis\"];\n" % \
1006                 (spc, name, sink_name, name, src_name)
1007
1008        if gst_is_bin(self.val):
1009            s += "%s  fillcolor=\"#ffffff\";\n" % spc
1010            s += self.dot(indent+1)
1011        else:
1012            if src_name and not sink_name:
1013                s += "%s  fillcolor=\"#ffaaaa\";\n" % spc
1014            elif not src_name and sink_name:
1015                s += "%s  fillcolor=\"#aaaaff\";\n" % spc
1016            elif src_name and sink_name:
1017                s += "%s  fillcolor=\"#aaffaa\";\n" % spc
1018            else:
1019                s += "%s  fillcolor=\"#ffffff\";\n" % spc
1020        s += "%s}\n\n" % spc
1021
1022        for p in self.pads():
1023            if not p.is_linked():
1024                continue
1025            if p.direction() == "GST_PAD_SRC":
1026                s += p.link_dot(indent, self)
1027            else:
1028                pp = p.peer()
1029                if not g_inherits_type(pp.val, "GstGhostPad") and \
1030                   g_inherits_type(pp.val, "GstProxyPad"):
1031                    s += pp.link_dot(indent, None)
1032        return s
1033
1034    def _dot_pads(self, indent, pad_group, cluster_name):
1035        spc = "  " * indent
1036        s = "%ssubgraph cluster_%s {\n" % (spc, cluster_name)
1037        s += "%s  label=\"\";\n" % spc
1038        s += "%s  style=\"invis\";\n" % spc
1039        name = None
1040        for p in self.pads(pad_group):
1041            s += p.dot(indent)
1042            if not name:
1043                name = p.dot_name()
1044        s += "%s}\n\n" % spc
1045        return(s, name)
1046
1047    def dot(self, indent):
1048        s = ""
1049        for child in self.children():
1050            try:
1051                s += child._dot(indent)
1052            except gdb.MemoryError:
1053                gdb.write("warning: inaccessible memory in element 0x%x\n" %
1054                          long(child.val))
1055        return s
1056
1057    def pipeline_dot(self):
1058        t = g_type_name_from_instance(self.val)
1059
1060        s = "digraph pipeline {\n"
1061        s += "  rankdir=LR;\n"
1062        s += "  fontname=\"sans\";\n"
1063        s += "  fontsize=\"10\";\n"
1064        s += "  labelloc=t;\n"
1065        s += "  nodesep=.1;\n"
1066        s += "  ranksep=.2;\n"
1067        s += "  label=\"<%s>\\n%s%s%s\";\n" % (t, self.name(), "", "")
1068        s += "  node [style=\"filled,rounded\", shape=box, fontsize=\"9\", " \
1069             "fontname=\"sans\", margin=\"0.0,0.0\"];\n"
1070        s += "  edge [labelfontsize=\"6\", fontsize=\"9\", " \
1071             "fontname=\"monospace\"];\n"
1072        s += "  \n"
1073        s += "  legend [\n"
1074        s += "    pos=\"0,0!\",\n"
1075        s += "    margin=\"0.05,0.05\",\n"
1076        s += "    style=\"filled\",\n"
1077        s += "    label=\"Legend\\lElement-States: [~] void-pending, " \
1078             "[0] null, [-] ready, [=] paused, [>] playing\\l" \
1079             "Pad-Activation: [-] none, [>] push, [<] pull\\l" \
1080             "Pad-Flags: [b]locked, [f]lushing, [b]locking, [E]OS; " \
1081             "upper-case is set\\lPad-Task: [T] has started task, " \
1082             "[t] has paused task\\l\",\n"
1083        s += "  ];"
1084        s += "\n"
1085
1086        s += self.dot(1)
1087
1088        s += "}\n"
1089
1090        return s
1091
1092
1093class GstDot(gdb.Command):
1094    """\
1095Create a pipeline dot file as close as possible to the output of
1096GST_DEBUG_BIN_TO_DOT_FILE. This command will find the top-level parent
1097for the given gstreamer object and create the dot for that element.
1098
1099Usage: gst-dot <gst-object> <file-name>"""
1100    def __init__(self):
1101        super(GstDot, self).__init__("gst-dot", gdb.COMMAND_DATA)
1102
1103    def invoke(self, arg, from_tty):
1104        self.dont_repeat()
1105        args = gdb.string_to_argv(arg)
1106        if len(args) != 2:
1107            raise Exception("Usage: gst-dot <gst-object> <file>")
1108
1109        value = gdb.parse_and_eval(args[0])
1110        if not value:
1111            raise Exception("'%s' is not a valid object" % args[0])
1112
1113        value = gst_object_from_value(value)
1114        value = gst_object_pipeline(value)
1115
1116        dot = GdbGstElement(value).pipeline_dot()
1117        file = open(args[1], "w")
1118        file.write(dot)
1119        file.close()
1120
1121    def complete(self, text, word):
1122        cmd = gdb.string_to_argv(text)
1123        if len(cmd) == 0 or(len(cmd) == 1 and len(word) > 0):
1124            return gdb.COMPLETE_SYMBOL
1125        return gdb.COMPLETE_FILENAME
1126
1127
1128class GstPrint(gdb.Command):
1129    """\
1130Print high-level information for GStreamer objects
1131
1132Usage gst-print <gstreamer-object>"""
1133    def __init__(self):
1134        super(GstPrint, self).__init__("gst-print", gdb.COMMAND_DATA,
1135                                       gdb.COMPLETE_SYMBOL)
1136
1137    def invoke(self, arg, from_tty):
1138        value = gdb.parse_and_eval(arg)
1139        if not value:
1140            raise Exception("'%s' is not a valid object" % arg)
1141
1142        if value.type.code != gdb.TYPE_CODE_PTR:
1143            value = value.address
1144
1145        if g_inherits_type(value, "GstElement"):
1146            obj = GdbGstElement(value)
1147        elif g_inherits_type(value, "GstPad"):
1148            obj = GdbGstPad(value)
1149        elif g_inherits_type(value, "GstCaps"):
1150            obj = GdbGstCaps(value)
1151        elif g_inherits_type(value, "GstEvent"):
1152            obj = GdbGstEvent(value)
1153        elif g_inherits_type(value, "GstQuery"):
1154            obj = GdbGstQuery(value)
1155        elif g_inherits_type(value, "GstBuffer"):
1156            obj = GdbGstBuffer(value)
1157        elif is_gst_type(value, "GstStructure"):
1158            obj = GdbGstStructure(value)
1159        else:
1160            raise Exception("'%s' has an unknown type (%s)" % (arg, value))
1161
1162        obj.print(0)
1163
1164
1165class GstPipelineTree(gdb.Command):
1166    """\
1167Usage: gst-pipeline-tree <gst-object>"""
1168    def __init__(self):
1169        super(GstPipelineTree, self).__init__("gst-pipeline-tree",
1170                                              gdb.COMPLETE_SYMBOL)
1171
1172    def invoke(self, arg, from_tty):
1173        self.dont_repeat()
1174        args = gdb.string_to_argv(arg)
1175        if len(args) != 1:
1176            raise Exception("Usage: gst-pipeline-tree <gst-object>")
1177
1178        value = gdb.parse_and_eval(args[0])
1179        if not value:
1180            raise Exception("'%s' is not a valid object" % args[0])
1181
1182        value = gst_object_from_value(value)
1183        value = gst_object_pipeline(value)
1184        GdbGstElement(value).print_tree(0)
1185
1186
1187GstDot()
1188GstPrint()
1189GstPipelineTree()
1190
1191
1192class GstPipeline(gdb.Function):
1193    """\
1194Find the top-level pipeline for the given element"""
1195
1196    def __init__(self):
1197        super(GstPipeline, self).__init__("gst_pipeline")
1198
1199    def invoke(self, arg):
1200        value = gst_object_from_value(arg)
1201        return gst_object_pipeline(value)
1202
1203
1204class GstBinGet(gdb.Function):
1205    """\
1206Find a child element with the given name"""
1207
1208    def __init__(self):
1209        super(GstBinGet, self).__init__("gst_bin_get")
1210
1211    def find(self, obj, name, recurse):
1212        for child in obj.children():
1213            if child.name() == name:
1214                return child.val
1215            if recurse:
1216                result = self.find(child, name, recurse)
1217                if result is not None:
1218                    return result
1219
1220    def invoke(self, element, arg):
1221        value = gst_object_from_value(element)
1222        if not g_inherits_type(value, "GstElement"):
1223            raise Exception("'%s' is not a GstElement" %
1224                            str(value.address))
1225
1226        try:
1227            name = arg.string()
1228        except gdb.error:
1229            raise Exception("Usage: $gst_bin_get(<gst-object>, \"<name>\")")
1230
1231        obj = GdbGstElement(value)
1232        child = self.find(obj, name, False)
1233        if child is None:
1234            child = self.find(obj, name, True)
1235        if child is None:
1236            raise Exception("No child named '%s' found." % name)
1237        return child
1238
1239
1240class GstElementPad(gdb.Function):
1241    """\
1242Get the pad with the given name"""
1243
1244    def __init__(self):
1245        super(GstElementPad, self).__init__("gst_element_pad")
1246
1247    def invoke(self, element, arg):
1248        value = gst_object_from_value(element)
1249        if not g_inherits_type(value, "GstElement"):
1250            raise Exception("'%s' is not a GstElement" %
1251                            str(value.address))
1252
1253        try:
1254            name = arg.string()
1255        except gdb.error:
1256            raise Exception("Usage: $gst_element_pad(<gst-object>, \"<pad-name>\")")
1257
1258        obj = GdbGstElement(value)
1259        for pad in obj.pads():
1260            if pad.name() == name:
1261                return pad.val
1262
1263        raise Exception("No pad named '%s' found." % name)
1264
1265
1266GstPipeline()
1267GstBinGet()
1268GstElementPad()
1269
1270
1271def register(obj):
1272    if obj is None:
1273        obj = gdb
1274
1275    # Make sure this is always used before the glib lookup function.
1276    # Otherwise the gobject pretty printer is used for GstObjects
1277    obj.pretty_printers.insert(0, gst_pretty_printer_lookup)
1278