From 98cd6d9addb5e55c70df19974babadb06aa5060d Mon Sep 17 00:00:00 2001 From: OBarronCS <55004530+OBarronCS@users.noreply.github.com> Date: Fri, 12 Dec 2025 07:25:40 -0500 Subject: [PATCH] Cleanup emulator register reads (#3489) --- pwndbg/aglib/disasm/arch.py | 6 +-- pwndbg/aglib/disasm/disassembly.py | 2 +- pwndbg/emu/emulator.py | 63 +++++++++--------------------- 3 files changed, 23 insertions(+), 48 deletions(-) diff --git a/pwndbg/aglib/disasm/arch.py b/pwndbg/aglib/disasm/arch.py index b33a38dbe..5c5bb92a9 100644 --- a/pwndbg/aglib/disasm/arch.py +++ b/pwndbg/aglib/disasm/arch.py @@ -212,10 +212,10 @@ class DisassemblyAssistant: # Ensure emulator's program counter is at the correct location. # This occurs very rarely - observed sometimes when the remote is stalling, ctrl-c, and for some reason emulator returns PC=0. if emu: - if emu.pc != instruction.address: + if emu.pc() != instruction.address: if DEBUG_ENHANCEMENT: print( - f"Program counter and emu.pc do not line up: {hex(pwndbg.aglib.regs.pc)=} {hex(emu.pc)=}" + f"Program counter and emu.pc do not line up: {hex(pwndbg.aglib.regs.pc)=} {hex(emu.pc())=}" ) emu = jump_emu = None @@ -716,7 +716,7 @@ class DisassemblyAssistant: # 1. Only use it to determine non-call's (`nexti` should step over calls) # 2. Make sure we haven't manually set .condition to False (which should override the emulators prediction) if not instruction.call_like and instruction.condition != InstructionCondition.FALSE: - next_addr = jump_emu.pc + next_addr = jump_emu.pc() # Handle edge case - if the target happens to be the next address in memory and it's a jump, we need this variable # so the disasm output is accurate. diff --git a/pwndbg/aglib/disasm/disassembly.py b/pwndbg/aglib/disasm/disassembly.py index 89ec20bcb..ca7a9624e 100644 --- a/pwndbg/aglib/disasm/disassembly.py +++ b/pwndbg/aglib/disasm/disassembly.py @@ -466,7 +466,7 @@ def near( # Upon execution the previous instruction, the Thumb mode bit may have changed. # This means we know whether the next instruction executed will be Thumb or not. # This returns None in the case the Thumb bit is not relevent. - last_emulated_thumb_bit_value = emulated_arm_mode_cache[emu.pc] = ( + last_emulated_thumb_bit_value = emulated_arm_mode_cache[emu.pc()] = ( emu.read_thumb_bit() ) diff --git a/pwndbg/emu/emulator.py b/pwndbg/emu/emulator.py index 42b427002..5ef46f9aa 100644 --- a/pwndbg/emu/emulator.py +++ b/pwndbg/emu/emulator.py @@ -273,7 +273,7 @@ class Emulator: debug(DEBUG_INIT, "# Setting TLB mode to virtual") self.uc.ctl_set_tlb_mode(U.UC_TLB_VIRTUAL) # type: ignore[attr-defined] - self.regs: pwndbg.lib.regs.RegisterSet = pwndbg.aglib.regs.current + self.reg_set: pwndbg.lib.regs.RegisterSet = pwndbg.aglib.regs.current # Whether the emulator is allowed to emulate instructions # There are cases when the emulator is incorrect or we want to disable it for certain instruction types, @@ -292,7 +292,7 @@ class Emulator: self.last_single_step_result = InstructionExecutedResult(None, None) # Initialize the register state - for emu_reg in self.regs.emulated_regs_order: + for emu_reg in self.reg_set.emulated_regs_order: reg = emu_reg.name enum = self.get_reg_enum(reg) @@ -344,6 +344,9 @@ class Emulator: return None # raise AttributeError(f"AttributeError: {self!r} object has no register {name!r}") + def pc(self) -> int: + return self.read_register(self.reg_set.pc) + # Read size worth of memory, return None on error def read_memory(self, address: int, size: int) -> bytes | None: # Don't attempt if the address is not mapped on the host process @@ -587,18 +590,10 @@ class Emulator: return sz[:max_string_len] + "..." - def __getattr__(self, name: str): - reg = self.get_reg_enum(name) - - if reg: - return self.uc.reg_read(reg) - - raise AttributeError(f"AttributeError: {self!r} object has no attribute {name!r}") - def update_pc(self, pc=None) -> None: if pc is None: pc = pwndbg.aglib.regs.pc - self.uc.reg_write(self.get_reg_enum(self.regs.pc), pc) + self.uc.reg_write(self.get_reg_enum(self.reg_set.pc), pc) def read_thumb_bit(self) -> int: """ @@ -612,10 +607,10 @@ class Emulator: Mimics the `read_thumb_bit` function defined in aglib/arch.py """ if self.arch == "arm": - if (cpsr := self.cpsr) is not None: + if (cpsr := self.read_register("cpsr")) is not None: return (cpsr >> 5) & 1 elif self.arch == "armcm": - if (xpsr := self.xpsr) is not None: + if (xpsr := self.read_register("xpsr")) is not None: return (xpsr >> 24) & 1 return 0 @@ -717,35 +712,15 @@ class Emulator: Also supports general registers like 'sp' and 'pc'. """ - if not self.regs: + if not self.reg_set: return None - # If we're looking for an exact register ('eax', 'ebp', 'r0') then - # we can look those up easily. - # - # 'eax' ==> enum - # - # if reg in self.regs.all: + # Look up the Unicorn enum for an exact register ('eax', 'ebp', 'r0') + # This does not handle aliases, such as "sp" or "pc" e = self.const_regs.get(reg.upper(), None) if e is not None: return e - # If we're looking for an abstract register which *is* accounted for, - # we can also do an indirect lookup. - # - # 'pc' ==> 'eip' ==> enum - # - if hasattr(self.regs, reg): - return self.get_reg_enum(getattr(self.regs, reg)) - - # If we're looking for an abstract register which does not exist on - # the RegisterSet objects, we need to do an indirect lookup. - # - # 'sp' ==> 'stack' ==> 'esp' ==> enum - # - elif reg == "sp": - return self.get_reg_enum(self.regs.stack) - return None def hook_add(self, *a, **kw): @@ -769,7 +744,7 @@ class Emulator: def emulate_with_hook(self, hook, count=512) -> None: ident = self.hook_add(U.UC_HOOK_CODE, hook) - pc: int = self.pc + pc: int = self.pc() # Unicorn appears to disregard the UC_MODE_THUMB mode passed into the constructor, and instead # determines Thumb mode based on the PC that is passed to the `emu_start` function # https://github.com/unicorn-engine/unicorn/issues/391 @@ -884,7 +859,7 @@ class Emulator: self.last_single_step_result = InstructionExecutedResult(None, None) - pc = pc or self.pc + pc = pc or self.pc() if instruction is None: instruction = pwndbg.aglib.disasm.disassembly.one_raw(pc) @@ -912,7 +887,7 @@ class Emulator: # If above call does not throw an Exception, we successfully executed the instruction self.last_pc = pc - debug(DEBUG_EXECUTING, "Unicorn now at pc=%#x", self.pc) + debug(DEBUG_EXECUTING, "Unicorn now at pc=%#x", self.pc()) except U.unicorn.UcError: debug(DEBUG_EXECUTING, "Emulator failed to execute instruction") self.last_single_step_result = InstructionExecutedResult(None, None) @@ -940,10 +915,10 @@ class Emulator: # For debugging def dumpregs(self) -> None: for reg in ( - list(self.regs.retaddr) - + list(self.regs.misc) - + list(self.regs.common) - + list(self.regs.flags) + list(self.reg_set.retaddr) + + list(self.reg_set.misc) + + list(self.reg_set.common) + + list(self.reg_set.flags) ): enum = self.get_reg_enum(reg) @@ -960,4 +935,4 @@ class Emulator: debug(DEBUG_TRACE, "# trace_hook: %#-8x %r", (address, data)) def __repr__(self) -> str: - return f"Valid: {self.valid}, PC: {self.pc:#x}" + return f"Valid: {self.valid}, PC: {self.pc():#x}"