def test_uts_release_no_debug_info(self): prog = drgn.Program() prog.set_kernel() self.assertEqual( prog["UTS_RELEASE"].string_().decode(), os.uname().release, )
def setUp(self): # We only want to create the Program once, so it's cached as a class # variable. If we can't run these tests for whatever reason, we also # cache that. if LinuxHelperTestCase.prog is not None: return if LinuxHelperTestCase.skip_reason is None: try: run_tests = int(os.environ["DRGN_RUN_LINUX_HELPER_TESTS"]) != 0 except (KeyError, ValueError): run_tests = True force_run = False else: force_run = run_tests if not run_tests: LinuxHelperTestCase.skip_reason = "env DRGN_RUN_LINUX_HELPER_TESTS=0" elif not force_run and os.geteuid() != 0: LinuxHelperTestCase.skip_reason = ( "Linux helper tests must be run as root " "(run with env DRGN_RUN_LINUX_HELPER_TESTS=1 to force") else: prog = drgn.Program() prog.set_kernel() try: prog.load_debug_info(main=True) LinuxHelperTestCase.prog = prog return except drgn.MissingDebugInfoError as e: if force_run: raise LinuxHelperTestCase.skip_reason = str(e) self.skipTest(LinuxHelperTestCase.skip_reason)
def setUpClass(cls): # We only want to create the Program once for all tests, so it's cached # as a class variable (in the base class). if LinuxVMCoreTestCase.prog is None: prog = drgn.Program() prog.set_core_dump(VMCORE_PATH) LinuxKernelTestCase._load_debug_info(prog) LinuxVMCoreTestCase.prog = prog
def setup_target(args: argparse.Namespace) -> drgn.Program: """ Based on the validated input from the command line, setup the drgn.Program for our target and its metadata. """ prog = drgn.Program() if args.core: try: prog.set_core_dump(args.core) except FileNotFoundError: print(f"sdb: no such file: '{args.core}'") sys.exit(2) # # This is currently a short-coming of drgn. Whenever we # open a crash/core dump we need to specify the vmlinux # or userland binary using the non-default debug info # load API. # args.symbol_search = [args.object] + args.symbol_search elif args.pid: prog.set_pid(args.pid) else: prog.set_kernel() if args.default_symbols: try: prog.load_default_debug_info() except drgn.MissingDebugInfoError as debug_info_err: # # If we encounter such an error it means that we can't # find the debug info for one or more kernel modules. # That's fine because the user may not need those, so # print a warning and proceed. # # Again because of the aforementioned short-coming of drgn # we quiet any errors when loading the *default debug info* # if we are looking at a crash/core dump. # if not args.quiet and not args.object: print("sdb: " + str(debug_info_err), file=sys.stderr) if args.symbol_search: try: load_debug_info(prog, args.symbol_search) except ( drgn.MissingDebugInfoError, OSError, ) as debug_info_err: # # See similar comment above # if not args.quiet: print("sdb: " + str(debug_info_err), file=sys.stderr) return prog
def setup_target() -> Optional[drgn.Program]: """ Create a drgn.Program instance and setup the SDB context for all the integration tests. If there is no crash dump to attach to this is going to be an empty drgn.Program. """ prog = drgn.Program() if not dump_exists(): return prog prog.set_core_dump(DUMP_PATH) load_debug_info(prog, [VMLX_PATH, MODS_PATH]) return prog
def setUpClass(cls): # We only want to create the Program once for all tests, so it's cached # as a class variable (in the base class). If we can't run these tests # for whatever reason, we also cache that. if LinuxKernelTestCase.prog is not None: return if LinuxKernelTestCase.skip_reason is None: try: run_tests = int(os.environ["DRGN_RUN_LINUX_KERNEL_TESTS"]) != 0 except (KeyError, ValueError): run_tests = True force_run = False else: force_run = run_tests if run_tests: prog = drgn.Program() try: prog.set_kernel() except PermissionError: if force_run: raise LinuxKernelTestCase.skip_reason = ( "Linux kernel tests must be run as root " "(run with env DRGN_RUN_LINUX_KERNEL_TESTS=1 to force)" ) except (FileNotFoundError, ValueError): if force_run: raise LinuxKernelTestCase.skip_reason = ( "Linux kernel tests require /proc/kcore " "(run with env DRGN_RUN_LINUX_KERNEL_TESTS=1 to force)" ) else: # Some of the tests use the loop module. Open loop-control # so that it is loaded. try: with open("/dev/loop-control", "r"): pass except FileNotFoundError: pass try: cls._load_debug_info(prog) LinuxKernelTestCase.prog = prog return except drgn.MissingDebugInfoError as e: if force_run: raise LinuxKernelTestCase.skip_reason = str(e) else: LinuxKernelTestCase.skip_reason = "env DRGN_RUN_LINUX_KERNEL_TESTS=0" raise unittest.SkipTest(LinuxKernelTestCase.skip_reason)
def test_module_debug_info(self): with open("/proc/modules", "r") as f: for line in f: if line.startswith("loop "): break else: self.skipTest("loop module is built in or not loaded") # An arbitrary symbol that we can use to check that the module debug # info was loaded. with open("/proc/kallsyms", "r") as f: for line in f: tokens = line.split() if tokens[2] == "loop_register_transfer": address = int(tokens[0], 16) break else: self.skipTest("loop_register_transfer symbol not found") # Test with and without using /proc and /sys. key = "DRGN_USE_PROC_AND_SYS_MODULES" old_value = os.environ.get(key) if old_value is None or int(old_value): new_value = "0" else: new_value = "1" try: os.environ[key] = new_value other_prog = drgn.Program() other_prog.set_kernel() other_prog.load_default_debug_info() for prog in (self.prog, other_prog): self.assertEqual( prog.symbol("loop_register_transfer").address, address) finally: if old_value is None: del os.environ[key] else: os.environ[key] = old_value
def main() -> None: python_version = '.'.join(str(v) for v in sys.version_info[:3]) libkdumpfile = f'with{"" if drgn._with_libkdumpfile else "out"} libkdumpfile' version = f'drgn {drgn.__version__} (using Python {python_version}, {libkdumpfile})' parser = argparse.ArgumentParser(prog='drgn', description='Scriptable debugger') program_group = parser.add_argument_group( title='program selection', ).add_mutually_exclusive_group() program_group.add_argument('-k', '--kernel', action='store_true', help='debug the running kernel (default)') program_group.add_argument('-c', '--core', metavar='PATH', type=str, help='debug the given core dump') program_group.add_argument( '-p', '--pid', metavar='PID', type=int, help='debug the running process with the given PID') symbol_group = parser.add_argument_group('debugging symbols') symbol_group.add_argument( '-s', '--symbols', metavar='PATH', type=str, action='append', help= 'load additional debugging symbols from the given file; this may option may be given more than once' ) symbol_group.add_argument( '--no-default-symbols', dest='default_symbols', action='store_false', help= "don't load any debugging symbols that were not explicitly added with -s" ) parser.add_argument( '-q', '--quiet', action='store_true', help= "don't print non-fatal warnings (e.g., about missing debugging information)" ) parser.add_argument( 'script', metavar='ARG', type=str, nargs=argparse.REMAINDER, help='script to execute instead of running in interactive mode') parser.add_argument('--version', action='version', version=version) args = parser.parse_args() prog = drgn.Program() if args.core is not None: prog.set_core_dump(args.core) elif args.pid is not None: prog.set_pid(args.pid or os.getpid()) else: prog.set_kernel() try: prog.load_debug_info(args.symbols or [], args.default_symbols) except drgn.MissingDebugInfoError as e: if not args.quiet: print(str(e), file=sys.stderr) init_globals: Dict[str, Any] = {'prog': prog} if args.script: sys.argv = args.script runpy.run_path(args.script[0], init_globals=init_globals, run_name='__main__') else: import atexit import readline from drgn.internal.rlcompleter import Completer init_globals['drgn'] = drgn drgn_globals = [ 'NULL', 'Object', 'cast', 'container_of', 'execscript', 'reinterpret', 'sizeof', ] for attr in drgn_globals: init_globals[attr] = getattr(drgn, attr) init_globals['__name__'] = '__main__' init_globals['__doc__'] = None histfile = os.path.expanduser('~/.drgn_history') try: readline.read_history_file(histfile) except FileNotFoundError: pass readline.parse_and_bind('tab: complete') readline.set_history_length(1000) atexit.register(readline.write_history_file, histfile) readline.set_completer(Completer(init_globals).complete) atexit.register(lambda: readline.set_completer(None)) sys.displayhook = displayhook banner = version + """ For help, type help(drgn). >>> import drgn >>> from drgn import """ + ', '.join(drgn_globals) if prog.flags & drgn.ProgramFlags.IS_LINUX_KERNEL: banner += '\n>>> from drgn.helpers.linux import *' module = importlib.import_module('drgn.helpers.linux') for name in module.__dict__['__all__']: init_globals[name] = getattr(module, name) code.interact(banner=banner, exitmsg='', local=init_globals)
def main() -> None: drgn_version = pkg_resources.get_distribution("drgn").version python_version = ".".join(str(v) for v in sys.version_info[:3]) libkdumpfile = f'with{"" if drgn._with_libkdumpfile else "out"} libkdumpfile' version = f"drgn {drgn_version} (using Python {python_version}, {libkdumpfile})" parser = argparse.ArgumentParser(prog="drgn", description="Scriptable debugger") program_group = parser.add_argument_group( title="program selection", ).add_mutually_exclusive_group() program_group.add_argument( "-k", "--kernel", action="store_true", help="debug the running kernel (default)" ) program_group.add_argument( "-c", "--core", metavar="PATH", type=str, help="debug the given core dump" ) program_group.add_argument( "-p", "--pid", metavar="PID", type=int, help="debug the running process with the given PID", ) symbol_group = parser.add_argument_group("debugging symbols") symbol_group.add_argument( "-s", "--symbols", metavar="PATH", type=str, action="append", help="load additional debugging symbols from the given file; this option may be given more than once", ) default_symbols_group = symbol_group.add_mutually_exclusive_group() default_symbols_group.add_argument( "--main-symbols", dest="default_symbols", action="store_const", const={"main": True}, help="only load debugging symbols for the main executable and those added with -s; " "for userspace programs, this is currently equivalent to --no-default-symbols", ) default_symbols_group.add_argument( "--no-default-symbols", dest="default_symbols", action="store_const", const={}, help="don't load any debugging symbols that were not explicitly added with -s", ) parser.add_argument( "-q", "--quiet", action="store_true", help="don't print non-fatal warnings (e.g., about missing debugging information)", ) parser.add_argument( "script", metavar="ARG", type=str, nargs=argparse.REMAINDER, help="script to execute instead of running in interactive mode", ) parser.add_argument("--version", action="version", version=version) args = parser.parse_args() prog = drgn.Program() if args.core is not None: prog.set_core_dump(args.core) elif args.pid is not None: prog.set_pid(args.pid or os.getpid()) else: prog.set_kernel() if args.default_symbols is None: args.default_symbols = {"default": True, "main": True} try: prog.load_debug_info(args.symbols, **args.default_symbols) except drgn.MissingDebugInfoError as e: if not args.quiet: print(str(e), file=sys.stderr) init_globals: Dict[str, Any] = {"prog": prog} if args.script: sys.argv = args.script runpy.run_path(args.script[0], init_globals=init_globals, run_name="__main__") else: import atexit import readline from drgn.internal.rlcompleter import Completer init_globals["drgn"] = drgn drgn_globals = [ "NULL", "Object", "cast", "container_of", "execscript", "reinterpret", "sizeof", ] for attr in drgn_globals: init_globals[attr] = getattr(drgn, attr) init_globals["__name__"] = "__main__" init_globals["__doc__"] = None histfile = os.path.expanduser("~/.drgn_history") try: readline.read_history_file(histfile) except OSError as e: if not isinstance(e, FileNotFoundError) and not args.quiet: print("could not read history:", str(e), file=sys.stderr) def write_history_file(): try: readline.write_history_file(histfile) except OSError as e: if not args.quiet: print("could not write history:", str(e), file=sys.stderr) atexit.register(write_history_file) readline.set_history_length(1000) readline.parse_and_bind("tab: complete") readline.set_completer(Completer(init_globals).complete) atexit.register(lambda: readline.set_completer(None)) sys.displayhook = displayhook banner = ( version + """ For help, type help(drgn). >>> import drgn >>> from drgn import """ + ", ".join(drgn_globals) ) if prog.flags & drgn.ProgramFlags.IS_LINUX_KERNEL: banner += "\n>>> from drgn.helpers.linux import *" module = importlib.import_module("drgn.helpers.linux") for name in module.__dict__["__all__"]: init_globals[name] = getattr(module, name) code.interact(banner=banner, exitmsg="", local=init_globals)
def setup_basic_mock_program() -> drgn.Program: # # We specify an architecture here so we can have consistent # results through explicit assumptions like size of pointers # and representation of integers. # platform = drgn.Platform( drgn.Architecture.X86_64, drgn.PlatformFlags.IS_LITTLE_ENDIAN | drgn.PlatformFlags.IS_64_BIT) prog = drgn.Program(platform) # # We create these types BEFORE registering the type callback # below and outside of the callback itself but access them # thorugh the closure of the outer function. This is because # calling prog.type() from within the callback itself can # lead to infinite recursion, and not using prog from withing # the callback means that we'd need to recreate basic types # like int and void *. # int_type = prog.type('int') voidp_type = prog.type('void *') struct_type = create_struct_type('test_struct', ['ts_int', 'ts_voidp'], [int_type, voidp_type]) def mock_type_find(kind: drgn.TypeKind, name: str, filename: Optional[str]) -> Optional[drgn.Type]: assert filename is None mocked_types = { 'test_struct': struct_type, } if name in mocked_types: assert kind == mocked_types[name].kind return mocked_types[name] return None prog.add_type_finder(mock_type_find) global_struct_addr = 0xffffffffc0a8aee0 def mock_object_find(prog: drgn.Program, name: str, flags: drgn.FindObjectFlags, filename: Optional[str]) -> Optional[drgn.Object]: assert filename is None assert flags == drgn.FindObjectFlags.ANY mock_objects = { 'global_int': (int_type, 0xffffffffc0a8aee0), 'global_void_ptr': (voidp_type, 0xffff88d26353c108), 'global_struct': (struct_type, global_struct_addr), } if name in mock_objects: type_, addr = mock_objects[name] return drgn.Object(prog, type=type_, address=addr) return None prog.add_object_finder(mock_object_find) def fake_memory_reader(address: int, count: int, physical: int, offset: bool) -> bytes: assert address == physical assert not offset fake_mappings = { # address of global_struct and its first member ts_int global_struct_addr: b'\x01\x00\x00\x00' } assert address in fake_mappings assert count == len(fake_mappings[address]) return fake_mappings[address] prog.add_memory_segment(0, 0xffffffffffffffff, fake_memory_reader) return prog
def setup_basic_mock_program() -> drgn.Program: # pylint: disable=too-many-locals # # We specify an architecture here so we can have consistent # results through explicit assumptions like size of pointers # and representation of integers. # platform = drgn.Platform( drgn.Architecture.X86_64, drgn.PlatformFlags.IS_LITTLE_ENDIAN | drgn.PlatformFlags.IS_64_BIT) prog = drgn.Program(platform) # # We create these types BEFORE registering the type callback # below and outside of the callback itself but access them # thorugh the closure of the outer function. This is because # calling prog.type() from within the callback itself can # lead to infinite recursion, and not using prog from withing # the callback means that we'd need to recreate basic types # like int and void *. # int_type = prog.type('int') int_array_type = prog.type('int[2]') voidp_type = prog.type('void *') mocked_types: Dict[str, drgn.Type] = {} def mock_type_find(kind: drgn.TypeKind, name: str, filename: Optional[str]) -> Optional[drgn.Type]: assert filename is None if name in mocked_types: assert kind == mocked_types[name].kind return mocked_types[name] return None prog.add_type_finder(mock_type_find) # # More complex types are added to the mocked_types table in # an ad-hoc way here. # struct_type = create_struct_type(prog, 'test_struct', ['ts_int', 'ts_voidp', 'ts_array'], [int_type, voidp_type, int_array_type]) mocked_types['test_struct'] = struct_type structp_type = prog.type('struct test_struct *') complex_struct_type = create_struct_type( prog, 'complex_struct', ['cs_structp', 'cs_struct', 'cs_structp_null'], [structp_type, struct_type, structp_type]) mocked_types['complex_struct'] = complex_struct_type global_void_ptr_addr = 0xffff88d26353c108 global_int_addr = 0xffffffffc0000000 global_struct_addr = 0xffffffffc0a8aee0 global_cstruct_addr = 0xffffffffd0000000 def mock_object_find(prog: drgn.Program, name: str, flags: drgn.FindObjectFlags, filename: Optional[str]) -> Optional[drgn.Object]: assert filename is None assert flags == drgn.FindObjectFlags.ANY mock_objects = { 'global_void_ptr': (voidp_type, global_void_ptr_addr), 'global_int': (int_type, global_int_addr), 'global_struct': (struct_type, global_struct_addr), 'global_cstruct': (complex_struct_type, global_cstruct_addr), } if name in mock_objects: type_, addr = mock_objects[name] return drgn.Object(prog, type=type_, address=addr) return None prog.add_object_finder(mock_object_find) # # Remember that our mocks are little-endian! # fake_mappings = { # # Initialize global_int in memory (value = 0x01020304). # global_int_addr: b'\x04\x03\x02\x01', # # Initialize global_struct in memory. # global_struct = { # .ts_int = 0x00000001 # .ts_voidp = <address of global_int> # .ts_array = [0x0f0f0f0f, 0xdeadbeef] # } # global_struct_addr: b'\x01\x00\x00\x00', (global_struct_addr + int_type.size): global_int_addr.to_bytes(8, byteorder='little'), (global_struct_addr + int_type.size + voidp_type.size): b'\x0f\x0f\x0f\x0f', (global_struct_addr + (2 * int_type.size) + voidp_type.size): b'\xef\xbe\xad\xde', # # Initialize global_cstruct in memory. # global_cstruct = { # .cs_structp = <addres of global_struct> # .cs_struct = { # .ts_int = 0x0000000A # .ts_voidp = <address of global_cstruct> # .ts_array = [0x80000000, 0x7fffffff] # } # .cs_structp_null = 0x0 (NULL) # } # global_cstruct_addr: global_struct_addr.to_bytes(8, byteorder='little'), (global_cstruct_addr + structp_type.size): b'\x0A\x00\x00\x00', (global_cstruct_addr + structp_type.size + int_type.size): global_cstruct_addr.to_bytes(8, byteorder='little'), (global_cstruct_addr + structp_type.size + int_type.size + voidp_type.size): b'\x00\x00\x00\x80', (global_cstruct_addr + structp_type.size + (2 * int_type.size) + voidp_type.size): b'\xff\xff\xff\x7f', (global_cstruct_addr + structp_type.size + (3 * int_type.size) + voidp_type.size): b'\x00\x00\x00\x00\x00\x00\x00\x00', } def fake_memory_reader(address: int, count: int, physical: int, offset: bool) -> bytes: assert address == physical assert not offset assert address in fake_mappings assert count == len(fake_mappings[address]) return fake_mappings[address] prog.add_memory_segment(0, 0xffffffffffffffff, fake_memory_reader) return prog
# This module collects helpers related to working with the mlxsw Spectrum # driver. But since it's a module, the drgn `prog' variable is not available. So # we need to construct it anew. Then once we have it, we can export it to the # tool, and use drgn as just a library, and run the tool through python. import sys import drgn import drgn.helpers.linux as helpers prog = drgn.Program() prog.set_kernel() try: prog.load_debug_info(None, default=True, main=True) except drgn.MissingDebugInfoError as e: print(str(e), file=sys.stderr) class MlxswSpPort: def __init__(self, _mlxsw_sp_port): self._mlxsw_sp_port = _mlxsw_sp_port def __getattr__(self, key): return getattr(self._mlxsw_sp_port, key) def name(self): dev = self._mlxsw_sp_port.dev if dev.value_() == 0: raise RuntimeError("No netdev associated with the port") return dev.name.string_().decode("utf-8")