python/vdb.py
changeset 178 5d1c3e5fab6b
parent 177 fd154978bab5
child 198 1d26f35595b2
--- a/python/vdb.py	Mon Jul 08 12:37:49 2019 +0100
+++ b/python/vdb.py	Mon Jul 08 13:31:11 2019 +0100
@@ -7,4 +7,390 @@
 # You may find a full license text in LICENSE.txt or at http://creativecommons.org/licenses/by-nc/4.0/
 #
 
+import itertools
+import argparse
+import copy
 import gdb
+
+
+class Error(Exception):
+    pass
+
+class BasicPrinter(object):
+    def __init__(self, value):
+        self._value = value
+
+    def to_string(self):
+        return self._value
+
+class _VarObjRegistry(object):
+
+    def __init__(self):
+        self._seqno = 0
+        self._varobjs = {}
+
+    def __iter__(self):
+        return iter(self._varobjs)
+
+    def __getitem__(self, name):
+        return self._varobjs[name]
+
+    def __delitem__(self, name):
+        del self._varobjs[name]
+
+    def generate_name(self):
+        """
+        Generate and return a new, unique variable name.
+        """
+        name = 'vdbvar' + str(self._seqno)
+        self._seqno = self._seqno + 1
+        return name
+
+    def validate_name(self, name):
+        """
+        Check if given `name` is not yet used (and therefore can
+        be used for new variable). Throws an error if the name
+        is already in use, otherwise no-op.
+        """
+        if name in self._varobjs:
+            raise Error("Name not unique: %s" % name)
+
+    def register(self, varobj):
+        self.validate_name(varobj.name)
+        self._varobjs[varobj.name] = varobj
+
+    def unregister(self, varobj):
+        del self._varobjs[varobj.name]
+
+    def unregister_all(self):
+        self._varobjs = {}
+
+_VarObjs = _VarObjRegistry()
+
+
+class _VarObj(object):
+    def __init__(self, name, expr, value, parent = None):
+        self._name = name
+        self._expr = expr
+        self._value = value
+        self._parent = parent
+        self.set_visualizer(None)
+
+    def __copy__(self):
+        return _VarObj(None, self._expr, self._value, self._parent)
+
+    def to_mi(self, properties = [ 'name', 'exp', 'numchild', 'value', 'type', 'thread_id', 'frozen', 'has_more', 'displayhint', 'dynamic' ]):
+        """
+        Return MI record representing this variable object.
+        """
+        return { p : getattr(self, p) for p in properties if getattr(self, p) != None}
+
+    def children(self):
+        """
+        Return an iterable (possibly empty) over child varobjs.
+        Children are NOT registered, the caller must take care
+        about this.
+        """
+        if not hasattr(self, '_children'):
+            def child(exp, value):
+                child_name = self._name + '.' + exp
+                child = _VarObj(child_name, exp, value, self)
+                return child
+            if not hasattr(self._visualizer, "children"):
+                self._children = []
+            else:
+                class lazy():
+                    def __init__(self, iterator):
+                        self._head = []
+                        self._rest = iterator
+
+                    def __iter__(self):
+                        for each in self._head:
+                            yield each
+                        for each in self._rest:
+                            self._head.append(each)
+                            yield each
+                self._children = lazy(map(lambda pair : child(pair[0], pair[1]), self._visualizer.children()))
+        return iter(self._children)
+
+    def set_visualizer(self,visualizer):
+        """
+        Set new visualizer
+        """
+
+        self._visualizer = visualizer
+        if self._visualizer == None:
+            self._visualizer = gdb.default_visualizer(self._value)
+        if self._visualizer == None:
+            self._visualizer = BasicPrinter(self._value)
+        if hasattr(self, "_children"):
+            del self._children
+
+    @property
+    def parent(self):
+        """
+        Return parent varobj or None
+        """
+        return self._parent
+
+    @property
+    def name(self):
+        """
+        The name of the varobj.
+        """
+        return self._name
+
+    @property
+    def numchild(self):
+        """
+        The number of children of the varobj. This number is not necessarily
+        reliable for a dynamic varobj. Instead, you must examine the ‘has_more’
+        attribute.
+        """
+        return 0
+
+    @property
+    def value(self):
+        """
+        The varobj’s scalar value. For a varobj whose type is some sort of
+        aggregate (e.g., a struct), or for a dynamic varobj, this value will not
+        be interesting.
+        """
+        return str(self._visualizer.to_string())
+
+    @property
+    def type(self):
+        """
+        The varobj’s type. This is a string representation of the type, as would
+        be printed by the GDB CLI. If ‘print object’ (see set print object) is
+        set to on, the actual (derived) type of the object is shown rather than
+        the declared one.
+        """
+        return str(self._value.type)
+
+    @property
+    def thread_id(self):
+        """
+        If a variable object is bound to a specific thread, then this is the
+        thread’s global identifier.
+        """
+        return None
+
+    @property
+    def has_more(self):
+        """
+        For a dynamic varobj, this indicates whether there appear to be any
+        children available. For a non-dynamic varobj, this will be 0.
+        """
+        try:
+            next(self.children())
+            return 1
+        except StopIteration:
+            return 0
+
+    @property
+    def dynamic(self):
+        """
+        This attribute will be present and have the value ‘1’ if the varobj is a
+        dynamic varobj. If the varobj is not a dynamic varobj, then this
+        attribute will not be present.
+        """
+        return 1
+
+    @property
+    def displayhint(self):
+        """
+        A dynamic varobj can supply a display hint to the front end. The value
+        comes directly from the Python pretty-printer object’s display_hint
+        method. See Pretty Printing API.
+        """
+        return None
+
+    @property
+    def frozen(self):
+        """
+        If the variable object is frozen, this variable will be present with a
+        value of 1.
+        """
+        return 0
+
+    @property
+    def exp(self):
+        """
+        The expression to be shown to the user by the front end to designate
+        this child. For example this may be the name of a structure member.
+        """
+        return self._expr
+
+_VarEvaluators = []
+
+def register_evaluator(callable, replace = True):
+    """
+    Register `callable` in list of evaluators which are tried when creating
+    -vdb-var-create. To evaluate and expression, `callable` is invoked passing
+    expression string and must return either `gdb.Value` or `None`.
+    """
+    _VarEvaluators.append(callable)
+
+def unregister_evaluator(callable):
+    """
+    Unregister previously registered evaluator.
+    """
+    if callable in _VarEvaluators:
+        _VarEvaluators.remove(callable)
+
+def unregister_evaluators():
+    """
+    Unregister all evaluators. To be used mainly in tests
+    """
+    _VarEvaluators = []
+
+def parse_and_eval(expression):
+    """
+    Parse and evaluate expression and return a gdb.Value() as follows:
+
+      * First, use builtin `gdb.parse_and_eval()`. If builtin succeeds,
+        return resulting value.
+      * Second, check if `expressions` matches any argument or local
+        as returned by frame decorator for current frame, if it does,
+        return it.
+      * Finally, try to use any registered custom evaluator (see
+        `register_evaluator()`. If first non-None value is returned.
+      * If none of the above yields a value, an exception is thrown, just
+        like `gdb.parse_and_eval()` does if it cannot parse/evaluate given
+        expression.
+    """
+
+    # First, try builtin gdb.parse_and_eval:
+    try:
+        return gdb.parse_and_eval(expression)
+    except:
+        pass
+
+    # Second, try frame decorator frame_args() / frame_locals():
+    decorator = next(gdb.frames.execute_frame_filters(gdb.selected_frame(), 0, 0))
+    for var in itertools.chain(decorator.frame_args(), decorator.frame_locals()):
+        if var.sym == expression:
+            return var.value()
+
+    # Firnally, try custom registered expression evaluators:
+    for evaluate in _VarEvaluators:
+        value = evaluate(expression)
+        if value != None:
+            return value
+
+    # Nothing works, give up.
+    raise Error("Cannot evaluate expression: %s" % expression)
+
+def micommand(name):
+    def _command(func):
+        class _Command(gdb.MICommand):
+            def invoke(*args, **kwargs):
+                return func(*args[1:], **kwargs)
+            def __call__(self, *args, **kwargs):
+                return self.invoke(*args, **kwargs)
+        return _Command(name)
+    return _command
+
+@micommand('-vdb-var-create')
+def _var_create(argv):
+    argv_parser = argparse.ArgumentParser()
+    argv_parser.add_argument('name')
+    argv_parser.add_argument('frame')
+    argv_parser.add_argument('expr')
+    args = argv_parser.parse_args(argv)
+
+    if args.frame != '*':
+        raise Exception("frame designator must be '*'")
+    if args.name == '-':
+        args.name = _VarObjs.generate_name()
+    _VarObjs.validate_name(args.name)
+
+    varobj = _VarObj(args.name, args.expr, parse_and_eval(args.expr))
+    _VarObjs.register(varobj)
+    return varobj.to_mi()
+
+@micommand('-vdb-var-delete')
+def _var_delete(argv):
+    argv_parser = argparse.ArgumentParser()
+    argv_parser.add_argument('name')
+    argv_parser.add_argument('-c', dest="children_only", action="store_true")
+    args = argv_parser.parse_args(argv)
+
+    if args.children_only:
+        _VarObjs.unregister_children(_VarObjs[args.name])
+    else:
+        _VarObjs.unregister(_VarObjs[args.name])
+    return None
+
+@micommand('-vdb-var-update')
+def _var_update(argv):
+    # Not yet supported
+    return []
+
+@micommand('-vdb-var-duplicate')
+def _var_duplicate(argv):
+    argv_parser = argparse.ArgumentParser()
+    argv_parser.add_argument('name')
+    args = argv_parser.parse_args(argv)
+
+    orig = _VarObjs[args.name]
+    dup = copy.copy(orig)
+    dup._name = _VarObjs.generate_name()
+    _VarObjs.register(dup)
+    return dup.to_mi()
+
+@micommand('-vdb-var-list-children')
+def _var_list_children(argv):
+    argv_parser = argparse.ArgumentParser()
+    argv_parser.add_argument('--all-values', dest="values", action="store_true")
+    argv_parser.add_argument('--no-values', dest="values", action="store_false")
+    argv_parser.add_argument('name')
+    args = argv_parser.parse_args(argv)
+
+    varobj = _VarObjs[args.name]
+    children = list(varobj.children())
+    for child in children:
+        if not child.name in _VarObjs:
+            _VarObjs.register(child)
+    return map(_VarObj.to_mi, children)
+
+
+@micommand('-vdb-var-info-path-expression')
+def _var_info_path_expression(argv):
+    argv_parser = argparse.ArgumentParser()
+    argv_parser.add_argument('name')
+    args = argv_parser.parse_args(argv)
+
+    varobj = _VarObjs[args.name]
+    if varobj.parent != None:
+        raise Error("-vdb-var-info-path-expression is not supported for children of dynamic varobjs")
+    else:
+        return varobj.exp
+
+@micommand('-vdb-var-set-visualizer')
+def _var_set_visualizer(argv):
+    argv_parser = argparse.ArgumentParser()
+    argv_parser.add_argument('name')
+    argv_parser.add_argument('visualizer')
+    args = argv_parser.parse_args(argv)
+
+    varobj = _VarObjs[args.name]
+    # Now, create visualizer )pretty-printer) object. This is
+    # done by evaluating given visualizer expression and and
+    # "calling" the result with gdb.Value as parameter).
+    #
+    # We have to be careful here: the expression is user-supplied
+    # and might use stuff from __main__ module. So first try as it is
+    # and if if fails, try to evaluate again in context of __main__.
+    #
+    visualizer_factory = None
+    try:
+        visualizer_factory = eval(args.visualizer)
+    except:
+        import __main__
+        visualizer_factory = eval(args.visualizer, __main__.__dict__ , __main__.__dict__)
+
+    visualizer = visualizer_factory(varobj._value)
+
+    varobj.set_visualizer(visualizer)
\ No newline at end of file