def test_set_pid(self): # Debug the running Python interpreter itself. prog = Program() self.assertIsNone(prog.platform) self.assertFalse(prog.flags & ProgramFlags.IS_LIVE) prog.set_pid(os.getpid()) self.assertEqual(prog.platform, host_platform) self.assertTrue(prog.flags & ProgramFlags.IS_LIVE) data = b"hello, world!" buf = ctypes.create_string_buffer(data) self.assertEqual(prog.read(ctypes.addressof(buf), len(data)), data) self.assertRaisesRegex( ValueError, "program memory was already initialized", prog.set_pid, os.getpid(), )
def test_different_programs_typedef(self): self.assertRaisesRegex( ValueError, "type is from different program", self.prog.typedef_type, "INT", Program().int_type("int", 4, True), )
def _for_each_block_device(prog: Program) -> Iterator[Object]: try: class_in_private = prog.cache["knode_class_in_device_private"] except KeyError: # Linux kernel commit 570d0200123f ("driver core: move # device->knode_class to device_private") (in v5.1) moved the list # node. class_in_private = prog.type("struct device_private").has_member("knode_class") prog.cache["knode_class_in_device_private"] = class_in_private devices = prog["block_class"].p.klist_devices.k_list.address_of_() if class_in_private: for device_private in list_for_each_entry( "struct device_private", devices, "knode_class.n_node" ): yield device_private.device else: yield from list_for_each_entry("struct device", devices, "knode_class.n_node")
def test_decode_enum_type_flags_incomplete(self): self.assertRaisesRegex( TypeError, "incomplete", decode_enum_type_flags, 2, Program().enum_type(None), )
def test_different_programs_function_parameter(self): self.assertRaisesRegex( ValueError, "type is from different program", self.prog.function_type, self.prog.void_type(), (TypeParameter(Program().int_type("int", 4, True)), ), )
def test_different_programs_function_return(self): self.assertRaisesRegex( ValueError, "type is from different program", self.prog.function_type, Program().int_type("int", 4, True), (), )
def test_different_programs_enum(self): self.assertRaisesRegex( ValueError, "type is from different program", self.prog.enum_type, None, Program().int_type("int", 4, True), (), )
def test_different_programs_compound(self): self.assertRaisesRegex( ValueError, "type is from different program", self.prog.struct_type, None, 4, (TypeMember(Program().int_type("int", 4, True)), ), )
def test_different_programs_complex(self): self.assertRaisesRegex( ValueError, "type is from different program", self.prog.complex_type, "double _Complex", 16, Program().float_type("double", 8), )
def test_not_core_dump(self): prog = Program() self.assertRaisesRegex(ValueError, "not an ELF core file", prog.set_core_dump, "/dev/null") with tempfile.NamedTemporaryFile() as f: f.write(create_elf_file(ET.EXEC, [])) f.flush() self.assertRaisesRegex(ValueError, "not an ELF core file", prog.set_core_dump, f.name)
def test_overlap_same_address_smaller_size(self): # Existing segment: |_______| # New segment: |___| prog = Program(MOCK_PLATFORM) segment1 = unittest.mock.Mock(side_effect=zero_memory_read) segment2 = unittest.mock.Mock(side_effect=zero_memory_read) prog.add_memory_segment(0xFFFF0000, 128, segment1) prog.add_memory_segment(0xFFFF0000, 64, segment2) prog.read(0xFFFF0000, 128) segment1.assert_called_once_with(0xFFFF0040, 64, 64, False) segment2.assert_called_once_with(0xFFFF0000, 64, 0, False)
def test_overlap_same_address_larger_size(self): # Existing segment: |___| # New segment: |_______| prog = Program() segment1 = unittest.mock.Mock(side_effect=zero_memory_read) segment2 = unittest.mock.Mock(side_effect=zero_memory_read) prog.add_memory_segment(0xffff0000, 64, segment1) prog.add_memory_segment(0xffff0000, 128, segment2) prog.read(0xffff0000, 128) segment1.assert_not_called() segment2.assert_called_once_with(0xffff0000, 128, 0, False)
def _for_each_block_device(prog: Program) -> Iterator[Object]: try: class_in_private = prog.cache["knode_class_in_device_private"] except KeyError: # We need a proper has_member(), but this is fine for now. class_in_private = any( member.name == "knode_class" for member in prog.type( "struct device_private").members # type: ignore[union-attr] ) prog.cache["knode_class_in_device_private"] = class_in_private devices = prog["block_class"].p.klist_devices.k_list.address_of_() if class_in_private: for device_private in list_for_each_entry("struct device_private", devices, "knode_class.n_node"): yield device_private.device else: yield from list_for_each_entry("struct device", devices, "knode_class.n_node")
def load_debug_info(prog: drgn.Program, dpaths: [str]) -> None: """ Iterates over all the paths provided (`dpaths`) and attempts to load any debug information it finds. If the path provided is a directory, the whole directory is traversed in search of debug info. """ for path in dpaths: if os.path.isfile(path): prog.load_debug_info([path]) elif os.path.isdir(path): kos = [] for (ppath, __, files) in os.walk(path): for i in files: if i.endswith(".ko"): kos.append(os.sep.join([ppath, i])) prog.load_debug_info(kos) else: print("sdb: " + path + " is not a regular file or directory")
def test_zero_fill(self): data = b"hello, world" prog = Program() with tempfile.NamedTemporaryFile() as f: f.write( create_elf_file( ET.CORE, [ ElfSection( p_type=PT.LOAD, vaddr=0xFFFF0000, data=data, memsz=len(data) + 4, ), ], )) f.flush() prog.set_core_dump(f.name) self.assertEqual(prog.read(0xFFFF0000, len(data) + 4), data + bytes(4))
def test_decode_enum_type_flags(self): prog = Program(MOCK_PLATFORM) for bit_numbers, flags in ( (True, self.FLAGS_BIT_NUMBERS), (False, self.FLAGS_VALUES), ): with self.subTest(bit_numbers=bit_numbers): type = prog.enum_type( None, prog.int_type("int", 4, True), [TypeEnumerator(*flag) for flag in flags], ) self.assertEqual( decode_enum_type_flags(4, type, bit_numbers), "UNDERLINE" ) self.assertEqual( decode_enum_type_flags(27, type, bit_numbers), "BOLD|ITALIC|0x18" )
def print_metaslab(prog: drgn.Program, msp, print_header, indent): spacemap = msp.ms_sm if print_header: print( "".ljust(indent), "ADDR".ljust(18), "ID".rjust(4), "OFFSET".rjust(16), "FREE".rjust(8), "FRAG".rjust(5), "UCMU".rjust(8), ) print("".ljust(indent), "-" * 65) free = msp.ms_size if spacemap != drgn.NULL(prog, spacemap.type_): free -= spacemap.sm_phys.smp_alloc ufrees = msp.ms_unflushed_frees.rt_space uallocs = msp.ms_unflushed_allocs.rt_space free = free + ufrees - uallocs uchanges_free_mem = msp.ms_unflushed_frees.rt_root.avl_numnodes uchanges_free_mem *= prog.type("range_seg_t").type.size uchanges_alloc_mem = msp.ms_unflushed_allocs.rt_root.avl_numnodes uchanges_alloc_mem *= prog.type("range_seg_t").type.size uchanges_mem = uchanges_free_mem + uchanges_alloc_mem print( "".ljust(indent), hex(msp).ljust(16), str(int(msp.ms_id)).rjust(4), hex(msp.ms_start).rjust(16), nicenum(free).rjust(8), end="", ) if msp.ms_fragmentation == -1: print("-".rjust(6), end="") else: print((str(msp.ms_fragmentation) + "%").rjust(6), end="") print(nicenum(uchanges_mem).rjust(9))
def test_overlap_within_segment(self): # Existing segment: |_______| # New segment: |___| prog = Program() segment1 = unittest.mock.Mock(side_effect=zero_memory_read) segment2 = unittest.mock.Mock(side_effect=zero_memory_read) prog.add_memory_segment(0xffff0000, 128, segment1) prog.add_memory_segment(0xffff0020, 64, segment2) prog.read(0xffff0000, 128) segment1.assert_has_calls([ unittest.mock.call(0xffff0000, 32, 00, False), unittest.mock.call(0xffff0060, 32, 96, False), ]) segment2.assert_called_once_with(0xffff0020, 64, 0, False)
def test_overlap_segment_head_and_tail(self): # Existing segment: |_______||_______| # New segment: |_______| prog = Program() segment1 = unittest.mock.Mock(side_effect=zero_memory_read) segment2 = unittest.mock.Mock(side_effect=zero_memory_read) segment3 = unittest.mock.Mock(side_effect=zero_memory_read) prog.add_memory_segment(0xffff0000, 128, segment1) prog.add_memory_segment(0xffff0080, 128, segment2) prog.add_memory_segment(0xffff0040, 128, segment3) prog.read(0xffff0000, 256) segment1.assert_called_once_with(0xffff0000, 64, 0, False) segment2.assert_called_once_with(0xffff00c0, 64, 64, False) segment3.assert_called_once_with(0xffff0040, 128, 0, False)
def _test_module_debug_info(self, use_proc_and_sys): old_use_proc_and_sys = (int( os.environ.get("DRGN_USE_PROC_AND_SYS_MODULES", "1")) != 0) with setenv("DRGN_USE_PROC_AND_SYS_MODULES", "1" if use_proc_and_sys else "0"): if old_use_proc_and_sys == use_proc_and_sys: prog = self.prog else: prog = Program() prog.set_kernel() prog.load_default_debug_info() self.assertEqual( prog.symbol(self.SYMBOL).address, self.symbol_address)
def test_pid_memory(self): data = b"hello, world!" buf = ctypes.create_string_buffer(data) address = ctypes.addressof(buf) # QEMU user-mode emulation doesn't seem to emulate /proc/$pid/mem # correctly on a 64-bit host with a 32-bit guest; see # https://gitlab.com/qemu-project/qemu/-/issues/698. Packit uses mock # to cross-compile and test packages, which in turn uses QEMU user-mode # emulation. Skip this test if /proc/$pid/mem doesn't work so that # those builds succeed. try: with open("/proc/self/mem", "rb") as f: f.seek(address) functional_proc_pid_mem = f.read(len(data)) == data except OSError: functional_proc_pid_mem = False if not functional_proc_pid_mem: self.skipTest("/proc/$pid/mem is not functional") prog = Program() prog.set_pid(os.getpid()) self.assertEqual(prog.read(ctypes.addressof(buf), len(data)), data)
def _test_by_pid(self, orc): old_orc = int(os.environ.get("DRGN_PREFER_ORC_UNWINDER", "0")) != 0 with setenv("DRGN_PREFER_ORC_UNWINDER", "1" if orc else "0"): if orc == old_orc: prog = self.prog else: prog = Program() prog.set_kernel() prog.load_default_debug_info() pid = fork_and_pause() wait_until(lambda: proc_state(pid) == "S") self.assertIn("pause", str(prog.stack_trace(pid))) os.kill(pid, signal.SIGKILL) os.waitpid(pid, 0)
def setUpClass(cls): super().setUpClass() with tempfile.NamedTemporaryFile() as core_dump_file: try: subprocess.check_call( [ "zstd", "--quiet", "--force", "--decompress", "--stdout", os.path.join(os.path.dirname(__file__), "sample.coredump.zst"), ], stdout=core_dump_file, ) except FileNotFoundError: raise unittest.SkipTest("zstd not found") cls.prog = Program() cls.prog.set_core_dump(core_dump_file.name)
def test_physical(self): data = b'hello, world' prog = Program() with tempfile.NamedTemporaryFile() as f: f.write( create_elf_file(ET.CORE, [ ElfSection( p_type=PT.LOAD, vaddr=0xffff0000, paddr=0xa0, data=data, ), ])) f.flush() prog.set_core_dump(f.name) self.assertEqual(prog.read(0xffff0000, len(data)), data) self.assertEqual(prog.read(0xa0, len(data), physical=True), data)
def mock_program(platform=MOCK_PLATFORM, *, segments=None, types=None, objects=None): def mock_find_type(kind, name, filename): if filename: return None for type in types: if type.kind == kind: try: type_name = type.name except AttributeError: try: type_name = type.tag except AttributeError: continue if type_name == name: return type return None def mock_object_find(prog, name, flags, filename): if filename: return None for obj in objects: if obj.name == name: if obj.value is not None: if flags & FindObjectFlags.CONSTANT: break elif obj.type.kind == TypeKind.FUNCTION: if flags & FindObjectFlags.FUNCTION: break elif flags & FindObjectFlags.VARIABLE: break else: return None return Object(prog, obj.type, address=obj.address, value=obj.value) prog = Program(platform) if segments is not None: add_mock_memory_segments(prog, segments) if types is not None: prog.add_type_finder(mock_find_type) if objects is not None: prog.add_object_finder(mock_object_find) return prog
def test_unsaved(self): data = b"hello, world" prog = Program() with tempfile.NamedTemporaryFile() as f: f.write( create_elf_file( ET.CORE, [ ElfSection( p_type=PT.LOAD, vaddr=0xFFFF0000, data=data, memsz=len(data) + 4, ), ], ) ) f.flush() prog.set_core_dump(f.name) with self.assertRaisesRegex(FaultError, "memory not saved in core dump") as cm: prog.read(0xFFFF0000, len(data) + 4) self.assertEqual(cm.exception.address, 0xFFFF000C)
def test_not_elf(self): prog = Program() self.assertRaisesRegex(FileFormatError, 'not an ELF file', prog.set_core_dump, '/dev/null')
def test_debug_info(self): Program().load_debug_info([])
def finder(kind, name, filename): if kind == TypeKind.TYPEDEF and name == "foo": prog = Program() return prog.typedef_type("foo", prog.void_type()) else: return None
def _get_printk_records_lockless(prog: Program, prb: Object) -> List[PrintkRecord]: ulong_size = sizeof(prog.type("unsigned long")) DESC_SV_BITS = ulong_size * 8 DESC_FLAGS_SHIFT = DESC_SV_BITS - 2 DESC_FLAGS_MASK = 3 << DESC_FLAGS_SHIFT DESC_ID_MASK = DESC_FLAGS_MASK ^ ((1 << DESC_SV_BITS) - 1) LOG_CONT = prog["LOG_CONT"].value_() desc_committed = prog["desc_committed"].value_() desc_finalized = prog["desc_finalized"].value_() def record_committed(current_id: int, state_var: int) -> bool: state_desc_id = state_var & DESC_ID_MASK state = 3 & (state_var >> DESC_FLAGS_SHIFT) return (current_id == state_desc_id) and (state == desc_committed or state == desc_finalized) desc_ring = prb.desc_ring descs = desc_ring.descs.read_() infos = desc_ring.infos.read_() desc_ring_mask = (1 << desc_ring.count_bits.value_()) - 1 text_data_ring = prb.text_data_ring text_data_ring_data = text_data_ring.data.read_() text_data_ring_mask = (1 << text_data_ring.size_bits) - 1 result = [] def add_record(current_id: int) -> None: idx = current_id & desc_ring_mask desc = descs[idx].read_() if not record_committed(current_id, desc.state_var.counter.value_()): return lpos_begin = desc.text_blk_lpos.begin & text_data_ring_mask lpos_next = desc.text_blk_lpos.next & text_data_ring_mask lpos_begin += ulong_size if lpos_begin == lpos_next: # Data-less record. return if lpos_begin > lpos_next: # Data wrapped. lpos_begin -= lpos_begin info = infos[idx].read_() text_len = info.text_len if lpos_next - lpos_begin < text_len: # Truncated record. text_len = lpos_next - lpos_begin caller_tid, caller_cpu = _caller_id(info.caller_id.value_()) context = {} subsystem = info.dev_info.subsystem.string_() device = info.dev_info.device.string_() if subsystem: context[b"SUBSYSTEM"] = subsystem if device: context[b"DEVICE"] = device result.append( PrintkRecord( text=prog.read(text_data_ring_data + lpos_begin, text_len), facility=info.facility.value_(), level=info.level.value_(), seq=info.seq.value_(), timestamp=info.ts_nsec.value_(), caller_tid=caller_tid, caller_cpu=caller_cpu, continuation=bool(info.flags.value_() & LOG_CONT), context=context, )) head_id = desc_ring.head_id.counter.value_() current_id = desc_ring.tail_id.counter.value_() while current_id != head_id: add_record(current_id) current_id = (current_id + 1) & DESC_ID_MASK add_record(current_id) return result