Repository: pwndbg/pwndbg

Issue:
Support Pagetables for i386/x86_32 Targets
**Is your feature request related to a problem? Please describe.**
When using pwndbg on a 32 bit QEMU target, vmmap fails to retrieve page tables, with the error that it is not implemented. And prevents context from being opened at all.

**Describe the solution you'd like**
Add pagetable support for i386/x86_32 targets,  or suppress the error if an architecture is not supported so that context can still be used.

**Describe alternatives you've considered**
At the very least, this error should be surpressed to allow normal application function.

**Additional context**
This specifically occurred for me when I am targeting a 32 bit QEMU target with limited file system access.
See the following stack trace.
![image](https://user-images.githubusercontent.com/77745122/218270048-46a21926-00d9-44f4-b30f-b7df2193e4b7.png)

Support Pagetables for i386/x86_32 Targets
**Is your feature request related to a problem? Please describe.**
When using pwndbg on a 32 bit QEMU target, vmmap fails to retrieve page tables, with the error that it is not implemented. And prevents context from being opened at all.

**Describe the solution you'd like**
Add pagetable support for i386/x86_32 targets,  or suppress the error if an architecture is not supported so that context can still be used.

**Describe alternatives you've considered**
At the very least, this error should be surpressed to allow normal application function.

**Additional context**
This specifically occurred for me when I am targeting a 32 bit QEMU target with limited file system access.
See the following stack trace.
![image](https://user-images.githubusercontent.com/77745122/218270048-46a21926-00d9-44f4-b30f-b7df2193e4b7.png)



Discussion:
Are you seeing this with `qemu-user` or `qemu-system-i386`?
qemu-system-i386. It is integrated into a custom QEMU build for emulating the Original Xbox. (XEMU)
Thanks, I'll try to take a look this week. In the meantime, you can try `set kernel-vmmap none` to see if that will at least make pwndbg usable again, even if you don't have page table information.
Looking for Update on this @gsingh93 
Does a `monitor info mem` command work on this target? If so, can you see if `set kernel-vmmap monitor` solves your issue?
Are you seeing this with `qemu-user` or `qemu-system-i386`?
qemu-system-i386. It is integrated into a custom QEMU build for emulating the Original Xbox. (XEMU)
Thanks, I'll try to take a look this week. In the meantime, you can try `set kernel-vmmap none` to see if that will at least make pwndbg usable again, even if you don't have page table information.
Looking for Update on this @gsingh93 
Does a `monitor info mem` command work on this target? If so, can you see if `set kernel-vmmap monitor` solves your issue?

Resolving patch:
diff --git a/pwndbg/gdblib/kernel/__init__.py b/pwndbg/gdblib/kernel/__init__.py
--- a/pwndbg/gdblib/kernel/__init__.py
+++ b/pwndbg/gdblib/kernel/__init__.py
@@ -1,6 +1,8 @@
 import functools
 import math
 import re
+from abc import ABC
+from abc import abstractmethod
 from typing import Optional
 from typing import Tuple
 
@@ -103,7 +105,7 @@ def is_kaslr_enabled() -> bool:
     return "nokaslr" not in kcmdline()
 
 
-class ArchOps:
+class ArchOps(ABC):
     # More information on the physical memory model of the Linux kernel and
     # especially the mapping between pages and page frame numbers (pfn) can
     # be found at https://docs.kernel.org/mm/memory-model.html
@@ -111,27 +113,35 @@ class ArchOps:
     # use through kernel configuration, enabling support for additional models
     # in the page_to_pfn() and pfn_to_page() methods in the future.
 
+    @abstractmethod
     def page_size(self) -> int:
         raise NotImplementedError()
 
+    @abstractmethod
     def per_cpu(self, addr: gdb.Value, cpu=None):
         raise NotImplementedError()
 
+    @abstractmethod
     def virt_to_phys(self, virt: int) -> int:
         raise NotImplementedError()
 
+    @abstractmethod
     def phys_to_virt(self, phys: int) -> int:
         raise NotImplementedError()
 
+    @abstractmethod
     def phys_to_pfn(self, phys: int) -> int:
         raise NotImplementedError()
 
+    @abstractmethod
     def pfn_to_phys(self, pfn: int) -> int:
         raise NotImplementedError()
 
+    @abstractmethod
     def pfn_to_page(self, phys: int) -> int:
         raise NotImplementedError()
 
+    @abstractmethod
     def page_to_pfn(self, page: int) -> int:
         raise NotImplementedError()
 
@@ -154,29 +164,102 @@ def page_to_virt(self, page: int) -> int:
         return pfn_to_virt(page_to_pfn(page))
 
 
-class x86_64Ops(ArchOps):
+class x86Ops(ArchOps):
+    def page_size(self) -> int:
+        return 1 << self.page_shift
+
+    def phys_to_virt(self, phys: int) -> int:
+        return (phys + self.page_offset) % (1 << self.ptr_size)
+
+    def phys_to_pfn(self, phys: int) -> int:
+        return phys >> self.page_shift
+
+    def pfn_to_phys(self, pfn: int) -> int:
+        return pfn << self.page_shift
+
+    @property
+    @abstractmethod
+    def ptr_size(self) -> int:
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def page_shift(self) -> int:
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def page_offset(self) -> int:
+        raise NotImplementedError()
+
+    @staticmethod
+    def paging_enabled() -> bool:
+        return int(pwndbg.gdblib.regs.cr0) & BIT(31) != 0
+
+
+class i386Ops(x86Ops):
+    def __init__(self) -> None:
+        # https://elixir.bootlin.com/linux/v6.2/source/arch/x86/include/asm/page_32_types.h#L18
+        self._PAGE_OFFSET = int(kconfig()["CONFIG_PAGE_OFFSET"], 16)
+        self.START_KERNEL_map = self.PAGE_OFFSET
+
+    @property
+    def ptr_size(self) -> int:
+        return 32
+
+    @property
+    def page_offset(self) -> int:
+        return self._PAGE_OFFSET
+
+    @property
+    def page_shift(self) -> int:
+        # https://elixir.bootlin.com/linux/v6.2/source/arch/x86/include/asm/page_types.h#L10
+        return 12
+
+    def virt_to_phys(self, virt: int) -> int:
+        return (virt - self.page_offset) % (1 << 32)
+
+    def per_cpu(self, addr: gdb.Value, cpu: Optional[int] = None):
+        raise NotImplementedError()
+
+    def pfn_to_page(self, pfn: int) -> int:
+        raise NotImplementedError()
+
+    def page_to_pfn(self, page: int) -> int:
+        raise NotImplementedError()
+
+
+class x86_64Ops(x86Ops):
     def __init__(self) -> None:
         if self.uses_5lvl_paging():
             # https://elixir.bootlin.com/linux/v6.2/source/arch/x86/include/asm/page_64_types.h#L41
-            self.PAGE_OFFSET = 0xFF11000000000000
+            self._PAGE_OFFSET = 0xFF11000000000000
             # https://elixir.bootlin.com/linux/v6.2/source/arch/x86/include/asm/pgtable_64_types.h#L131
             self.VMEMMAP_START = 0xFFD4000000000000
         else:
             # https://elixir.bootlin.com/linux/v6.2/source/arch/x86/include/asm/page_64_types.h#L42
-            self.PAGE_OFFSET = 0xFFFF888000000000
+            self._PAGE_OFFSET = 0xFFFF888000000000
             # https://elixir.bootlin.com/linux/v6.2/source/arch/x86/include/asm/pgtable_64_types.h#L130
             self.VMEMMAP_START = 0xFFFFEA0000000000
 
         self.STRUCT_PAGE_SIZE = gdb.lookup_type("struct page").sizeof
         self.STRUCT_PAGE_SHIFT = int(math.log2(self.STRUCT_PAGE_SIZE))
 
-        # https://elixir.bootlin.com/linux/v6.2/source/arch/x86/include/asm/page_64_types.h#L50
         self.START_KERNEL_map = 0xFFFFFFFF80000000
-        self.PAGE_SHIFT = 12
         self.phys_base = 0x1000000
 
-    def page_size(self) -> int:
-        return 1 << self.PAGE_SHIFT
+    @property
+    def ptr_size(self) -> int:
+        return 64
+
+    @property
+    def page_offset(self) -> int:
+        return self._PAGE_OFFSET
+
+    @property
+    def page_shift(self) -> int:
+        # https://elixir.bootlin.com/linux/v6.2/source/arch/x86/include/asm/page_64_types.h#L50
+        return 12
 
     def per_cpu(self, addr: gdb.Value, cpu: Optional[int] = None):
         if cpu is None:
@@ -189,18 +272,9 @@ def per_cpu(self, addr: gdb.Value, cpu: Optional[int] = None):
 
     def virt_to_phys(self, virt: int) -> int:
         if virt < self.START_KERNEL_map:
-            return (virt - self.PAGE_OFFSET) % (1 << 64)
+            return (virt - self.page_offset) % (1 << 64)
         return ((virt - self.START_KERNEL_map) + self.phys_base) % (1 << 64)
 
-    def phys_to_virt(self, phys: int) -> int:
-        return (phys + self.PAGE_OFFSET) % (1 << 64)
-
-    def phys_to_pfn(self, phys: int) -> int:
-        return phys >> self.PAGE_SHIFT
-
-    def pfn_to_phys(self, pfn: int) -> int:
-        return pfn << self.PAGE_SHIFT
-
     def pfn_to_page(self, pfn: int) -> int:
         # assumption: SPARSEMEM_VMEMMAP memory model used
         # FLATMEM or SPARSEMEM not (yet) implemented
@@ -211,10 +285,6 @@ def page_to_pfn(self, page: int) -> int:
         # FLATMEM or SPARSEMEM not (yet) implemented
         return (page - self.VMEMMAP_START) >> self.STRUCT_PAGE_SHIFT
 
-    @staticmethod
-    def paging_enabled() -> bool:
-        return int(pwndbg.gdblib.regs.cr0) & BIT(31) != 0
-
     @staticmethod
     @requires_debug_syms()
     def cpu_feature_capability(feature: int) -> bool:
@@ -308,6 +378,8 @@ def arch_ops() -> ArchOps:
             _arch_ops = Aarch64Ops()
         elif pwndbg.gdblib.arch.name == "x86-64":
             _arch_ops = x86_64Ops()
+        elif pwndbg.gdblib.arch.name == "i386":
+            _arch_ops = i386Ops()
 
     return _arch_ops
 
@@ -441,7 +513,9 @@ def virt_to_pfn(virt: int) -> int:
 @requires_debug_syms()
 def paging_enabled() -> bool:
     arch_name = pwndbg.gdblib.arch.name
-    if arch_name == "x86-64":
+    if arch_name == "i386":
+        return i386Ops.paging_enabled()
+    elif arch_name == "x86-64":
         return x86_64Ops.paging_enabled()
     elif arch_name == "aarch64":
         return Aarch64Ops.paging_enabled()


Test patch: