--- 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