Пример #1
0
 def test_uts_release_no_debug_info(self):
     prog = drgn.Program()
     prog.set_kernel()
     self.assertEqual(
         prog["UTS_RELEASE"].string_().decode(),
         os.uname().release,
     )
Пример #2
0
 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)
Пример #3
0
 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
Пример #4
0
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
Пример #5
0
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
Пример #6
0
 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)
Пример #7
0
    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
Пример #8
0
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)
Пример #9
0
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)
Пример #10
0
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
Пример #11
0
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
Пример #12
0
# 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")