Repository: pwndbg/pwndbg
Pull request #2771: PwndbgArchitecture
Author: login: OBarronCS
Created: 2025-03-05T07:51:15Z | Merged: 2025-04-24T11:02:07Z

This PR introduces a class based mechanism to refer to information related to the current architecture being debugged, as well as other related context, with the goal of simplifying and cleaning up code.

See original PR and discussion here: #2367




Diff:
diff --git a/pwndbg/aglib/__init__.py b/pwndbg/aglib/__init__.py
index 08f0fe37580..ec6227f56af 100644
--- a/pwndbg/aglib/__init__.py
+++ b/pwndbg/aglib/__init__.py
@@ -1,10 +1,13 @@
 from __future__ import annotations
 
 from pwndbg.aglib import arch as arch_mod
-from pwndbg.aglib.arch import arch as arch
+from pwndbg.aglib.arch import PwndbgArchitecture
+from pwndbg.aglib.arch import get_pwndbg_architecture
 
 regs = None
 
+arch: PwndbgArchitecture = get_pwndbg_architecture("i386")
+
 
 def load_aglib():
     import pwndbg.aglib.argv
@@ -34,3 +37,8 @@ def load_aglib():
     regs_: regs_mod.module = regs_mod
     global regs
     regs = regs_
+
+
+def set_arch(pwndbg_arch: PwndbgArchitecture):
+    global arch
+    arch = pwndbg_arch
diff --git a/pwndbg/aglib/arch.py b/pwndbg/aglib/arch.py
index 4d674cff555..1341fddc933 100644
--- a/pwndbg/aglib/arch.py
+++ b/pwndbg/aglib/arch.py
@@ -1,39 +1,380 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
+from typing import List
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_AARCH64
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_LOONGARCH
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_SYSTEMZ
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_LOONGARCH64
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+import pwndbg.aglib.disasm
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchAttribute
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+
+def register_arch(arch: PwndbgArchitecture):
+    registered_architectures[arch.name] = arch
+
+
+def get_pwndbg_architecture(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture | None:
+    if name not in registered_architectures:
+        return None
+
+    return registered_architectures[name]
+
+
+class PwndbgArchitecture(ArchDefinition):
+    """
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
+
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
+    """
+
+    ### All subclasses must provide values for the following attributes
+
+    max_instruction_size: int
+
+    ###
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    endian: EndianType
+    ptrsize: int
+    """Pointer size in bytes"""
+    ptrbits: int
+    """Pointer size in bits"""
+    ptrmask: int
+    function_abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    attributes: List[ArchAttribute]
+
+    fmts: Dict[int, str]
+    fmt: str
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.platform = arch_definition.platform
+        self.attributes = arch_definition.attributes
+
+        self.endian: EndianType = arch_definition.endian
+
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        self.function_abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int] | None:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return None
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    max_instruction_size = 16
+
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    32-bit mode x86
+    """
+
+    max_instruction_size = 16
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
 
-    Return None if the Thumb bit is not relevent to the current architecture
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    max_instruction_size = 16
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    max_instruction_size = 4
+
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.disassembly.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
 
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    max_instruction_size = 4
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    max_instruction_size = 4
+
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_AARCH64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    max_instruction_size = 4
+
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_PPC, CS_MODE_64)
+
+
+class SparcArch(PwndbgArchitecture):
+    max_instruction_size = 4
+
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        mode = CS_MODE_V9 if self.ptrsize == 8 else 0
+        return (CS_ARCH_SPARC, mode)
+
+
+class RISCV32Arch(PwndbgArchitecture):
+    max_instruction_size = 22
+
+    def __init__(self) -> None:
+        super().__init__("rv32")
 
-arch: Arch = Arch("i386", 4, "little")
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV32 | CS_MODE_RISCVC)
+
+
+class RISCV64Arch(PwndbgArchitecture):
+    max_instruction_size = 22
+
+    def __init__(self) -> None:
+        super().__init__("rv64")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV64 | CS_MODE_RISCVC)
+
+
+class MipsArch(PwndbgArchitecture):
+    max_instruction_size = 8
+
+    def __init__(self) -> None:
+        super().__init__("mips")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        extra = 0
+        for attribute in self.attributes:
+            if attribute.cs_mode is not None:
+                extra |= attribute.cs_mode
+
+        if extra == 0:
+            extra = CS_MODE_MIPS64 if self.ptrsize == 8 else CS_MODE_MIPS32
+
+        return (CS_ARCH_MIPS, extra)
+
+
+class Loongarch64Arch(PwndbgArchitecture):
+    max_instruction_size = 4
+
+    def __init__(self) -> None:
+        super().__init__("loongarch64")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_LOONGARCH, CS_MODE_LOONGARCH64)
+
+
+class S390xArch(PwndbgArchitecture):
+    max_instruction_size = 6
+
+    def __init__(self) -> None:
+        super().__init__("s390x")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_SYSTEMZ, 0)
+
+
+# Register the architecture classes
+all_arches = [
+    AMD64Arch(),
+    i386Arch(),
+    i8086Arch(),
+    ArmArch(),
+    ArmCortexArch(),
+    AArch64Arch(),
+    PowerPCArch(),
+    SparcArch(),
+    RISCV32Arch(),
+    RISCV64Arch(),
+    MipsArch(),
+    Loongarch64Arch(),
+    S390xArch(),
+]
+
+for arch in all_arches:
+    register_arch(arch)
+
+
+def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
+    thumb_bit = pwndbg.aglib.arch.read_thumb_bit()
+    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
 
 
 def update() -> None:
@@ -42,4 +383,14 @@ def update() -> None:
     pwnlib.context.context.arch = PWNLIB_ARCH_MAPPINGS.get(a.name, "none")
     pwnlib.context.context.bits = a.ptrsize * 8
 
-    arch.update(a.name, a.ptrsize, a.endian)
+    if a.name != pwndbg.aglib.arch.name:
+        pwndbg_arch = get_pwndbg_architecture(a.name)
+        if pwndbg_arch is None:
+            raise pwndbg.dbg_mod.Error(
+                f"Unsupported architecture: {a.name}. "
+                f"It may be that Pwndbg is not correctly categorizing the architecture. "
+                f"Please file a bug report. "
+            )
+        pwndbg.aglib.set_arch(pwndbg_arch)
+
+    pwndbg.aglib.arch.update(a)
diff --git a/pwndbg/aglib/disasm/arch.py b/pwndbg/aglib/disasm/arch.py
index 9e2ddb3ea24..76a91bf3f71 100644
--- a/pwndbg/aglib/disasm/arch.py
+++ b/pwndbg/aglib/disasm/arch.py
@@ -605,11 +605,9 @@ def _get_syscall_arch_info(self, instruction) -> Tuple[str, str]:
 
         Elements of the tuple will be None to indicate it's not a syscall
         """
-        try:
-            abi = pwndbg.lib.abi.ABI.syscall()
-        except KeyError:
+        if pwndbg.aglib.arch.syscall_abi is None:
             return (None, None)
-        return (pwndbg.aglib.arch.name, abi.syscall_register)
+        return (pwndbg.aglib.arch.name, pwndbg.aglib.arch.syscall_abi.syscall_register)
 
     def _enhance_conditional(self, instruction: PwndbgInstruction, emu: Emulator) -> None:
         """
diff --git a/pwndbg/aglib/disasm/disassembly.py b/pwndbg/aglib/disasm/disassembly.py
index 21af1e244c5..c13353a1ab5 100644
--- a/pwndbg/aglib/disasm/disassembly.py
+++ b/pwndbg/aglib/disasm/disassembly.py
@@ -38,51 +38,13 @@
 from pwndbg.dbg import EventType
 from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 
-if pwndbg.dbg.is_gdblib_available():
-    import gdb
-
-
-CapstoneArch = {
-    "arm": CS_ARCH_ARM,
-    "armcm": CS_ARCH_ARM,
-    "aarch64": CS_ARCH_AARCH64,
-    "i386": CS_ARCH_X86,
-    "i8086": CS_ARCH_X86,
-    "x86-64": CS_ARCH_X86,
-    "powerpc": CS_ARCH_PPC,
-    "mips": CS_ARCH_MIPS,
-    "sparc": CS_ARCH_SPARC,
-    "rv32": CS_ARCH_RISCV,
-    "rv64": CS_ARCH_RISCV,
-    "s390x": CS_ARCH_SYSTEMZ,
-    "loongarch64": CS_ARCH_LOONGARCH,
-}
-
 CapstoneEndian = {
     "little": CS_MODE_LITTLE_ENDIAN,
     "big": CS_MODE_BIG_ENDIAN,
 }
 
-CapstoneMode = {4: CS_MODE_32, 8: CS_MODE_64}
-
 CapstoneSyntax = {"intel": CS_OPT_SYNTAX_INTEL, "att": CS_OPT_SYNTAX_ATT}
 
-# For variable-instruction-width architectures
-# (x86 and amd64), we keep a cache of instruction
-# sizes, and where the end of the instruction falls.
-#
-# This allows us to consistently disassemble backward.
-VariableInstructionSizeMax = {
-    "i386": 16,
-    "x86-64": 16,
-    "i8086": 16,
-    "mips": 8,
-    "rv32": 22,
-    "rv64": 22,
-    "s390x": 6,
-    "loongarch64": 4,
-}
-
 
 # Caching strategy:
 # To ensure we don't have stale register/memory information in our cached PwndbgInstruction,
@@ -136,19 +98,17 @@ def clear_on_reg_mem_change() -> None:
 
 
 @pwndbg.lib.cache.cache_until("objfile")
-def get_disassembler_cached(arch, ptrsize: int, endian, extra=None):
-    arch = CapstoneArch[arch]
-
-    if extra is None:
-        mode = CapstoneMode[ptrsize]
+def get_disassembler(address: int, cs_info: Tuple[int, int] = None):
+    if cs_info is not None:
+        arch, mode = cs_info
     else:
-        mode = extra
+        arch, mode = pwndbg.aglib.arch.get_capstone_constants(address)
 
-    mode |= CapstoneEndian[endian]
-
-    flavor = pwndbg.dbg.x86_disassembly_flavor()
+    mode |= CapstoneEndian[pwndbg.aglib.arch.endian]
 
     cs = Cs(arch, mode)
+
+    flavor = pwndbg.dbg.x86_disassembly_flavor()
     try:
         cs.syntax = CapstoneSyntax[flavor]
     except CsError:
@@ -157,77 +117,6 @@ def get_disassembler_cached(arch, ptrsize: int, endian, extra=None):
     return cs
 
 
-def get_disassembler(address):
-    if pwndbg.aglib.arch.name == "armcm":
-        thumb_mode = emulated_arm_mode_cache[address]
-        if thumb_mode is None:
-            thumb_mode = pwndbg.aglib.regs.xpsr & (1 << 24)
-        # novermin
-        extra = (CS_MODE_MCLASS | CS_MODE_THUMB) if thumb_mode else CS_MODE_MCLASS
-
-    elif pwndbg.aglib.arch.name in ("arm", "aarch64"):
-        thumb_mode = emulated_arm_mode_cache[address]
-        if thumb_mode is None:
-            thumb_mode = pwndbg.aglib.regs.cpsr & (1 << 5)
-        extra = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
-
-    elif pwndbg.aglib.arch.name == "sparc":
-        if pwndbg.dbg.is_gdblib_available() and "v9" in gdb.newest_frame().architecture().name():
-            extra = CS_MODE_V9
-        else:
-            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
-            extra = 0
-
-    elif pwndbg.aglib.arch.name == "i8086":
-        extra = CS_MODE_16
-
-    elif pwndbg.aglib.arch.name == "mips":
-        if pwndbg.dbg.is_gdblib_available():
-            # Example: "mips:isa64r2"
-            raw_arch_name = gdb.newest_frame().architecture().name()
-
-            if "isa32r2" in raw_arch_name:
-                extra = CS_MODE_MIPS32R2
-            elif "isa32r3" in raw_arch_name:
-                extra = CS_MODE_MIPS32R3
-            elif "isa32r5" in raw_arch_name:
-                extra = CS_MODE_MIPS32R5
-            elif "isa32r6" in raw_arch_name:
-                extra = CS_MODE_MIPS32R6
-            elif "isa64r2" in raw_arch_name:
-                extra = CS_MODE_MIPS64R2
-            elif "isa64r3" in raw_arch_name:
-                extra = CS_MODE_MIPS64R3
-            elif "isa64r5" in raw_arch_name:
-                extra = CS_MODE_MIPS64R5
-            elif "isa64r6" in raw_arch_name:
-                extra = CS_MODE_MIPS64R6
-            elif "micromips" in raw_arch_name:
-                extra = CS_MODE_MICRO
-            elif "mips5" in raw_arch_name:
-                extra = CS_MODE_MIPS5
-            elif pwndbg.aglib.arch.ptrsize == 64:
-                extra = CS_MODE_MIPS64
-            else:
-                extra = CS_MODE_MIPS32
-
-    elif pwndbg.aglib.arch.name == "rv32":
-        extra = CS_MODE_RISCV32 | CS_MODE_RISCVC  # novermin
-    elif pwndbg.aglib.arch.name == "rv64":
-        extra = CS_MODE_RISCV64 | CS_MODE_RISCVC  # novermin
-    elif pwndbg.aglib.arch.name == "s390x":
-        # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
-        extra = 0
-    elif pwndbg.aglib.arch.name == "loongarch64":
-        extra = CS_MODE_LOONGARCH64
-    else:
-        extra = None
-
-    return get_disassembler_cached(
-        pwndbg.aglib.arch.name, pwndbg.aglib.arch.ptrsize, pwndbg.aglib.arch.endian, extra
-    )
-
-
 def get_one_instruction(
     address,
     emu: pwndbg.emu.emulator.Emulator = None,
@@ -245,12 +134,12 @@ def get_one_instruction(
         if cached is not None:
             return cached
 
-    if pwndbg.aglib.arch.name not in CapstoneArch:
+    cs_info = pwndbg.aglib.arch.get_capstone_constants(address)
+    if cs_info is None:
         return ManualPwndbgInstruction(address)
 
-    md = get_disassembler(address)
-    size = VariableInstructionSizeMax.get(pwndbg.aglib.arch.name, 4)
-    data = pwndbg.aglib.memory.read(address, size, partial=True)
+    md = get_disassembler(address, cs_info)
+    data = pwndbg.aglib.memory.read(address, pwndbg.aglib.arch.max_instruction_size, partial=True)
     for ins in md.disasm(bytes(data), address, 1):
         pwn_ins: PwndbgInstruction = PwndbgInstructionImpl(ins)
 
diff --git a/pwndbg/arguments.py b/pwndbg/arguments.py
index f4e385ba9d4..f3b302d5da4 100644
--- a/pwndbg/arguments.py
+++ b/pwndbg/arguments.py
@@ -44,9 +44,9 @@ def get(instruction: PwndbgInstruction) -> List[Tuple[pwndbg.lib.functions.Argum
         return []
 
     if instruction.call_like:
-        try:
-            abi = pwndbg.lib.abi.ABI.default()
-        except KeyError:
+        abi = pwndbg.aglib.arch.function_abi
+
+        if abi is None:
             return []
 
         target = instruction.target
@@ -60,13 +60,10 @@ def get(instruction: PwndbgInstruction) -> List[Tuple[pwndbg.lib.functions.Argum
     elif CS_GRP_INT in instruction.groups:
         # Get the syscall number and name
         name = instruction.syscall_name
-        try:
-            abi = pwndbg.lib.abi.ABI.syscall()
-        except KeyError:
-            return []
+        abi = pwndbg.aglib.arch.syscall_abi
         target = None
 
-        if name is None:
+        if name is None or abi is None:
             return []
     else:
         return []
@@ -115,8 +112,7 @@ def get(instruction: PwndbgInstruction) -> List[Tuple[pwndbg.lib.functions.Argum
     return result
 
 
-def argname(n: int, abi: pwndbg.lib.abi.ABI | None = None) -> str:
-    abi = abi or pwndbg.lib.abi.ABI.default()
+def argname(n: int, abi: pwndbg.lib.abi.ABI) -> str:
     regs = abi.register_arguments
 
     if n < len(regs):
@@ -131,7 +127,11 @@ def argument(n: int, abi: pwndbg.lib.abi.ABI | None = None) -> int:
     instruction.
     Works only for ABIs that use registers for arguments.
     """
-    abi = abi or pwndbg.lib.abi.ABI.default()
+    abi = abi or pwndbg.aglib.arch.function_abi
+    if abi is None:
+        raise pwndbg.dbg_mod.Error(
+            f"Function ABI not defined for current architecture, {pwndbg.aglib.arch.function_abi}"
+        )
     regs = abi.register_arguments
 
     if n < len(regs):
@@ -149,7 +149,9 @@ def arguments(abi: pwndbg.lib.abi.ABI | None = None):
     Yields (arg_name, arg_value) tuples for arguments from a given ABI.
     Works only for ABIs that use registers for arguments.
     """
-    abi = abi or pwndbg.lib.abi.ABI.default()
+    abi = abi or pwndbg.aglib.arch.function_abi
+    if abi is None:
+        return []
     regs = abi.register_arguments
 
     for i in range(len(regs)):
diff --git a/pwndbg/commands/context.py b/pwndbg/commands/context.py
index a5a4d5048b2..f7d59d42950 100644
--- a/pwndbg/commands/context.py
+++ b/pwndbg/commands/context.py
@@ -979,7 +979,7 @@ def context_disasm(target=sys.stdout, with_banner=True, width=None):
     syntax = pwndbg.aglib.disasm.disassembly.CapstoneSyntax[flavor]
 
     # Get the Capstone object to set disassembly syntax
-    cs = next(iter(pwndbg.aglib.disasm.disassembly.get_disassembler_cached.cache.values()), None)
+    cs = next(iter(pwndbg.aglib.disasm.disassembly.get_disassembler.cache.values()), None)
 
     # The `None` case happens when the cache was not filled yet (see e.g. #881)
     if cs is not None and cs.syntax != syntax:
diff --git a/pwndbg/commands/hijack_fd.py b/pwndbg/commands/hijack_fd.py
index 7ad3978e7f1..2735972a26f 100644
--- a/pwndbg/commands/hijack_fd.py
+++ b/pwndbg/commands/hijack_fd.py
@@ -32,7 +32,9 @@ class ShellcodeRegs(NamedTuple):
 
 def get_shellcode_regs() -> ShellcodeRegs:
     register_set = pwndbg.lib.regs.reg_sets[pwndbg.aglib.arch.name]
-    syscall_abi = pwndbg.lib.abi.ABI.syscall()
+    syscall_abi = pwndbg.aglib.arch.syscall_abi
+    if syscall_abi is None:
+        raise pwndbg.dbg_mod.Error("Syscall ABI not defined for current architecture")
 
     # pickup free register what is not used for syscall abi
     newfd_reg = next(
@@ -50,7 +52,10 @@ def get_shellcode_regs() -> ShellcodeRegs:
 
 
 def stack_size_alignment(s: int) -> int:
-    syscall_abi = pwndbg.lib.abi.ABI.syscall()
+    syscall_abi = pwndbg.aglib.arch.syscall_abi
+    if syscall_abi is None:
+        raise pwndbg.dbg_mod.Error("Syscall ABI not defined for current architecture")
+
     return s + (syscall_abi.arg_alignment - (s % syscall_abi.arg_alignment))
 
 
diff --git a/pwndbg/dbg/__init__.py b/pwndbg/dbg/__init__.py
index 1feefad9035..36e657e3ab6 100644
--- a/pwndbg/dbg/__init__.py
+++ b/pwndbg/dbg/__init__.py
@@ -21,6 +21,7 @@
 
 import pwndbg.lib.memory
 from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+from pwndbg.lib.arch import ArchDefinition
 
 dbg: Debugger = None
 
@@ -74,33 +75,6 @@ class DebuggerType(Enum):
     LLDB = 2
 
 
-class Arch:
-    """
-    The definition of an architecture.
-    """
-
-    @property
-    def endian(self) -> Literal["little", "big"]:
-        """
-        Wether code in this module is little or big.
-        """
-        raise NotImplementedError()
-
-    @property
-    def name(self) -> PWNDBG_SUPPORTED_ARCHITECTURES_TYPE:
-        """
-        Name of the architecture.
-        """
-        raise NotImplementedError()
-
-    @property
-    def ptrsize(self) -> int:
-        """
-        Length of the pointer in this module.
-        """
-        raise NotImplementedError()
-
-
 class StopPoint:
     """
     The handle to either an insalled breakpoint or watchpoint.
@@ -506,7 +480,7 @@ def types_with_name(self, name: str) -> Sequence[Type]:
         """
         raise NotImplementedError()
 
-    def arch(self) -> Arch:
+    def arch(self) -> ArchDefinition:
         """
         The default architecture of this process.
         """
diff --git a/pwndbg/dbg/gdb/__init__.py b/pwndbg/dbg/gdb/__init__.py
index 111b21db0ec..f727530fb7b 100644
--- a/pwndbg/dbg/gdb/__init__.py
+++ b/pwndbg/dbg/gdb/__init__.py
@@ -29,7 +29,9 @@
 from pwndbg.dbg import selection
 from pwndbg.gdblib import gdb_version
 from pwndbg.gdblib import load_gdblib
-from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+from pwndbg.lib.arch import ArchAttribute
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 from pwndbg.lib.memory import PAGE_MASK
 from pwndbg.lib.memory import PAGE_SIZE
 
@@ -58,36 +60,21 @@
     "s390:64-bit",
 )
 
-
-class GDBArch(pwndbg.dbg_mod.Arch):
-    _endian: Literal["little", "big"]
-    _name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
-    _ptrsize: int
-
-    def __init__(
-        self,
-        endian: Literal["little", "big"],
-        name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE,
-        ptrsize: int,
-    ):
-        self._endian = endian
-        self._name = name
-        self._ptrsize = ptrsize
-
-    @override
-    @property
-    def endian(self) -> Literal["little", "big"]:
-        return self._endian
-
-    @override
-    @property
-    def name(self) -> PWNDBG_SUPPORTED_ARCHITECTURES_TYPE:
-        return self._name
-
-    @override
-    @property
-    def ptrsize(self) -> int:
-        return self._ptrsize
+# `show architecture` returns a string like "mips:isa32r5"
+gdb_mips_to_arch_attribute_map = {
+    "mips5": ArchAttribute.MIPS_ISA_5,
+    "micromips": ArchAttribute.MIPS_ISA_MICRO,
+    "isa32": ArchAttribute.MIPS_ISA_32,
+    "isa32r2": ArchAttribute.MIPS_ISA_32R2,
+    "isa32r3": ArchAttribute.MIPS_ISA_32R3,
+    "isa32r5": ArchAttribute.MIPS_ISA_32R5,
+    "isa32r6": ArchAttribute.MIPS_ISA_32R6,
+    "isa64": ArchAttribute.MIPS_ISA_64,
+    "isa64r2": ArchAttribute.MIPS_ISA_64R2,
+    "isa64r3": ArchAttribute.MIPS_ISA_64R3,
+    "isa64r5": ArchAttribute.MIPS_ISA_64R5,
+    "isa64r6": ArchAttribute.MIPS_ISA_64R6,
+}
 
 
 def parse_and_eval(expression: str, global_context: bool) -> gdb.Value:
@@ -723,7 +710,7 @@ def types_with_name(self, name: str) -> Sequence[pwndbg.dbg_mod.Type]:
             return []
 
     @override
-    def arch(self) -> pwndbg.dbg_mod.Arch:
+    def arch(self) -> ArchDefinition:
         ptrsize = pwndbg.aglib.typeinfo.ptrsize
         not_exactly_arch = False
 
@@ -741,6 +728,14 @@ def arch(self) -> pwndbg.dbg_mod.Arch:
 
         arch = arch.lower()
 
+        arch_attributes = []
+
+        if arch.startswith("mips:"):
+            isa = arch[5:]
+
+            if (attribute := gdb_mips_to_arch_attribute_map.get(isa)) is not None:
+                arch_attributes.append(attribute)
+
         # Below, we fix the fetched architecture
         for match in gdb_architecture_name_fixup_list:
             if match in arch:
@@ -765,12 +760,24 @@ def arch(self) -> pwndbg.dbg_mod.Arch:
                     match = "powerpc"
                 elif match == "s390:64-bit":
                     match = "s390x"
-                return GDBArch(endian, match, ptrsize)  # type: ignore[arg-type]
+                return ArchDefinition(
+                    name=match,  # type: ignore[arg-type]
+                    ptrsize=ptrsize,
+                    endian=endian,
+                    platform=Platform.LINUX,
+                    attributes=arch_attributes,
+                )
 
         if not_exactly_arch:
             raise RuntimeError(f"Could not deduce architecture from: {arch}")
 
-        return GDBArch(endian, arch, ptrsize)  # type: ignore[arg-type]
+        return ArchDefinition(
+            name=arch,  # type: ignore[arg-type]
+            ptrsize=ptrsize,
+            endian=endian,
+            platform=Platform.LINUX,
+            attributes=arch_attributes,
+        )
 
     @override
     def break_at(
diff --git a/pwndbg/dbg/lldb/__init__.py b/pwndbg/dbg/lldb/__init__.py
index 7718f22f212..70fd2aa0186 100644
--- a/pwndbg/dbg/lldb/__init__.py
+++ b/pwndbg/dbg/lldb/__init__.py
@@ -29,7 +29,8 @@
 import pwndbg.lib.memory
 from pwndbg.aglib import load_aglib
 from pwndbg.dbg import selection
-from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 from pwndbg.lib.regs import reg_sets
 
 T = TypeVar("T")
@@ -54,33 +55,6 @@ def rename_register(name: str, proc: LLDBProcess) -> str:
     return name
 
 
-class LLDBArch(pwndbg.dbg_mod.Arch):
-    def __init__(
-        self,
-        name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE,
-        ptrsize: int,
-        endian: Literal["little", "big"],
-    ):
-        self._endian = endian
-        self._name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
-        self._ptrsize = ptrsize
-
-    @override
-    @property
-    def endian(self) -> Literal["little", "big"]:
-        return self._endian
-
-    @override
-    @property
-    def name(self) -> PWNDBG_SUPPORTED_ARCHITECTURES_TYPE:
-        return self._name
-
-    @override
-    @property
-    def ptrsize(self) -> int:
-        return self._ptrsize
-
-
 class LLDBRegisters(pwndbg.dbg_mod.Registers):
     groups: lldb.SBValueList
     proc: LLDBProcess
@@ -1473,7 +1447,7 @@ def types_with_name(self, name: str) -> Sequence[pwndbg.dbg_mod.Type]:
         return [LLDBType(types.GetTypeAtIndex(i)) for i in range(types.GetSize())]
 
     @override
-    def arch(self) -> pwndbg.dbg_mod.Arch:
+    def arch(self) -> ArchDefinition:
         endian0 = self.process.GetByteOrder()
         endian1 = self.target.GetByteOrder()
 
@@ -1545,7 +1519,7 @@ def _has_xpsr(thread) -> bool:
             # Pwndbg use a different name for riscv64.
             name = "rv64"
 
-        return LLDBArch(name, ptrsize0, endian)
+        return ArchDefinition(name=name, ptrsize=ptrsize0, endian=endian, platform=Platform.LINUX)
 
     @override
     def break_at(
diff --git a/pwndbg/lib/abi.py b/pwndbg/lib/abi.py
index 1c3c5db397c..1479056fa0f 100644
--- a/pwndbg/lib/abi.py
+++ b/pwndbg/lib/abi.py
@@ -5,8 +5,6 @@
 from typing import List
 from typing import Tuple
 
-import pwndbg.aglib.arch
-
 
 class ABI:
     """
@@ -35,18 +33,6 @@ def __init__(self, regs: List[str], align: int, minimum: int) -> None:
         self.arg_alignment = align
         self.stack_minimum = minimum
 
-    @staticmethod
-    def default() -> ABI:
-        return DEFAULT_ABIS[(8 * pwndbg.aglib.arch.ptrsize, pwndbg.aglib.arch.name, "linux")]
-
-    @staticmethod
-    def syscall() -> SyscallABI:
-        return SYSCALL_ABIS[(8 * pwndbg.aglib.arch.ptrsize, pwndbg.aglib.arch.name, "linux")]
-
-    @staticmethod
-    def sigreturn() -> SigreturnABI:
-        return SIGRETURN_ABIS[(8 * pwndbg.aglib.arch.ptrsize, pwndbg.aglib.arch.name, "linux")]
-
 
 class SyscallABI(ABI):
     """
diff --git a/pwndbg/lib/arch.py b/pwndbg/lib/arch.py
index 9495ecb5618..47f924bcd61 100644
--- a/pwndbg/lib/arch.py
+++ b/pwndbg/lib/arch.py
@@ -1,10 +1,31 @@
 from __future__ import annotations
 
-import struct
-import sys
 import typing
+from dataclasses import dataclass
+from dataclasses import field
+from enum import Enum
+from enum import auto
+from typing import List
 from typing import Literal
 
+from capstone import CS_MODE_MICRO
+from capstone import CS_MODE_MIPS1
+from capstone import CS_MODE_MIPS2
+from capstone import CS_MODE_MIPS3
+from capstone import CS_MODE_MIPS4
+from capstone import CS_MODE_MIPS5
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R2
+from capstone import CS_MODE_MIPS32R3
+from capstone import CS_MODE_MIPS32R5
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_MIPS64R2
+from capstone import CS_MODE_MIPS64R3
+from capstone import CS_MODE_MIPS64R5
+from capstone import CS_MODE_MIPS64R6
+from capstone import CS_MODE_NANOMIPS
+
 # Names of arches that Pwndbg supports
 # The names that GDB/LLDB recognize are converted to a name in this list
 PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = Literal[
@@ -45,49 +66,60 @@
 }
 
 
-FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
-FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
-
-
-class Arch:
-    def __init__(
-        self,
-        arch_name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE,
-        ptrsize: int,
-        endian: Literal["little", "big"],
-    ) -> None:
-        self.update(arch_name, ptrsize, endian)
-        self.native_endian = str(sys.byteorder)
-
-    def update(
-        self,
-        arch_name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE,
-        ptrsize: int,
-        endian: Literal["little", "big"],
-    ) -> None:
-        self.name = arch_name
-        self.ptrsize = ptrsize
-        self.ptrmask = (1 << 8 * ptrsize) - 1
-        self.endian = endian
-
-        self.fmts = FMT_LITTLE_ENDIAN if endian == "little" else FMT_BIG_ENDIAN
-        self.fmt = self.fmts[self.ptrsize]
-
-        if self.name == "arm" and self.endian == "big":
-            self.qemu = "armeb"
-        elif self.name == "mips" and self.endian == "little":
-            self.qemu = "mipsel"
-        else:
-            self.qemu = self.name
-
-    def pack(self, integer: int) -> bytes:
-        return struct.pack(self.fmt, integer & self.ptrmask)
-
-    def unpack(self, data: bytes) -> int:
-        return struct.unpack(self.fmt, data)[0]
-
-    def pack_size(self, integer: int, size: int) -> bytes:
-        return struct.pack(self.fmts[size], integer & self.ptrmask)
-
-    def unpack_size(self, data: bytes, size: int) -> int:
-        return struct.unpack(self.fmts[size], data)[0]
+# The platform being debugged
+class Platform(Enum):
+    LINUX = (auto(),)
+    DARWIN = (auto(),)
+    # WINDOWS = auto(),
+    # ANDROID = auto(),
+    # OPENBSD = auto(),
+    # FREEBSD = auto(),
+
+
+PWNLIB_PLATFORM_MAPPINGS: dict[Platform, str] = {
+    Platform.LINUX: "linux",
+    Platform.DARWIN: "darwin",
+}
+
+
+# A series of tuples
+# Index 0 = Unique integer
+# Index 1 = Capstone constant associated with the attribute
+class ArchAttribute(Enum):
+    # MIPS ATTRIBUTES
+    ## MIPS created and removed lots of instructions between ISA versions, sometimes re-using
+    ## old instruction encodings. To disassemble correctly, we need to choose the correct ISA.
+    MIPS_ISA_1 = auto(), CS_MODE_MIPS1
+    MIPS_ISA_2 = auto(), CS_MODE_MIPS2
+    MIPS_ISA_3 = auto(), CS_MODE_MIPS3
+    MIPS_ISA_4 = auto(), CS_MODE_MIPS4
+    MIPS_ISA_5 = auto(), CS_MODE_MIPS5
+
+    MIPS_ISA_32 = auto(), CS_MODE_MIPS32
+    MIPS_ISA_32R2 = auto(), CS_MODE_MIPS32R2
+
+    MIPS_ISA_32R3 = auto(), CS_MODE_MIPS32R3
+    MIPS_ISA_32R5 = auto(), CS_MODE_MIPS32R5
+    MIPS_ISA_32R6 = auto(), CS_MODE_MIPS32R6
+
+    MIPS_ISA_64 = auto(), CS_MODE_MIPS64
+    MIPS_ISA_64R2 = auto(), CS_MODE_MIPS64R2
+    MIPS_ISA_64R3 = auto(), CS_MODE_MIPS64R3
+    MIPS_ISA_64R5 = auto(), CS_MODE_MIPS64R5
+    MIPS_ISA_64R6 = auto(), CS_MODE_MIPS64R6
+
+    MIPS_ISA_MICRO = auto(), CS_MODE_MICRO
+    MIPS_ISA_NANO = auto(), CS_MODE_NANOMIPS
+
+    def __init__(self, _, cs_mode):
+        self.cs_mode = cs_mode
+
+
+@dataclass
+class ArchDefinition:
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    ptrsize: int
+    """Pointer size in bytes"""
+    endian: Literal["little", "big"]
+    platform: Platform
+    attributes: List[ArchAttribute] = field(default_factory=list)
diff --git a/pwndbg/lib/gcc.py b/pwndbg/lib/gcc.py
index 18978cd768e..75de15627ba 100644
--- a/pwndbg/lib/gcc.py
+++ b/pwndbg/lib/gcc.py
@@ -11,12 +11,12 @@
 from typing import Any
 from typing import List
 
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
 
 printed_message = False
 
 
-def which(arch: Arch) -> List[str]:
+def which(arch: ArchDefinition) -> List[str]:
     gcc = _which_binutils("g++", arch)
 
     if gcc is None:
@@ -35,7 +35,7 @@ def which(arch: Arch) -> List[str]:
     return [gcc] + _flags(arch.name)
 
 
-def _which_binutils(util: str, arch: Arch, **kwargs: Any) -> str | None:
+def _which_binutils(util: str, arch: ArchDefinition, **kwargs: Any) -> str | None:
     ###############################
     # Borrowed from pwntools' code
     ###############################
diff --git a/tests/gdb-tests/tests/test_architectures.py b/tests/gdb-tests/tests/test_architectures.py
new file mode 100644
index 00000000000..9e88e9af317
--- /dev/null
+++ b/tests/gdb-tests/tests/test_architectures.py
@@ -0,0 +1,9 @@
+from __future__ import annotations
+
+from pwndbg.aglib.arch import registered_architectures
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES
+
+
+def test_all_pwndbg_architectures_are_defined():
+    for arch in PWNDBG_SUPPORTED_ARCHITECTURES:
+        assert arch in registered_architectures
diff --git a/tests/unit-tests/mocks/aglib.py b/tests/unit-tests/mocks/aglib.py
index 96d2ec840c4..c119fa017e5 100644
--- a/tests/unit-tests/mocks/aglib.py
+++ b/tests/unit-tests/mocks/aglib.py
@@ -4,7 +4,7 @@
 import types
 from unittest.mock import MagicMock
 
-from mocks.arch import Amd64Arch
+from mocks.arch import MockAmd64Arch
 from mocks.config import Config
 from mocks.typeinfo import Amd64TypeInfo
 
@@ -14,7 +14,7 @@ def __init__(self, module_name):
         super().__init__(module_name)
 
         self.config_mod = Config(module_name + ".config")
-        self.arch = Amd64Arch(module_name + ".arch")
+        self.arch = MockAmd64Arch(module_name + ".arch")
         self.typeinfo = Amd64TypeInfo(module_name + ".typeinfo")
         self.regs = MagicMock(__name__=module_name + ".regs")
         self.prompt = MagicMock(__name__=module_name + ".prompt")
diff --git a/tests/unit-tests/mocks/arch.py b/tests/unit-tests/mocks/arch.py
index cd32076531d..a6504d6a0ef 100644
--- a/tests/unit-tests/mocks/arch.py
+++ b/tests/unit-tests/mocks/arch.py
@@ -4,7 +4,7 @@
 import types
 
 
-class Amd64Arch(types.ModuleType):
+class MockAmd64Arch(types.ModuleType):
     def __init__(self, module_name):
         super().__init__(module_name)
 
diff --git a/tests/unit-tests/mocks/dbg.py b/tests/unit-tests/mocks/dbg.py
index 4e1a5161e15..677941bcc96 100644
--- a/tests/unit-tests/mocks/dbg.py
+++ b/tests/unit-tests/mocks/dbg.py
@@ -1,30 +1,16 @@
 from __future__ import annotations
 
-from typing import Literal
-
 from typing_extensions import override
 
 import pwndbg
-
-
-class MockArch(pwndbg.dbg_mod.Arch):
-    @override
-    def ptrsize(self) -> int:
-        return 8
-
-    @override
-    def arch(self) -> str:
-        return "x86-64"
-
-    @override
-    def endian(self) -> Literal["little", "big"]:
-        return "little"
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
 
 class MockInferior(pwndbg.dbg_mod.Process):
     @override
-    def arch(self) -> dbg_mod.Arch:
-        return MockArch()
+    def arch(self) -> ArchDefinition:
+        return ArchDefinition(name="x86-64", ptrsize=8, endian="little", platform=Platform.LINUX)
 
 
 class MockDebugger(pwndbg.dbg_mod.Debugger):
diff --git a/tests/unit-tests/mocks/gdblib.py b/tests/unit-tests/mocks/gdblib.py
index eb37ddac93c..bf3814c3c70 100644
--- a/tests/unit-tests/mocks/gdblib.py
+++ b/tests/unit-tests/mocks/gdblib.py
@@ -4,7 +4,7 @@
 import types
 from unittest.mock import MagicMock
 
-from mocks.arch import Amd64Arch
+from mocks.arch import MockAmd64Arch
 from mocks.config import Config
 from mocks.typeinfo import Amd64TypeInfo
 
@@ -24,7 +24,7 @@ def __init__(self, module_name):
         self.gdb_version = (12, 0)
 
         self.config_mod = Config(module_name + ".config")
-        self.arch = Amd64Arch(module_name + ".arch")
+        self.arch = MockAmd64Arch(module_name + ".arch")
         self.typeinfo = Amd64TypeInfo(module_name + ".typeinfo")
         self.regs = MagicMock(__name__=module_name + ".regs")
         self.prompt = MagicMock(__name__=module_name + ".prompt")


Reviews:
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T07:58:47Z
user: login: patryk4815
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T16:12:35Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T02:24:03Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T02:26:19Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T02:38:11Z
user: login: patryk4815
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T08:37:35Z
user: login: patryk4815
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T08:38:03Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T08:41:38Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T17:30:28Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T15:48:24Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T15:48:50Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T18:46:35Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T18:48:27Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T15:24:34Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T15:26:43Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T15:27:09Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T20:08:06Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T20:09:46Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T20:09:54Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T20:10:07Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T20:13:39Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T20:14:22Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T20:15:09Z
user: login: patryk4815
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T20:15:23Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T20:16:36Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T20:17:31Z
user: login: disconnect3d
body: I like this! I have added a few comments here and there :)
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-31T20:18:29Z
user: login: disconnect3d
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T20:19:31Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T06:59:11Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T07:05:39Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T07:08:55Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T07:16:05Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T07:19:14Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T07:25:52Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T07:47:42Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T07:56:19Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T08:12:22Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T08:22:17Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-18T07:54:37Z
user: login: OBarronCS
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-18T07:55:27Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR refactors the way Pwndbg represents and handles architecture‐related information by introducing a new class-based mechanism using an ArchDefinition dataclass and corresponding PwndbgArchitecture subclasses. The changes propagate across tests, mocks, and core modules (e.g. lib, dbg, commands, and aglib/disasm), updating type hints and function signatures to use the new abstractions while also cleaning up legacy class definitions.

### Reviewed Changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File                                            | Description                                                                         |
|-------------------------------------------------|-------------------------------------------------------------------------------------|
| tests/unit-tests/mocks/gdblib.py                | Updated to import and instantiate MockAmd64Arch instead of the legacy Amd64Arch.     |
| tests/unit-tests/mocks/dbg.py                   | Replaced references to the old MockArch with the new ArchDefinition return type.      |
| tests/unit-tests/mocks/arch.py                  | Renamed Amd64Arch to MockAmd64Arch for consistency.                                  |
| tests/unit-tests/mocks/aglib.py                 | Adjusted architecture instantiation to use MockAmd64Arch.                           |
| tests/gdb-tests/tests/test_architectures.py     | Added test to verify that all supported architectures are registered.              |
| pwndbg/lib/gcc.py                               | Updated function signatures to accept ArchDefinition instead of legacy Arch.         |
| pwndbg/lib/arch.py                              | Replaced the old Arch implementation with ArchDefinition, new enums, and structure.  |
| pwndbg/lib/abi.py                               | Removed old static ABI methods in favor of new ABI references via aglib.arch.          |
| pwndbg/dbg/lldb/__init__.py                     | Updated LLDB architecture method to return an ArchDefinition instance.               |
| pwndbg/dbg/gdb/__init__.py                      | Updated to use ArchDefinition and introduced a mapping for mips attributes.          |
| pwndbg/dbg/__init__.py                          | Removed the deprecated Arch class and updated arch() to return ArchDefinition.       |
| pwndbg/commands/hijack_fd.py                    | Changed syscall_abi retrieval to use the new aglib.arch.syscall_abi.                 |
| pwndbg/commands/context.py                      | Replaced call of get_disassembler_cached() with get_disassembler() using new cache.    |
| pwndbg/arguments.py                             | Revised ABI default and argument functions to retrieve ABI from aglib.arch.          |
| pwndbg/aglib/disasm/disassembly.py              | Removed the cached disassembler function and now uses get_disassembler() directly.     |
| pwndbg/aglib/disasm/arch.py                     | Updated _get_syscall_arch_info to retrieve the syscall register via the new ABI.       |
| pwndbg/aglib/arch.py                            | Introduces PwndbgArchitecture as the new base with multiple architecture subclasses. |
| pwndbg/aglib/__init__.py                        | Sets global aglib.arch using get_pwndbg_architecture and provides set_arch().         |
</details>





state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T10:59:05Z
user: login: disconnect3d
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-24T11:01:27Z

Review comments:
diff_hunk: @@ -1,10 +1,14 @@
 from __future__ import annotations
 
 from pwndbg.aglib import arch as arch_mod
-from pwndbg.aglib.arch import arch as arch
+from pwndbg.aglib.arch import PwndbgArchitecture
+
+# from pwndbg.aglib.arch import arch as arch
 
 regs = None
 
+arch: PwndbgArchitecture = PwndbgArchitecture.get_arch("i386")
+
path: pwndbg/aglib/__init__.py
user: login: OBarronCS
body: Contrary to my understanding of Python imports, this seems to work great - the imported value is not stale anywhere that does `import pwndbg.aglib.arch`.
created_at: 2025-03-05T07:55:17Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,317 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
+import gdb
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+
+# Uncommenting this create circular import
+# from pwndbg.aglib.disasm import emulated_arm_mode_cache
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
 
+EndianType = Literal["little", "big"]
+
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture:
+    """
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
+
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
+    """
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: Attaching ABI's to this object makes it simpler for us to change them when necessary (such as when debugging on macos).
created_at: 2025-03-05T07:56:17Z
author_association: MEMBER
diff_hunk: @@ -42,4 +320,8 @@ def update() -> None:
     pwnlib.context.context.arch = PWNLIB_ARCH_MAPPINGS.get(a.name, "none")
     pwnlib.context.context.bits = a.ptrsize * 8
 
-    arch.update(a.name, a.ptrsize, a.endian)
+    if a.name != pwndbg.aglib.arch.name:
+        pwndbg_arch = PwndbgArchitecture.get_arch(a.name)
+        pwndbg.aglib.set_arch(pwndbg_arch)
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: Once we detect that the architecture has changed, get the correct class and set the global variable to the instance.
created_at: 2025-03-05T07:57:04Z
author_association: MEMBER
diff_hunk: @@ -46,7 +46,7 @@
 FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
 FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
 
-
+# Unused
path: pwndbg/lib/arch.py
user: login: OBarronCS
body: All the functionality of Arch is in PwndbgArchitecture
created_at: 2025-03-05T07:57:47Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,317 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
+import gdb
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+
+# Uncommenting this create circular import
+# from pwndbg.aglib.disasm import emulated_arm_mode_cache
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: Some trickiness with circular imports
created_at: 2025-03-05T07:58:12Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,317 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
+import gdb
path: pwndbg/aglib/arch.py
user: login: patryk4815
body: ```suggestion
if pwndbg.dbg.is_gdblib_available():
    import gdb
```
fixes lldb
created_at: 2025-03-05T16:12:35Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,317 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
+import gdb
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: Newest changes avoids the use of `gdb` in the arch file
created_at: 2025-03-26T02:24:02Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,319 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+
+# Uncommenting this create circular import
+# from pwndbg.aglib.disasm import emulated_arm_mode_cache
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
+
+EndianType = Literal["little", "big"]
+
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
 
 
-def read_thumb_bit() -> int | None:
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+            # If a custom class has not been registered for the architecture, use base implementation
+            # PwndbgArchitecture.registered_architectures[name] = PwndbgArchitecture(name)
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: A decision we need to make is what happens for "default" values here? Right now, it returns None (same behavior as before), which was historically been the source of crashes because there are a handful of functions assume this is a valid ABI object.

We could create some "dummy" object, or at all the callsights check that this value is not "None"?
created_at: 2025-03-26T02:26:18Z
author_association: MEMBER
diff_hunk: @@ -43,49 +44,20 @@
 }
 
 
-FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
-FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+# The platform being debugged
+class Platform(Enum):
+    LINUX = (auto(),)
+    MACOSX = (auto(),)
path: pwndbg/lib/arch.py
user: login: OBarronCS
body: Would we prefer `DARWIN` here? One example use for "platform" is to select the ABI.
created_at: 2025-03-26T02:38:11Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,319 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+
+# Uncommenting this create circular import
+# from pwndbg.aglib.disasm import emulated_arm_mode_cache
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
+
+EndianType = Literal["little", "big"]
+
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
 
 
-def read_thumb_bit() -> int | None:
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+            # If a custom class has not been registered for the architecture, use base implementation
+            # PwndbgArchitecture.registered_architectures[name] = PwndbgArchitecture(name)
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if pwndbg.dbg.is_gdblib_available() and "v9" in self.name_raw:
path: pwndbg/aglib/arch.py
user: login: patryk4815
body: ```suggestion
        if "v9" in self.name_raw:
```

`pwndbg.dbg.is_gdblib_available()` is not needed here after removing gdb
created_at: 2025-03-26T08:37:35Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,319 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+
+# Uncommenting this create circular import
+# from pwndbg.aglib.disasm import emulated_arm_mode_cache
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
+
+EndianType = Literal["little", "big"]
+
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
 
 
-def read_thumb_bit() -> int | None:
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+            # If a custom class has not been registered for the architecture, use base implementation
+            # PwndbgArchitecture.registered_architectures[name] = PwndbgArchitecture(name)
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if pwndbg.dbg.is_gdblib_available() and "v9" in self.name_raw:
+            mode = CS_MODE_V9
+        else:
+            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
+            mode = 0
+
+        return (CS_ARCH_SPARC, mode)
+
+
+class RISCV32Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv32")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV32 | CS_MODE_RISCVC)
+
+
+class RISCV64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV64 | CS_MODE_RISCVC)
+
+
+class MipsArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("mips")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - Capstone v6 increased the number of MIPS constants
+        if pwndbg.dbg.is_gdblib_available() and "isa32r6" in self.name_raw:
path: pwndbg/aglib/arch.py
user: login: patryk4815
body: `pwndbg.dbg.is_gdblib_available() is not needed here after removing gdb`
```suggestion
        if "isa32r6" in self.name_raw:
```
created_at: 2025-03-26T08:38:03Z
author_association: MEMBER
diff_hunk: @@ -1,10 +1,14 @@
 from __future__ import annotations
 
 from pwndbg.aglib import arch as arch_mod
-from pwndbg.aglib.arch import arch as arch
+from pwndbg.aglib.arch import PwndbgArchitecture
+
+# from pwndbg.aglib.arch import arch as arch
 
 regs = None
 
+arch: PwndbgArchitecture = PwndbgArchitecture.get_arch("i386")
+
path: pwndbg/aglib/__init__.py
user: login: disconnect3d
body: What if one does `from pwndbg.aglib import arch`? then it would be copy and be problematic?
created_at: 2025-03-26T08:41:38Z
author_association: MEMBER
diff_hunk: @@ -1,10 +1,14 @@
 from __future__ import annotations
 
 from pwndbg.aglib import arch as arch_mod
-from pwndbg.aglib.arch import arch as arch
+from pwndbg.aglib.arch import PwndbgArchitecture
+
+# from pwndbg.aglib.arch import arch as arch
 
 regs = None
 
+arch: PwndbgArchitecture = PwndbgArchitecture.get_arch("i386")
+
path: pwndbg/aglib/__init__.py
user: login: OBarronCS
body: Yes, I believe so - ideally there is a linting rule we can use to forbid importing in that way.
created_at: 2025-03-26T17:30:28Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,319 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
path: pwndbg/aglib/arch.py
user: login: disconnect3d
body: can we collapse those to: `from capstone import (a,b,c,d)`?
created_at: 2025-03-28T15:48:24Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,319 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+
+# Uncommenting this create circular import
+# from pwndbg.aglib.disasm import emulated_arm_mode_cache
path: pwndbg/aglib/arch.py
user: login: disconnect3d
body: So why leaving the comment here? :)
created_at: 2025-03-28T15:48:49Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,319 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+
+# Uncommenting this create circular import
+# from pwndbg.aglib.disasm import emulated_arm_mode_cache
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: Good point - removing this, was meant to be temporary
created_at: 2025-03-28T18:46:35Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,319 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: Unfortunately it appears that our linting rules do not like this, `./lint.sh -f` automatically expands each import into it's own line
created_at: 2025-03-28T18:48:26Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,319 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
path: pwndbg/aglib/arch.py
user: login: disconnect3d
body: We shall change it in the future.
created_at: 2025-03-31T15:24:33Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
path: pwndbg/aglib/arch.py
user: login: disconnect3d
body: Shouldn't such changes be done only in child classes and the base should just keep things as is?
created_at: 2025-03-31T15:26:43Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,319 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+
+# Uncommenting this create circular import
+# from pwndbg.aglib.disasm import emulated_arm_mode_cache
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
+
+EndianType = Literal["little", "big"]
+
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
 
 
-def read_thumb_bit() -> int | None:
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+            # If a custom class has not been registered for the architecture, use base implementation
+            # PwndbgArchitecture.registered_architectures[name] = PwndbgArchitecture(name)
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
path: pwndbg/aglib/arch.py
user: login: disconnect3d
body: Probably have a default or handle the None :x
created_at: 2025-03-31T15:27:09Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if "v9" in self.name_raw:
+            mode = CS_MODE_V9
+        else:
+            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
+            mode = 0
path: pwndbg/aglib/arch.py
user: login: disconnect3d
body: 1) This could be just:
```py
mode = CS_MODE_V9 if "v9" in self.name_raw else 0
```

2) Can we give an example of name_raw with sparc v9 in a comment?

3) The comment about ptrsize is really unclear to me. Can you extend it and elaborate a bit more?
created_at: 2025-03-31T20:08:05Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if "v9" in self.name_raw:
+            mode = CS_MODE_V9
+        else:
+            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
+            mode = 0
+
+        return (CS_ARCH_SPARC, mode)
+
+
+class RISCV32Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv32")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV32 | CS_MODE_RISCVC)
path: pwndbg/aglib/arch.py
user: login: disconnect3d
body: ```suggestion
        # We enable the RISC-V compressed ISA extension by default (CS_MODE_RISCVC)
        return (CS_ARCH_RISCV, CS_MODE_RISCV32 | CS_MODE_RISCVC)
```
created_at: 2025-03-31T20:09:46Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if "v9" in self.name_raw:
+            mode = CS_MODE_V9
+        else:
+            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
+            mode = 0
+
+        return (CS_ARCH_SPARC, mode)
+
+
+class RISCV32Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv32")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV32 | CS_MODE_RISCVC)
+
+
+class RISCV64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV64 | CS_MODE_RISCVC)
path: pwndbg/aglib/arch.py
user: login: disconnect3d
body: ```suggestion
        # We enable the RISC-V compressed ISA extension by default (CS_MODE_RISCVC)
        return (CS_ARCH_RISCV, CS_MODE_RISCV64 | CS_MODE_RISCVC)
```
created_at: 2025-03-31T20:09:54Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if "v9" in self.name_raw:
+            mode = CS_MODE_V9
+        else:
+            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
+            mode = 0
+
+        return (CS_ARCH_SPARC, mode)
+
+
+class RISCV32Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv32")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV32 | CS_MODE_RISCVC)
path: pwndbg/aglib/arch.py
user: login: disconnect3d
body: Actually, is there a reason why we do this? Can we somehow detect if this is there or not?
created_at: 2025-03-31T20:10:07Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
path: pwndbg/aglib/arch.py
user: login: disconnect3d
body: Not sure what's this comment about, to be removed?
created_at: 2025-03-31T20:13:39Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if "v9" in self.name_raw:
+            mode = CS_MODE_V9
+        else:
+            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
+            mode = 0
+
+        return (CS_ARCH_SPARC, mode)
+
+
+class RISCV32Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv32")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV32 | CS_MODE_RISCVC)
+
+
+class RISCV64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV64 | CS_MODE_RISCVC)
+
+
+class MipsArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("mips")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - Capstone v6 increased the number of MIPS constants
+        if "isa32r6" in self.name_raw:
path: pwndbg/aglib/arch.py
user: login: disconnect3d
body: Can we provide a comment with a full name_raw example?
created_at: 2025-03-31T20:14:22Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if "v9" in self.name_raw:
+            mode = CS_MODE_V9
+        else:
+            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
+            mode = 0
+
+        return (CS_ARCH_SPARC, mode)
+
+
+class RISCV32Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv32")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV32 | CS_MODE_RISCVC)
+
+
+class RISCV64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV64 | CS_MODE_RISCVC)
+
+
+class MipsArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("mips")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - Capstone v6 increased the number of MIPS constants
+        if "isa32r6" in self.name_raw:
+            extra = CS_MODE_MIPS32R6
+        elif self.ptrsize == 64:
+            extra = CS_MODE_MIPS64
+        else:
+            extra = CS_MODE_MIPS32
+
+        return (CS_ARCH_MIPS, extra)
+
+
+# Register the architecture classes
+AMD64Arch()
+i386Arch()
+i8086Arch()
+ArmArch()
+ArmCortexArch()
+AArch64Arch()
+PowerPCArch()
+SparcArch()
+RISCV32Arch()
+RISCV64Arch()
+MipsArch()
path: pwndbg/aglib/arch.py
user: login: disconnect3d
body: I don't like how this gets registered. We instantiate an object which seems to die immediately, but in practice it doesn't because it gets added to a static field. We could alternatively have all of this as a global list and have a function instead of static field for it.

It's not like I would die for changing it, but still.


created_at: 2025-03-31T20:15:09Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
path: pwndbg/aglib/arch.py
user: login: patryk4815
body: https://github.com/pwndbg/pwndbg/pull/2367#issuecomment-2487085647
created_at: 2025-03-31T20:15:23Z
author_association: MEMBER
diff_hunk: @@ -124,19 +108,17 @@ def clear_on_reg_mem_change() -> None:
 
 
 @pwndbg.lib.cache.cache_until("objfile")
-def get_disassembler_cached(arch, ptrsize: int, endian, extra=None):
-    arch = CapstoneArch[arch]
-
-    if extra is None:
-        mode = CapstoneMode[ptrsize]
+def get_disassembler(address: int, cs_info: Tuple[int, int] = None):
+    if cs_info is not None:
+        arch, mode = cs_info
     else:
-        mode = extra
+        arch, mode = pwndbg.aglib.arch.get_capstone_constants(address)
 
-    mode |= CapstoneEndian[endian]
-
-    flavor = pwndbg.dbg.x86_disassembly_flavor()
+    mode |= CapstoneEndian[pwndbg.aglib.arch.endian]
 
     cs = Cs(arch, mode)
+
+    flavor = pwndbg.dbg.x86_disassembly_flavor()
path: pwndbg/aglib/disasm/__init__.py
user: login: disconnect3d
body: Hmm, Isn't it weird that we get a x86 flavor while we may not be in x86-64?
created_at: 2025-03-31T20:16:36Z
author_association: MEMBER
diff_hunk: @@ -205,10 +143,11 @@ def get_one_instruction(
         if cached is not None:
             return cached
 
-    if pwndbg.aglib.arch.name not in CapstoneArch:
+    cs_info = pwndbg.aglib.arch.get_capstone_constants(address)
+    if None in cs_info:
path: pwndbg/aglib/disasm/__init__.py
user: login: disconnect3d
body: Is there a case when we get `(X, None)` here instead of `(None, None)`?

Should we maybe change the `get_capstone_constant` to return `None | cs_info` instead?
created_at: 2025-03-31T20:17:31Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
path: pwndbg/aglib/arch.py
user: login: disconnect3d
body: Makes sense, but this is about... platform? then why not comment it above platform or on the same line?
created_at: 2025-03-31T20:19:31Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if "v9" in self.name_raw:
+            mode = CS_MODE_V9
+        else:
+            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
+            mode = 0
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: The comment is outdated and doesn't apply/isn't correct, so I'm removing it.

The "v9" in self.name_raw check was to detect additional attributes of the architecture, `gdb` detects sparc as  "sparc:v9" or "sparc:v8", for example, with the v9, v8 being a variant. This was a hacky way to check for this "variant", as this logic was only used in two places in the code (used as well for a mips to check for "isa32r6").

I think a better solution would be to encode this sort of information in a more reliable way, maybe some sort of list of ArchitectureAttribute object or enums, so one could do 
```python
if ArchAttribute.SPARC_V9 in self.arch_attributes:
````
or something like that. 




created_at: 2025-04-14T06:59:11Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if "v9" in self.name_raw:
+            mode = CS_MODE_V9
+        else:
+            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
+            mode = 0
+
+        return (CS_ARCH_SPARC, mode)
+
+
+class RISCV32Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv32")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV32 | CS_MODE_RISCVC)
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: I believe the compressed instructions are an extension that add encodings which don't collide with the existing riscv instructions -- capstone should really internally have this enabled all the time, since the compressed instructions are very common. There is a register (`misa`) that can be checked for this (but it may not always be implemented). I think it's simpler to always have it enabled.
created_at: 2025-04-14T07:05:39Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: Forgot to remove this comment.
created_at: 2025-04-14T07:08:55Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if "v9" in self.name_raw:
+            mode = CS_MODE_V9
+        else:
+            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
+            mode = 0
+
+        return (CS_ARCH_SPARC, mode)
+
+
+class RISCV32Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv32")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV32 | CS_MODE_RISCVC)
+
+
+class RISCV64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV64 | CS_MODE_RISCVC)
+
+
+class MipsArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("mips")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - Capstone v6 increased the number of MIPS constants
+        if "isa32r6" in self.name_raw:
+            extra = CS_MODE_MIPS32R6
+        elif self.ptrsize == 64:
+            extra = CS_MODE_MIPS64
+        else:
+            extra = CS_MODE_MIPS32
+
+        return (CS_ARCH_MIPS, extra)
+
+
+# Register the architecture classes
+AMD64Arch()
+i386Arch()
+i8086Arch()
+ArmArch()
+ArmCortexArch()
+AArch64Arch()
+PowerPCArch()
+SparcArch()
+RISCV32Arch()
+RISCV64Arch()
+MipsArch()
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: I agree, not very clean. I created a function that takes these objects as a parameter to register them so that is it more clear how they are being used.
created_at: 2025-04-14T07:16:05Z
author_association: MEMBER
diff_hunk: @@ -124,19 +108,17 @@ def clear_on_reg_mem_change() -> None:
 
 
 @pwndbg.lib.cache.cache_until("objfile")
-def get_disassembler_cached(arch, ptrsize: int, endian, extra=None):
-    arch = CapstoneArch[arch]
-
-    if extra is None:
-        mode = CapstoneMode[ptrsize]
+def get_disassembler(address: int, cs_info: Tuple[int, int] = None):
+    if cs_info is not None:
+        arch, mode = cs_info
     else:
-        mode = extra
+        arch, mode = pwndbg.aglib.arch.get_capstone_constants(address)
 
-    mode |= CapstoneEndian[endian]
-
-    flavor = pwndbg.dbg.x86_disassembly_flavor()
+    mode |= CapstoneEndian[pwndbg.aglib.arch.endian]
 
     cs = Cs(arch, mode)
+
+    flavor = pwndbg.dbg.x86_disassembly_flavor()
path: pwndbg/aglib/disasm/__init__.py
user: login: OBarronCS
body: That function should be renamed to just "disassembly_flavor()` - in all other arches, gdb returns "intel" as the disassembly flavour, while x86 also allows "att".
created_at: 2025-04-14T07:19:14Z
author_association: MEMBER
diff_hunk: @@ -205,10 +143,11 @@ def get_one_instruction(
         if cached is not None:
             return cached
 
-    if pwndbg.aglib.arch.name not in CapstoneArch:
+    cs_info = pwndbg.aglib.arch.get_capstone_constants(address)
+    if None in cs_info:
path: pwndbg/aglib/disasm/__init__.py
user: login: OBarronCS
body: I made this change
created_at: 2025-04-14T07:25:52Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,319 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+
+# Uncommenting this create circular import
+# from pwndbg.aglib.disasm import emulated_arm_mode_cache
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
+
+EndianType = Literal["little", "big"]
+
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
 
 
-def read_thumb_bit() -> int | None:
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+            # If a custom class has not been registered for the architecture, use base implementation
+            # PwndbgArchitecture.registered_architectures[name] = PwndbgArchitecture(name)
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: Done - all places that refer to these check if it's None and return early/raise error where appropriate.
created_at: 2025-04-14T07:47:42Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: For some architectures (as the way we categorize them), many of the things that this `update()` method sets indeed are locked in. However, in many cases, many of these the attributes can change and having this parent function that updates all of them is very convenient.

As an example, the endianness of some architectures can be little/big and some can even change dynamically at runtime. Rather than go to each arch and define it explicitly or determine if it changes dynamically, it's simpler to just have this parent function set it at runtime.

As another case, we define one class for Mips, which encapsulates all types of mips, including 32-bit and 64-bit, so we need to detect and set the ptrsize at runtime, which this function allows for.
created_at: 2025-04-14T07:56:19Z
author_association: MEMBER
diff_hunk: @@ -42,4 +319,14 @@ def update() -> None:
     pwnlib.context.context.arch = PWNLIB_ARCH_MAPPINGS.get(a.name, "none")
     pwnlib.context.context.bits = a.ptrsize * 8
 
-    arch.update(a.name, a.ptrsize, a.endian)
+    if a.name != pwndbg.aglib.arch.name:
+        pwndbg_arch = get_pwndbg_architecture(a.name)
+        if pwndbg_arch is None:
+            raise pwndbg.dbg_mod.Error(
+                f"Unsupported architecture: {a.name}. "
+                f"It may be that Pwndbg is not correctly categorizing the architecture. "
+                f"Please file a bug report. "
+            )
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: Added a handler to detect unrecognized architectures - previously it would error out on a key error when fetching registers.
created_at: 2025-04-14T08:12:22Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if "v9" in self.name_raw:
+            mode = CS_MODE_V9
+        else:
+            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
+            mode = 0
+
+        return (CS_ARCH_SPARC, mode)
+
+
+class RISCV32Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv32")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV32 | CS_MODE_RISCVC)
+
+
+class RISCV64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV64 | CS_MODE_RISCVC)
+
+
+class MipsArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("mips")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - Capstone v6 increased the number of MIPS constants
+        if "isa32r6" in self.name_raw:
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: I added a comment with an example
created_at: 2025-04-14T08:22:16Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if "v9" in self.name_raw:
+            mode = CS_MODE_V9
+        else:
+            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
+            mode = 0
+
+        return (CS_ARCH_SPARC, mode)
+
+
+class RISCV32Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv32")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV32 | CS_MODE_RISCVC)
+
+
+class RISCV64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("rv64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_RISCV, CS_MODE_RISCV64 | CS_MODE_RISCVC)
+
+
+class MipsArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("mips")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - Capstone v6 increased the number of MIPS constants
+        if "isa32r6" in self.name_raw:
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: I removed name_raw (it would only work with gdb) and replaced it with a new ArchAttribute class so we can give the arch some more information about arch / ISA variants.
created_at: 2025-04-18T07:54:37Z
author_association: MEMBER
diff_hunk: @@ -1,39 +1,314 @@
 from __future__ import annotations
 
+import struct
+from typing import Dict
 from typing import Literal
+from typing import Tuple
 
 import pwnlib
+from capstone import CS_ARCH_ARM
+from capstone import CS_ARCH_ARM64
+from capstone import CS_ARCH_MIPS
+from capstone import CS_ARCH_PPC
+from capstone import CS_ARCH_RISCV
+from capstone import CS_ARCH_SPARC
+from capstone import CS_ARCH_X86
+from capstone import CS_MODE_16
+from capstone import CS_MODE_32
+from capstone import CS_MODE_64
+from capstone import CS_MODE_ARM
+from capstone import CS_MODE_MCLASS
+from capstone import CS_MODE_MIPS32
+from capstone import CS_MODE_MIPS32R6
+from capstone import CS_MODE_MIPS64
+from capstone import CS_MODE_RISCV32
+from capstone import CS_MODE_RISCV64
+from capstone import CS_MODE_RISCVC
+from capstone import CS_MODE_THUMB
+from capstone import CS_MODE_V9
+from typing_extensions import override
 
 import pwndbg
+import pwndbg.aglib
+from pwndbg.aglib import typeinfo
+from pwndbg.lib.abi import ABI
+from pwndbg.lib.abi import DEFAULT_ABIS
+from pwndbg.lib.abi import SIGRETURN_ABIS
+from pwndbg.lib.abi import SYSCALL_ABIS
+from pwndbg.lib.abi import SyscallABI
+from pwndbg.lib.arch import PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
 from pwndbg.lib.arch import PWNLIB_ARCH_MAPPINGS
-from pwndbg.lib.arch import Arch
+from pwndbg.lib.arch import ArchDefinition
+from pwndbg.lib.arch import Platform
 
+EndianType = Literal["little", "big"]
 
-def read_thumb_bit() -> int | None:
+FMT_LITTLE_ENDIAN = {1: "B", 2: "<H", 4: "<I", 8: "<Q"}
+FMT_BIG_ENDIAN = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}
+
+
+class PwndbgArchitecture(ArchDefinition):
     """
-    Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+    This class defines the context of the currently debugged architecture as well as other related information of the platform.
 
-    Return None if the Thumb bit is not relevent to the current architecture
+    This includes the following information:
+    - Capstone/Unicorn constants
+    - ABI information
     """
-    if pwndbg.aglib.arch.name == "arm":
+
+    name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE
+    name_raw: str  # The raw string returned by the debugger
+    endian: EndianType
+    ptrsize: int
+    ptrbits: int
+    ptrmask: int
+    abi: ABI | None
+    syscall_abi: SyscallABI | None
+    sigreturn_abi: SyscallABI | None
+    platform: Platform
+    # environment
+
+    registered_architectures: Dict[PWNDBG_SUPPORTED_ARCHITECTURES_TYPE, PwndbgArchitecture] = {}
+
+    @staticmethod
+    def get_arch(name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> PwndbgArchitecture:
+        if name not in PwndbgArchitecture.registered_architectures:
+            raise NotImplementedError()
+
+        return PwndbgArchitecture.registered_architectures[name]
+
+    def __init__(self, name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE) -> None:
+        """
+        Calling the constructor will register the class with global list of PwndbgArchitectures
+        """
+        self.registered_architectures[name] = self
+
+        self.name: PWNDBG_SUPPORTED_ARCHITECTURES_TYPE = name
+
+        # We have to set some values by default
+        # These will be set again by the code that detects the global architecture
+        self.update(
+            ArchDefinition(
+                name=name,
+                name_raw=name,
+                ptrsize=typeinfo.ptrsize,
+                endian="little",
+                platform=Platform.LINUX,
+            )
+        )
+
+    def update(self, arch_definition: ArchDefinition) -> None:
+        """
+        While debugging a process, certain aspects of the architecture can change.
+
+        For example:
+        - Some architectures can change endianness dynamically.
+        """
+        self.name_raw = arch_definition.name_raw
+        self.platform = arch_definition.platform
+
+        self.endian: EndianType = arch_definition.endian
+
+        # Pointer size in bytes
+        self.ptrsize: int = arch_definition.ptrsize
+        self.ptrbits: int = self.ptrsize * 8
+        self.ptrmask: int = (1 << self.ptrbits) - 1
+
+        default_abi_identifer = (self.ptrbits, self.name, "linux")
+
+        # TODO - allow these to be selected by some other means
+        self.abi = DEFAULT_ABIS.get(default_abi_identifer)
+        self.syscall_abi = SYSCALL_ABIS.get(default_abi_identifer)
+        self.sigreturn_abi = SIGRETURN_ABIS.get(default_abi_identifer)
+
+        self.fmts: Dict[int, str] = FMT_LITTLE_ENDIAN if self.endian == "little" else FMT_BIG_ENDIAN
+        self.fmt: str = self.fmts[self.ptrsize]
+
+    def pack(self, integer: int) -> bytes:
+        return struct.pack(self.fmt, integer & self.ptrmask)
+
+    def unpack(self, data: bytes) -> int:
+        return struct.unpack(self.fmt, data)[0]
+
+    def pack_size(self, integer: int, size: int) -> bytes:
+        return struct.pack(self.fmts[size], integer & self.ptrmask)
+
+    def unpack_size(self, data: bytes, size: int) -> int:
+        return struct.unpack(self.fmts[size], data)[0]
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        """
+        Return tuple of (CAPSTONE ARCH, CAPSTONE MODE) used to instantiate the Capstone disassembler for this architecture.
+        """
+        return (None, None)
+
+    def read_thumb_bit(self) -> Literal[0, 1, None]:
+        """
+        Return 0 or 1, representing the status of the Thumb bit in the current Arm architecture
+
+        Return None if the Thumb bit is not relevent to the current architecture
+        """
+        return None
+
+
+class AMD64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("x86-64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_64)
+
+
+class i386Arch(PwndbgArchitecture):
+    """
+    32-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i386")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_32)
+
+
+class i8086Arch(PwndbgArchitecture):
+    """
+    16-bit mode x86
+    """
+
+    def __init__(self) -> None:
+        super().__init__("i8086")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_X86, CS_MODE_16)
+
+
+class ArmArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("arm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        thumb_mode = pwndbg.aglib.disasm.emulated_arm_mode_cache[address]
+        if thumb_mode is None:
+            thumb_mode = self.read_thumb_bit()
+        mode = CS_MODE_THUMB if thumb_mode else CS_MODE_ARM
+
+        return (CS_ARCH_ARM, mode)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
         # When program initially starts, cpsr may not be readable
         if (cpsr := pwndbg.aglib.regs.cpsr) is not None:
-            return (cpsr >> 5) & 1
-    elif pwndbg.aglib.arch.name == "armcm":
-        # ARM Cortex-M procesors only suport Thumb mode. However, there is still a bit
-        # that represents the Thumb mode (which is currently architecturally defined to be 1)
-        if (xpsr := pwndbg.aglib.regs.xpsr) is not None:
-            return (xpsr >> 24) & 1
-    # AArch64 does not have a Thumb bit
-    return None
+            return (cpsr >> 5) & 1  # type: ignore[return-value]
 
+        return 0
 
-def get_thumb_mode_string() -> Literal["arm", "thumb"] | None:
-    thumb_bit = read_thumb_bit()
-    return None if thumb_bit is None else "thumb" if thumb_bit == 1 else "arm"
+
+class ArmCortexArch(PwndbgArchitecture):
+    """
+    Cortex-M processors run the M-profile Arm architecture.
+    This architecture is prevalent in bare-metal/embedded systems that lack operating systems.
+    Only Thumb-2 instructions are supported, and the Thumb bit is always 1.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("armcm")
+
+    @override
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM, CS_MODE_MCLASS | CS_MODE_THUMB)
+
+    @override
+    def read_thumb_bit(self) -> Literal[0, 1]:
+        """
+        On Cortex-M processors, the Thumb bit is architecturally defined to be 1.
+
+        This is the (xpsr >> 24) & 1, which is always 1.
+        """
+        return 1
+
+
+class AArch64Arch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("aarch64")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        return (CS_ARCH_ARM64, CS_MODE_ARM)
+
+
+class PowerPCArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("powerpc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        # TODO - what about 32-bit powerpc?
+        return (CS_ARCH_PPC, CS_MODE_64)
 
 
-arch: Arch = Arch("i386", 4, "little")
+class SparcArch(PwndbgArchitecture):
+    def __init__(self) -> None:
+        super().__init__("sparc")
+
+    def get_capstone_constants(self, address: int) -> Tuple[int, int]:
+        if "v9" in self.name_raw:
+            mode = CS_MODE_V9
+        else:
+            # The ptrsize base modes cause capstone.CsError: Invalid mode (CS_ERR_MODE)
+            mode = 0
path: pwndbg/aglib/arch.py
user: login: OBarronCS
body: In this specific case, turns out the V9 refers to 64-bit sparc, so I replaced this with a check on the ptrsize.
created_at: 2025-04-18T07:55:27Z
author_association: MEMBER

PR comments:
user: login: OBarronCS
created_at: 2025-03-05T07:53:46Z
body: Regarding @patryk4815's notes here: https://github.com/pwndbg/pwndbg/pull/2367#issuecomment-2488956588, referring to statements like

```python
pwndbg.aglib.arch.current.is_aarch64().is_e()
pwndbg.aglib.arch.current.is_sparc().is_v9()
pwndbg.aglib.arch.current.is_mips().is_r6()
pwndbg.aglib.arch.current.is_arm().read_thumb_bit()
```
 I think this is a good idea and is a little cleaner than other methods of manually checking these sorts of things. Should we limit these sorts of lookups to boolean values? Otherwise, we'd need to somehow indicate the case where the response is not relevant, perhaps responding with `None`?
author_association: MEMBER
user: login: OBarronCS
created_at: 2025-03-26T02:34:57Z
body: Made a lot more changes, putting this into a more final state.

Given the scope of changes, it might be wise to get an initial version in which preserves functionality. Then, future PR's can add on to this to get more of the benefits we want using this new way of representing architecture.
author_association: MEMBER
user: login: codecov[bot]
created_at: 2025-03-26T06:27:58Z
body: ## [Codecov](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) Report
Attention: Patch coverage is `16.66667%` with `220 lines` in your changes missing coverage. Please review.
> Project coverage is 10.12%. Comparing base [(`f2d6348`)](https://app.codecov.io/gh/pwndbg/pwndbg/commit/f2d63487717d26f728b51235b00c5ff222014a89?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) to head [(`764f487`)](https://app.codecov.io/gh/pwndbg/pwndbg/commit/764f4876404dc8393513f8a46d91c5e07869a10e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg).
> Report is 2 commits behind head on dev.

| [Files with missing lines](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) | Patch % | Lines |
|---|---|---|
| [pwndbg/aglib/arch.py](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&filepath=pwndbg%2Faglib%2Farch.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg#diff-cHduZGJnL2FnbGliL2FyY2gucHk=) | 16.08% | [163 Missing and 4 partials :warning: ](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) |
| [pwndbg/lib/arch.py](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&filepath=pwndbg%2Flib%2Farch.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg#diff-cHduZGJnL2xpYi9hcmNoLnB5) | 0.00% | [14 Missing :warning: ](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) |
| [pwndbg/arguments.py](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&filepath=pwndbg%2Farguments.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg#diff-cHduZGJnL2FyZ3VtZW50cy5weQ==) | 18.18% | [7 Missing and 2 partials :warning: ](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) |
| [pwndbg/commands/hijack\_fd.py](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&filepath=pwndbg%2Fcommands%2Fhijack_fd.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg#diff-cHduZGJnL2NvbW1hbmRzL2hpamFja19mZC5weQ==) | 0.00% | [6 Missing :warning: ](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) |
| [pwndbg/dbg/lldb/\_\_init\_\_.py](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&filepath=pwndbg%2Fdbg%2Flldb%2F__init__.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg#diff-cHduZGJnL2RiZy9sbGRiL19faW5pdF9fLnB5) | 0.00% | [5 Missing :warning: ](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) |
| [pwndbg/aglib/\_\_init\_\_.py](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&filepath=pwndbg%2Faglib%2F__init__.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg#diff-cHduZGJnL2FnbGliL19faW5pdF9fLnB5) | 20.00% | [4 Missing :warning: ](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) |
| [pwndbg/aglib/disasm/disassembly.py](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&filepath=pwndbg%2Faglib%2Fdisasm%2Fdisassembly.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg#diff-cHduZGJnL2FnbGliL2Rpc2FzbS9kaXNhc3NlbWJseS5weQ==) | 60.00% | [2 Missing and 2 partials :warning: ](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) |
| [pwndbg/dbg/gdb/\_\_init\_\_.py](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&filepath=pwndbg%2Fdbg%2Fgdb%2F__init__.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg#diff-cHduZGJnL2RiZy9nZGIvX19pbml0X18ucHk=) | 20.00% | [4 Missing :warning: ](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) |
| [pwndbg/lib/gcc.py](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&filepath=pwndbg%2Flib%2Fgcc.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg#diff-cHduZGJnL2xpYi9nY2MucHk=) | 0.00% | [3 Missing :warning: ](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) |
| [pwndbg/aglib/disasm/arch.py](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&filepath=pwndbg%2Faglib%2Fdisasm%2Farch.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg#diff-cHduZGJnL2FnbGliL2Rpc2FzbS9hcmNoLnB5) | 33.33% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) |
| ... and [1 more](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg) | |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##             dev    #2771      +/-   ##
=========================================
+ Coverage   5.39%   10.12%   +4.73%     
=========================================
  Files        226      227       +1     
  Lines      25760    25909     +149     
  Branches    3922     3928       +6     
=========================================
+ Hits        1390     2624    +1234     
+ Misses     24179    22846    -1333     
- Partials     191      439     +248     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/pwndbg/pwndbg/pull/2771?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pwndbg).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: disconnect3d
created_at: 2025-03-28T15:46:12Z
body: > Regarding @patryk4815's notes here: [#2367 (comment)](https://github.com/pwndbg/pwndbg/pull/2367#issuecomment-2488956588), referring to statements like
> 
> ```python
> pwndbg.aglib.arch.current.is_aarch64().is_e()
> pwndbg.aglib.arch.current.is_sparc().is_v9()
> pwndbg.aglib.arch.current.is_mips().is_r6()
> pwndbg.aglib.arch.current.is_arm().read_thumb_bit()
> ```
> 
> I think this is a good idea and is a little cleaner than other methods of manually checking these sorts of things. Should we limit these sorts of lookups to boolean values? Otherwise, we'd need to somehow indicate the case where the response is not relevant, perhaps responding with `None`?

I think its a bit weird to have a `is_something()` return an object that has additional `is_sth_else`.

Would u ever call `is_e()` if you are in a different arch than aarch64? Same for other cases?
If not, then u can just do `current.is_e()` and assume you are in aarch64 that has this method and not bother to have it in base class ( ? ? ? ).

author_association: MEMBER
user: login: OBarronCS
created_at: 2025-03-28T19:01:53Z
body: > I think its a bit weird to have a `is_something()` return an object that has additional `is_sth_else`.
> 
> Would u ever call `is_e()` if you are in a different arch than aarch64? Same for other cases? If not, then u can just do `current.is_e()` and assume you are in aarch64 that has this method and not bother to have it in base class ( ? ? ? ).

I agree - let's not implement this sort of `.is_x().is_sth_else()` pattern.

On the matter of not having the definition in the base class, this would introduce the additional annoyance of a "compile/lint time" error - with our linting rules, it would not be allowed, since the linting would check on the parent `PwndbgArchitecture` type and the method/attribute would not be found. Auto-complete would also be missing. For the sake of this "type safety", I think the next best solution is to include the method definitions in the parent class (which either throw errors or return some default value depending on the context) and have the child class that actually uses it override it (ex: read_thumb_bit())`
author_association: MEMBER
user: login: OBarronCS
created_at: 2025-04-14T08:25:10Z
body: I went through and made changes to address the comments in the review
author_association: MEMBER
user: login: OBarronCS
created_at: 2025-04-17T09:14:44Z
body: Interestingly, tests running on ubuntu 22.04 currently fail because they are on Python 3.10 which has a strange behavior in Protocol `__init__` methods which was fixed in later Python versions - https://github.com/python/cpython/issues/88970

Will have to remove the use of Protocol class in this case I suppose.
author_association: MEMBER
user: login: patryk4815
created_at: 2025-04-17T09:23:28Z
body: > Python 3.10 Support Ends in 1 year and 6 months (31 Oct 2026) 

So we have to wait soo long to drop py3.10 :(


author_association: MEMBER
user: login: disconnect3d
created_at: 2025-04-18T11:25:45Z
body: @OBarronCS pls fix conflicts
author_association: MEMBER