From 0405793d21538efe4504d145da389bd9718985b4 Mon Sep 17 00:00:00 2001 From: Thinker Lee Date: Sun, 15 Oct 2023 16:30:51 -0700 Subject: [PATCH] drgn.cli: look up symbols as missing in global namespace Introduce a new option --auto-find to make the interactive mode load the corresponding symbol from prog if the user code access a global variable that is undefined while the name is defined by the program being debugged. For example, you can access jiffies of the kernel directly without going through prog['jiffies']. print(jiffies) This line will be compiled and run. However, the auto-find mode will look into the code object and know that the code object is going to access a global variable named "jiffies". However, "jiffies" is not defined in the global namespace. The auto-find mode will check if the program being debugged defines a symbol with the same name and add it to the global namespace if there is. Signed-off-by: Thinker Lee --- drgn/cli.py | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/drgn/cli.py b/drgn/cli.py index 44839ec33..4a42b99b8 100644 --- a/drgn/cli.py +++ b/drgn/cli.py @@ -7,6 +7,7 @@ import argparse import builtins import code +import dis import importlib import logging import os @@ -16,6 +17,7 @@ import runpy import shutil import sys +import types from typing import Any, Callable, Dict, Optional import drgn @@ -27,6 +29,60 @@ logger = logging.getLogger("drgn") +def fill_global_ns(co_obj, obj_provider, global_ns, lvl=0): + '''Fill a global namespace for a co_obj. + + It determines the names that should be in the global namespace by + inspecting the bytecodes of the code object. It then looks up + objects through the object provider and adds them to the global + namespace if a name is missing. + + To support nested functions, it also looks into code objects + that are constants of the code object and recursively calls + itself to fill the global namespace for those code objects. + + ''' + for i, const in enumerate(co_obj.co_consts): + if type(const) is not types.CodeType: + continue + fill_global_ns(const, obj_provider, global_ns, lvl + 1) + for bc in dis.Bytecode(co_obj): + if bc.opname == 'LOAD_GLOBAL' or (lvl == 0 and bc.opname == 'LOAD_NAME' and bc.argval in co_obj.co_names): + name = bc.argval + if name in global_ns: + continue + if ('__builtins__' in global_ns) and (name in global_ns['__builtins__']): + continue + obj = obj_provider(name) + if obj is None: + continue + global_ns[name] = obj + + +class AutoFindConsole(code.InteractiveConsole): + def __init__(self, locals=None, filename="", **kwargs): + code.InteractiveConsole.__init__(self, locals, filename, **kwargs) + + def obj_provider(self, name): + prog = self.locals['prog'] + try: + return prog[name] + except KeyError: + pass + try: + return prog.type(name) + except LookupError: + pass + except SyntaxError: + pass + return None + + def runcode(self, codeobj): + global_ns = self.locals + fill_global_ns(codeobj, self.obj_provider, global_ns) + return code.InteractiveInterpreter.runcode(self, codeobj) + + class _LogFormatter(logging.Formatter): _LEVELS = ( (logging.DEBUG, "debug", "36"), @@ -230,6 +286,13 @@ def _main() -> None: ) parser.add_argument("--version", action="version", version=version) + parser.add_argument( + "--auto-find", + dest="auto_find", + action="store_true", + help="find symbols automatically without goingt through prog", + ) + args = parser.parse_args() if args.script: @@ -299,7 +362,7 @@ def _main() -> None: sys.path.insert(0, os.path.dirname(os.path.abspath(script))) runpy.run_path(script, init_globals={"prog": prog}, run_name="__main__") else: - run_interactive(prog) + run_interactive(prog, auto_find=args.auto_find) def run_interactive( @@ -307,6 +370,7 @@ def run_interactive( banner_func: Optional[Callable[[str], str]] = None, globals_func: Optional[Callable[[Dict[str, Any]], Dict[str, Any]]] = None, quiet: bool = False, + auto_find: bool = False, ) -> None: """ Run drgn's :ref:`interactive-mode` until the user exits. @@ -397,7 +461,11 @@ def run_interactive( sys.displayhook = _displayhook try: - code.interact(banner=banner, exitmsg="", local=init_globals) + if not auto_find: + code.interact(banner=banner, exitmsg="", local=init_globals) + else: + console = AutoFindConsole(locals=init_globals) + console.interact(banner=banner, exitmsg="") finally: try: readline.write_history_file(histfile)