From 260a7204a752291281712e17e3119039b8cd0212 Mon Sep 17 00:00:00 2001 From: patryk4815 Date: Wed, 20 Aug 2025 14:06:03 +0200 Subject: [PATCH] vmmap: fix riscv64 kernel (#3252) * vmmap: fix crash with pi.markers * fix refactor * vmmap: fix riscv64 `info mem` --- pwndbg/aglib/kernel/__init__.py | 58 ++++------- pwndbg/aglib/kernel/vmmap.py | 164 +++++++++++++++++++------------- 2 files changed, 121 insertions(+), 101 deletions(-) diff --git a/pwndbg/aglib/kernel/__init__.py b/pwndbg/aglib/kernel/__init__.py index c238ad574..29b76dfd5 100644 --- a/pwndbg/aglib/kernel/__init__.py +++ b/pwndbg/aglib/kernel/__init__.py @@ -427,49 +427,33 @@ class Aarch64Ops(ArchOps): return int(pwndbg.aglib.regs.SCTLR) & BIT(0) != 0 -_arch_paginginfo: ArchPagingInfo = None - - @pwndbg.lib.cache.cache_until("start") -def arch_paginginfo() -> ArchPagingInfo: - global _arch_paginginfo - if _arch_paginginfo is None: - if pwndbg.aglib.arch.name == "aarch64": - _arch_paginginfo = pwndbg.aglib.kernel.paging.Aarch64PagingInfo() - elif pwndbg.aglib.arch.name == "x86-64": - _arch_paginginfo = pwndbg.aglib.kernel.paging.x86_64PagingInfo() - return _arch_paginginfo - - -_arch_ops: ArchOps = None +def arch_paginginfo() -> ArchPagingInfo | None: + if pwndbg.aglib.arch.name == "aarch64": + return pwndbg.aglib.kernel.paging.Aarch64PagingInfo() + elif pwndbg.aglib.arch.name == "x86-64": + return pwndbg.aglib.kernel.paging.x86_64PagingInfo() + return None @pwndbg.lib.cache.cache_until("start") -def arch_ops() -> ArchOps: - global _arch_ops - if _arch_ops is None: - if pwndbg.aglib.arch.name == "aarch64": - _arch_ops = Aarch64Ops() - elif pwndbg.aglib.arch.name == "x86-64": - _arch_ops = x86_64Ops() - elif pwndbg.aglib.arch.name == "i386": - _arch_ops = i386Ops() - - return _arch_ops - - -_arch_symbols: pwndbg.aglib.kernel.symbol.ArchSymbols = None - +def arch_ops() -> ArchOps | None: + if pwndbg.aglib.arch.name == "aarch64": + return Aarch64Ops() + elif pwndbg.aglib.arch.name == "x86-64": + return x86_64Ops() + elif pwndbg.aglib.arch.name == "i386": + return i386Ops() + return None -def arch_symbols() -> pwndbg.aglib.kernel.symbol.ArchSymbols: - global _arch_symbols - if _arch_symbols is None: - if pwndbg.aglib.arch.name == "aarch64": - _arch_symbols = pwndbg.aglib.kernel.symbol.Aarch64Symbols() - elif pwndbg.aglib.arch.name == "x86-64": - _arch_symbols = pwndbg.aglib.kernel.symbol.x86_64Symbols() - return _arch_symbols +@pwndbg.lib.cache.cache_until("start") +def arch_symbols() -> pwndbg.aglib.kernel.symbol.ArchSymbols | None: + if pwndbg.aglib.arch.name == "aarch64": + return pwndbg.aglib.kernel.symbol.Aarch64Symbols() + elif pwndbg.aglib.arch.name == "x86-64": + return pwndbg.aglib.kernel.symbol.x86_64Symbols() + return None def ptr_size() -> int: diff --git a/pwndbg/aglib/kernel/vmmap.py b/pwndbg/aglib/kernel/vmmap.py index ed5e6c400..1ab31ae27 100644 --- a/pwndbg/aglib/kernel/vmmap.py +++ b/pwndbg/aglib/kernel/vmmap.py @@ -32,9 +32,8 @@ class KernelVmmap: self.pages = pages self.sections = None self.pi = pwndbg.aglib.kernel.arch_paginginfo() - if self.pi and not pwndbg.aglib.kernel.has_debug_symbols(): - return - self.sections = self.pi.markers() + if self.pi and pwndbg.aglib.kernel.has_debug_symbols(): + self.sections = self.pi.markers() def get_name(self, addr: int) -> str: if addr is None or self.sections is None: @@ -44,12 +43,12 @@ class KernelVmmap: _, next = self.sections[i + 1] if cur is None or next is None: continue - if addr >= cur and addr < next: + if cur <= addr < next: return name return None def adjust(self): - if self.pages is None or len(self.pages) == 0: + if self.pi is None or self.pages is None or len(self.pages) == 0: return for i, page in enumerate(self.pages): name = self.get_name(page.start) @@ -248,6 +247,84 @@ def kernel_vmmap_via_page_tables() -> Tuple[pwndbg.lib.memory.Page, ...]: monitor_info_mem_not_warned = True +def _parser_mem_info_line_x86(line: str) -> pwndbg.lib.memory.Page | None: + """ + Example response from `info mem`: + ``` + ffff903580000000-ffff903580099000 0000000000099000 -rw + ffff903580099000-ffff90358009b000 0000000000002000 -r- + ffff90358009b000-ffff903582200000 0000000002165000 -rw + ffff903582200000-ffff903582803000 0000000000603000 -r- + ``` + """ + + dash_idx = line.index("-") + space_idx = line.index(" ") + rspace_idx = line.rindex(" ") + + start = int(line[:dash_idx], 16) + end = int(line[dash_idx + 1 : space_idx], 16) + size = int(line[space_idx + 1 : rspace_idx], 16) + perm = line[rspace_idx + 1 :] + + flags = 0 + if "r" in perm: + flags |= 4 + if "w" in perm: + flags |= 2 + if "x" in perm: + flags |= 1 + + global monitor_info_mem_not_warned + if end - start != size and monitor_info_mem_not_warned: + print( + M.warn( + ( + "The vmmap output may be incorrect as `monitor info mem` output assertion/assumption\n" + "that end-start==size failed. The values are:\n" + "end=%#x; start=%#x; size=%#x; end-start=%#x\n" + "Note that this warning will not show up again in this Pwndbg/GDB session." + ) + % (end, start, size, end - start) + ) + ) + monitor_info_mem_not_warned = False + + return pwndbg.lib.memory.Page(start, size, flags, 0, "") + + +def _parser_mem_info_line_riscv64(line: str) -> pwndbg.lib.memory.Page | None: + """ + Example response from `info mem`: + ``` + vaddr paddr size attr + ---------------- ---------------- ---------------- ------- + 0000000000010000 00000000feece000 0000000000001000 r-xu-a- + 0000000000011000 00000000fefeb000 0000000000002000 r-xu-a- + 0000000000013000 00000000a0a7a000 0000000000002000 r-xu-a- + 0000000000015000 00000000bfe02000 0000000000002000 r-xu-a- + ``` + """ + + arr = line.split(" ", 3) + if len(arr) != 4: + raise ValueError("invalid line format") + + start, _, size, perm = arr + start = int(start, 16) + size = int(size, 16) + + flags = 0 + if "r" in perm: + flags |= 4 + if "w" in perm: + flags |= 2 + if "x" in perm: + flags |= 1 + + return pwndbg.lib.memory.Page(start, size, flags, 0, "") + + @pwndbg.lib.cache.cache_until("stop") def kernel_vmmap_via_monitor_info_mem() -> Tuple[pwndbg.lib.memory.Page, ...]: """ @@ -260,13 +337,6 @@ def kernel_vmmap_via_monitor_info_mem() -> Tuple[pwndbg.lib.memory.Page, ...]: See also: https://github.com/pwndbg/pwndbg/pull/685 (TODO: revisit with future QEMU versions) - - # Example output from the command: - # pwndbg> monitor info mem - # ffff903580000000-ffff903580099000 0000000000099000 -rw - # ffff903580099000-ffff90358009b000 0000000000002000 -r- - # ffff90358009b000-ffff903582200000 0000000002165000 -rw - # ffff903582200000-ffff903582803000 0000000000603000 -r- """ if not pwndbg.aglib.qemu.is_qemu_kernel(): return () @@ -275,69 +345,35 @@ def kernel_vmmap_via_monitor_info_mem() -> Tuple[pwndbg.lib.memory.Page, ...]: monitor_info_mem = pwndbg.dbg.selected_inferior().send_monitor("info mem") except pwndbg.dbg_mod.Error: # Exception should not happen in new qemu, can we clean up it? - monitor_info_mem = None - - is_error = monitor_info_mem is None or "unknown command" in monitor_info_mem - if is_error: # Older versions of QEMU/GDB may throw `gdb.error: "monitor" command # not supported by this target`. Newer versions will not throw, but will - # return a string starting with 'unknown command:'. We handle both of - # these cases in a `finally` block instead of an `except` block. - # TODO: Find out which other architectures don't support this command - if pwndbg.aglib.arch.name == "aarch64": - print( - M.error( - f"The {pwndbg.aglib.arch.name} architecture does" - " not support the `monitor info mem` command.\n" - "Run `help show kernel-vmmap` for other options." - ) - ) - return () + # return a string starting with 'unknown command:'. + monitor_info_mem = "unknown command" - lines = monitor_info_mem.splitlines() + parser_func = None + if pwndbg.aglib.arch.name in ("i386", "x86-64"): + parser_func = _parser_mem_info_line_x86 + elif pwndbg.aglib.arch.name == "rv64": + parser_func = _parser_mem_info_line_riscv64 - # Handle disabled PG - # This will prevent a crash on abstract architectures - if len(lines) == 1 and lines[0] == "PG disabled": + if parser_func is None or "unknown command" in monitor_info_mem: + print( + M.error( + f"The {pwndbg.aglib.arch.name} architecture does" + " not support the `monitor info mem` command.\n" + "Run `help show kernel-vmmap` for other options." + ) + ) return () - global monitor_info_mem_not_warned pages: List[pwndbg.lib.memory.Page] = [] - for line in lines: + for line in monitor_info_mem.splitlines(): try: - dash_idx = line.index("-") - space_idx = line.index(" ") - rspace_idx = line.rindex(" ") - - start = int(line[:dash_idx], 16) - end = int(line[dash_idx + 1 : space_idx], 16) - size = int(line[space_idx + 1 : rspace_idx], 16) + page = parser_func(line) except Exception: # invalid format continue - if end - start != size and monitor_info_mem_not_warned: - print( - M.warn( - ( - "The vmmap output may be incorrect as `monitor info mem` output assertion/assumption\n" - "that end-start==size failed. The values are:\n" - "end=%#x; start=%#x; size=%#x; end-start=%#x\n" - "Note that this warning will not show up again in this Pwndbg/GDB session." - ) - % (end, start, size, end - start) - ) - ) - monitor_info_mem_not_warned = False - perm = line[rspace_idx + 1 :] - - flags = 0 - if "r" in perm: - flags |= 4 - if "w" in perm: - flags |= 2 - if "x" in perm: - flags |= 1 - pages.append(pwndbg.lib.memory.Page(start, size, flags, 0, "")) + pages.append(page) return tuple(pages)