From c10c8f840b39b7a6e23582f6970e172cc41b8799 Mon Sep 17 00:00:00 2001 From: Disconnect3d Date: Sat, 20 Aug 2022 22:35:45 +0200 Subject: [PATCH] Fix coredump debugging (#1079) * Fix coredump debugging This commit fixes our headaches with core files debugging. The TL;DR is that we will now try to parse `info proc mappings` and `maintenance info sections` to give users best possible UX/vmmaps information. Related: * https://sourceware.org/bugzilla/show_bug.cgi?id=29508 * https://github.com/pwndbg/pwndbg/issues/985 * https://github.com/pwndbg/pwndbg/issues/954 * cleanup * cleanup * Fix core dbg when EHDR map is not mapped --- pwndbg/elf.py | 5 +++ pwndbg/vmmap.py | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/pwndbg/elf.py b/pwndbg/elf.py index 9f5d83f68..12bfb5994 100644 --- a/pwndbg/elf.py +++ b/pwndbg/elf.py @@ -240,6 +240,11 @@ def get_ehdr(pointer): vmmap = pwndbg.vmmap.find(pointer) base = None + # If there is no vmmap for the requested address, we can't do much + # (e.g. it could have been unmapped for whatever reason) + if vmmap is None: + return None, None + # We first check if the beginning of the page contains the ELF magic if pwndbg.memory.read(vmmap.start, 4) == b'\x7fELF': base = vmmap.start diff --git a/pwndbg/vmmap.py b/pwndbg/vmmap.py index cab52fcd1..541f9143c 100644 --- a/pwndbg/vmmap.py +++ b/pwndbg/vmmap.py @@ -34,6 +34,13 @@ custom_pages = [] kernel_vmmap_via_pt = pwndbg.config.Parameter('kernel-vmmap-via-page-tables', True, 'When on, it reads vmmap for kernels via page tables, otherwise uses QEMU kernel\'s `monitor info mem` command') + +@pwndbg.memoize.reset_on_objfile +@pwndbg.memoize.reset_on_start +def is_corefile(): + return gdb.execute('info target', to_string=True).startswith('Local core dump file') + + @pwndbg.memoize.reset_on_start @pwndbg.memoize.reset_on_stop def get(): @@ -49,6 +56,12 @@ def get(): else: pages.extend(kernel_vmmap_via_monitor_info_mem()) + if not pages: + pages.extend(coredump_maps()) + + # TODO/FIXME: Do we still need it after coredump_maps()? + # Add tests for other cases and see if this is needed e.g. for QEMU user + # if not, remove the code below & cleanup other parts of Pwndbg codebase if not pages: # If debuggee is launched from a symlink the debuggee memory maps will be # labeled with symlink path while in normal scenario the /proc/pid/maps @@ -154,6 +167,86 @@ def clear_custom_page(): pwndbg.memoize.reset() +@pwndbg.memoize.reset_on_objfile +@pwndbg.memoize.reset_on_start +def coredump_maps(): + """ + Parses `info proc mappings` and `maintenance info sections` + and tries to make sense out of the result :) + """ + pages = [] + + for line in gdb.execute('info proc mappings', to_string=True).splitlines(): + # We look for lines like: + # ['0x555555555000', '0x555555556000', '0x1000', '0x1000', '/home/user/a.out'] + try: + start, _end, size, offset, objfile = line.split() + start, size, offset = int(start, 16), int(size, 16), int(offset, 16) + except (IndexError, ValueError): + continue + + # Note: we set flags=0 because we do not have this information here + pages.append(pwndbg.memory.Page(start, size, 0, offset, objfile)) + + for line in gdb.execute('maintenance info sections', to_string=True).splitlines(): + # We look for lines like: + # ['[9]', '0x00000000->0x00000150', 'at', '0x00098c40:', '.auxv', 'HAS_CONTENTS'] + # ['[15]', '0x555555555000->0x555555556000', 'at', '0x00001430:', 'load2', 'ALLOC', 'LOAD', 'READONLY', 'CODE', 'HAS_CONTENTS'] + try: + _idx, start_end, _at, offset, name, *flags_list = line.split() + start, end = map(lambda v: int(v, 16), start_end.split('->')) + offset = int(offset[:-1], 16) + except (IndexError, ValueError): + continue + + # Note: can we deduce anything from 'ALLOC', 'HAS_CONTENTS' or 'LOAD' flags? + flags = 0 + if 'READONLY' in flags_list: flags |= 4 + if 'DATA' in flags_list: flags |= 2 + if 'CODE' in flags_list: flags |= 1 + + # Now, if the section is already in pages, just add its perms + known_page = False + + for page in pages: + if start in page: + page.flags |= flags + known_page = True + break + + if known_page: + continue + + pages.append(pwndbg.memory.Page(start, end-start, flags, offset, name)) + + # If the last page starts on e.g. 0xffffffffff600000 it must be vsyscall + vsyscall_page = pages[-1] + if vsyscall_page.start > 0xffffffffff000000 and vsyscall_page.flags & 1: + vsyscall_page.objfile = '[vsyscall]' + + # Detect stack based on addresses in AUXV from stack memory + stack_addr = None + + # TODO/FIXME: Can we uxe `pwndbg.auxv.get()` for this somehow? + auxv = gdb.execute('info auxv', to_string=True).splitlines() + for line in auxv: + if 'AT_EXECFN' in line: + try: + stack_addr = int(line.split()[-2], 16) + except Exception as e: + pass + break + + if stack_addr is not None: + for page in pages: + if stack_addr in page: + page.objfile = '[stack]' + page.flags |= 6 + break + + return tuple(pages) + + @pwndbg.memoize.reset_on_start @pwndbg.memoize.reset_on_stop def proc_pid_maps():