python/vdb/cli.py
author Jan Vrany <jan.vrany@labware.com>
Mon, 08 Aug 2022 12:27:57 +0100
changeset 268 47653b528e7b
parent 258 789898d2b71a
permissions -rw-r--r--
Update README.md

#
# jv:vdb - Visual / VM Debugger
# Copyright (C) 2015, 2021 Jan Vrany
#
# This software is licensed under 'Creative Commons Attribution-NonCommercial 4.0 International License'
#
# You may find a full license text in LICENSE.txt or at http://creativecommons.org/licenses/by-nc/4.0/
#
import os.path
import sys

try:
    import importlib
except:
    import imp as importlib

import gdb

class __PythonReload(gdb.Command):
    """
    Reload Python code
    Usage: pr

    Reload Python modules, making best-effort to update all code to reflect
    new version.However, this is not perfect due to limitations of Python's
    importlib.reload().

    NOTE, that this command only reload modules whose prefix is in
    `vdb.cli.pr.prefixes`. So, in order to reload your project-specific
    GDB modules, you need to do:

        from vdb.cli import pr
        pr.prefixes.append('coolproject')

    assuming all your GDB scripts are either in top-level script
    loaded by GDB or in module(s) starting with "coolproject" and
    imported from there. By default, this command reloads __main__
    and vdb modules.
    """

    def __init__(self, name, cmd_type):
        super().__init__(name, cmd_type)
        self.prefixes = ["__main__", "vdb"]

    def invoke(self, args, from_tty):
        self()

    def __call__(self):
        def imports(module):
            """
            Return a list of modules imported by given `module`.

            This is mostly a guesswork, no guarantee it'd return
            all of them, but should reasonably handle both,

                import xyz

            and

                from abc import ijk as bfl

            """
            imports = set()

            def module_of(obj):
                """
                Given an object, return a module it came from
                (or itself, if the object is module itself).

                Again, just a best-effort guesswork.
                """
                if isinstance(obj, type(sys.modules["__main__"])):
                    return obj
                elif hasattr(obj, "__module__"):
                    imported = getattr(obj, "__module__")
                    if isinstance(imported, type(sys.modules["__main__"])):
                        return sys.modules[imported.__name__]
                    elif isinstance(imported, str):
                        if imported in sys.modules:
                            return sys.modules[imported]
                elif hasattr(obj, "__class__"):
                    return module_of(getattr(obj, "__class__"))
                return None

            for name in dir(module):
                m = module_of(getattr(module, name))
                if m != None and m != module:
                    imports.add(m)

            # print("%s imports:" % module.__name__)
            # for i in imports:
            #     print("  * %s" % i.__name__)

            return imports

        def reload(
            module=sys.modules["__main__"], prefixes=self.prefixes, reloaded=set()
        ):
            """
            Reload given module and all it's imported modules
            """
            try_reload = any(
                [module.__name__.startswith(prefix) for prefix in prefixes]
            )

            if try_reload:
                if module not in reloaded:
                    reloaded.add(module)

                    for imported in imports(module):
                        reload(imported, prefixes)

                    print("Reloading %s..." % module.__name__)
                    # Sigh, importlib.reload() cannot reload __main__ (for whatever reason)
                    # So, we have to "reload" it manually by re-evaluating the source in
                    # context of __main__ module.
                    #
                    # TODO: Is there a betpiter way? Following code looks a wee bit too
                    # fragile
                    try:
                        if module.__name__ == "__main__":
                            if hasattr(module.__loader__, "path"):
                                source_path = module.__loader__.path
                                if os.path.exists(source_path):
                                    with open(source_path) as source_file:
                                        source = source_file.read()
                                        exec(source, module.__dict__)
                        else:
                            importlib.reload(module)
                    except Exception as ex:
                        print("Failed to reload module '%s': %s" % (module.__name__, ex))

        reload()
        print("Done!")


pr = __PythonReload("pr", gdb.COMMAND_MAINTENANCE)

class __DumpObjectCmd(gdb.Command):
    """
    Dump contents or expression EXP
    Usage: do EXP [EXP...]

    An expression EXP can be either OOP as numerical constant
    (for example '0x1ff10028') or C/C++ expression which is
    evaluated in current frame (for example, 'kernel->header.module')
    """
    def invoke (self, args, from_tty):
        self(*gdb.string_to_argv(args))

    def __call__(self, *exprs):
        for expr in exprs:
            self.dump(expr)

    def complete(self, text, word):
        return gdb.COMPLETE_EXPRESSION

    def dump(self, expr):
        import vdb
        value = None
        if isinstance(expr, gdb.Value):
            value = expr
        else:
            try:
                value = vdb.parse_and_eval(expr)
            except:
                print("Failed to evaluate '%s'" % expr)
                return

        printer = gdb.default_visualizer(value)
        if not printer:
            ty = value.dynamic_type
            if ty.code == gdb.TYPE_CODE_STRUCT or (ty.code == gdb.TYPE_CODE_PTR and ty.target().code == gdb.TYPE_CODE_STRUCT):
                from vdb.printing import CxxPrettyPrinter
                printer = CxxPrettyPrinter(value)

        if not printer:
            print("%s (%s)" % ( value, value.dynamic_type))
        else:
            print("%s (%s)" % ( printer.to_string(), value.dynamic_type))
            for name, child in printer.children():
                pp = gdb.default_visualizer(child)
                if pp == None:
                    print("  %-20s:  %s (%s)" % ( name , child, child.dynamic_type ))
                else:
                    try:
                        print("  %-20s:  %s (%s)" % ( name , pp.to_string(), child.dynamic_type ))
                    except Exception as e:
                        print("  %-20s:  %s (%s) <error: %s>" % ( name , child, child.dynamic_type, e ))


do = __DumpObjectCmd('do', gdb.COMMAND_DATA)