Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
def _check_stack_manipulation(
i: Instruction, stack_entries: List[StackEntry]
) -> Optional[Instruction]:
# Check whether this return is operating on a manipulated stack.
call_op = (Op.JSR, Op.RTS) if i.operation == Op.RTS else (Op.JSL, Op.RTL)
# Non-call instructions which operated on the region of the
# stack containing the return address from the subroutine.
stack_manipulators = [
s.instruction
for s in stack_entries
if not s.instruction or s.instruction.operation not in call_op
]
if stack_manipulators:
return stack_manipulators[-1]
return None
targets = self._calculate_targets(i, targets)
saved_state, saved_state_change = copy(self.state), copy(self.state_change)
possible_states = set()
for _, target in targets:
if target is None or self.rom.is_ram(target):
# If we can't reliably derive the address of the subroutine
# being called, we're left in an unknown state.
return self._unknown_subroutine_state(
i, unknown_reason=UnknownReason.INDIRECT_JUMP
)
# Run a parallel instance of the CPU to execute
# the subroutine that is being called.
cpu = self.copy(new_subroutine=True)
call_size = 2 if i.operation in (Op.JSR, Op.RTS) else 3
cpu.stack.push(i, i.pc, call_size)
cpu.stack_trace.append(self.subroutine_pc)
cpu.subroutine_pc = target
cpu.pc = target
# Emulate the called subroutine.
self.log.add_reference(i, target)
self.log.add_subroutine(target, stack_trace=cpu.stack_trace)
cpu.run()
# If we univocally know what the return state of the
# called subroutine is, we can propagate it to the
# current CPU state. Otherwise, to be on the safe
# side, we need to stop the execution.
known, unknown_reason = self._propagate_subroutine_state(i.pc, target)
if known or self._unknown_subroutine_state(
def push(self, instruction: Instruction) -> None:
if instruction.operation == Op.PHP:
self.stack.push(instruction, (copy(self.state), copy(self.state_change)))
elif instruction.operation == Op.PHA:
self.stack.push(instruction, self.registers.a.get(), self.state.a_size)
elif instruction.operation in (Op.PHX, Op.PHY):
self.stack.push(instruction, size=self.state.x_size)
elif instruction.operation in (Op.PHB, Op.PHK):
self.stack.push(instruction)
elif instruction.operation in (Op.PHD, Op.PEA, Op.PER):
self.stack.push(instruction, size=2)
else:
assert False
def pop(self, i: Instruction) -> bool:
if i.operation == Op.PLP:
entry = self.stack.pop_one()
if entry.instruction and entry.instruction.operation == Op.PHP:
self.state, self.state_change = entry.data
# We can't trust the disassembly if we don't know
# which state the PLP instruction is restoring.
else:
return self._unknown_subroutine_state(
i,
unknown_reason=UnknownReason.STACK_MANIPULATION,
stack_manipulator=entry.instruction,
)
elif i.operation in (Op.PLX, Op.PLY):
self.stack.pop(self.state.x_size)
elif i.operation == Op.PLB:
self.stack.pop_one()
elif i.operation == Op.PLD:
self.stack.pop(2)
else:
assert False
return True
(Op.TSB, AddressMode.ABSOLUTE),
(Op.ORA, AddressMode.ABSOLUTE),
(Op.ASL, AddressMode.ABSOLUTE),
(Op.ORA, AddressMode.ABSOLUTE_LONG),
(Op.BPL, AddressMode.RELATIVE),
(Op.ORA, AddressMode.DIRECT_PAGE_INDIRECT_INDEXED),
(Op.ORA, AddressMode.DIRECT_PAGE_INDIRECT),
(Op.ORA, AddressMode.STACK_RELATIVE_INDIRECT_INDEXED),
(Op.TRB, AddressMode.DIRECT_PAGE),
(Op.ORA, AddressMode.DIRECT_PAGE_INDEXED_X),
(Op.ASL, AddressMode.DIRECT_PAGE_INDEXED_X),
(Op.ORA, AddressMode.DIRECT_PAGE_INDIRECT_INDEXED_LONG),
(Op.CLC, AddressMode.IMPLIED),
(Op.ORA, AddressMode.ABSOLUTE_INDEXED_Y),
(Op.INC, AddressMode.IMPLIED_ACCUMULATOR),
(Op.TCS, AddressMode.IMPLIED),
(Op.TRB, AddressMode.ABSOLUTE),
(Op.ORA, AddressMode.ABSOLUTE_INDEXED_X),
(Op.ASL, AddressMode.ABSOLUTE_INDEXED_X),
(Op.ORA, AddressMode.ABSOLUTE_INDEXED_LONG),
(Op.JSR, AddressMode.ABSOLUTE),
(Op.AND, AddressMode.DIRECT_PAGE_INDEXED_INDIRECT),
(Op.JSL, AddressMode.ABSOLUTE_LONG),
(Op.AND, AddressMode.STACK_RELATIVE),
(Op.BIT, AddressMode.DIRECT_PAGE),
(Op.AND, AddressMode.DIRECT_PAGE),
(Op.ROL, AddressMode.DIRECT_PAGE),
(Op.AND, AddressMode.DIRECT_PAGE_INDIRECT_LONG),
(Op.PLP, AddressMode.IMPLIED),
(Op.AND, AddressMode.IMMEDIATE_M),
(Op.ROL, AddressMode.IMPLIED_ACCUMULATOR),
(Op.PLD, AddressMode.IMPLIED),
def change_a(self, i: Instruction) -> None:
if i.address_mode == AddressMode.IMMEDIATE_M:
assert i.argument is not None
a = self.registers.a.get()
if i.operation == Op.LDA:
self.registers.a.set(i.argument)
elif a is not None:
if i.operation == Op.ADC:
# TODO: handle carry flag.
self.registers.a.set(a + i.argument)
elif i.operation == Op.SBC:
# TODO: handle negative flag.
self.registers.a.set(a - i.argument)
elif i.operation == Op.TSC:
self.registers.a.set_whole(self.stack.pointer)
elif i.operation == Op.PLA:
self.stack.pop(self.state.a_size)
else:
self.registers.a.set(None)
def change_stack(self, i: Instruction) -> None:
if i.operation == Op.TCS:
a = self.registers.a.get_whole()
self.stack.set_pointer(i, a)
if a is not None:
return
# We keep the disassembly going if the stack manipulation
# doesn't otherwise influence the state of the processor.
i.stack_manipulation = StackManipulation.HARMLESS
def change_a(self, i: Instruction) -> None:
if i.address_mode == AddressMode.IMMEDIATE_M:
assert i.argument is not None
a = self.registers.a.get()
if i.operation == Op.LDA:
self.registers.a.set(i.argument)
elif a is not None:
if i.operation == Op.ADC:
# TODO: handle carry flag.
self.registers.a.set(a + i.argument)
elif i.operation == Op.SBC:
# TODO: handle negative flag.
self.registers.a.set(a - i.argument)
elif i.operation == Op.TSC:
self.registers.a.set_whole(self.stack.pointer)
elif i.operation == Op.PLA:
self.stack.pop(self.state.a_size)
else:
self.registers.a.set(None)
(Op.JMP, AddressMode.ABSOLUTE),
(Op.EOR, AddressMode.ABSOLUTE),
(Op.LSR, AddressMode.ABSOLUTE),
(Op.EOR, AddressMode.ABSOLUTE_LONG),
(Op.BVC, AddressMode.RELATIVE),
(Op.EOR, AddressMode.DIRECT_PAGE_INDIRECT_INDEXED),
(Op.EOR, AddressMode.DIRECT_PAGE_INDIRECT),
(Op.EOR, AddressMode.STACK_RELATIVE_INDIRECT_INDEXED),
(Op.MVN, AddressMode.MOVE),
(Op.EOR, AddressMode.DIRECT_PAGE_INDEXED_X),
(Op.LSR, AddressMode.DIRECT_PAGE_INDEXED_X),
(Op.EOR, AddressMode.DIRECT_PAGE_INDIRECT_INDEXED_LONG),
(Op.CLI, AddressMode.IMPLIED),
(Op.EOR, AddressMode.ABSOLUTE_INDEXED_Y),
(Op.PHY, AddressMode.IMPLIED),
(Op.TCD, AddressMode.IMPLIED),
(Op.JML, AddressMode.ABSOLUTE_LONG),
(Op.EOR, AddressMode.ABSOLUTE_INDEXED_X),
(Op.LSR, AddressMode.ABSOLUTE_INDEXED_X),
(Op.EOR, AddressMode.ABSOLUTE_INDEXED_LONG),
(Op.RTS, AddressMode.IMPLIED),
(Op.ADC, AddressMode.DIRECT_PAGE_INDEXED_INDIRECT),
(Op.PER, AddressMode.RELATIVE_LONG),
(Op.ADC, AddressMode.STACK_RELATIVE),
(Op.STZ, AddressMode.DIRECT_PAGE),
(Op.ADC, AddressMode.DIRECT_PAGE),
(Op.ROR, AddressMode.DIRECT_PAGE),
(Op.ADC, AddressMode.DIRECT_PAGE_INDIRECT_LONG),
(Op.PLA, AddressMode.IMPLIED),
(Op.ADC, AddressMode.IMMEDIATE_M),
(Op.ROR, AddressMode.IMPLIED_ACCUMULATOR),
(Op.RTL, AddressMode.IMPLIED),
Op.STX: "Store Index Register X to Memory",
Op.STY: "Store Index Register Y to Memory",
Op.STZ: "Store Zero to Memory",
Op.TAX: "Transfer Accumulator to Index Register X",
Op.TAY: "Transfer Accumulator to Index Register Y",
Op.TCD: "Transfer 16-bit Accumulator to Direct Page Register",
Op.TCS: "Transfer 16-bit Accumulator to Stack Pointer",
Op.TDC: "Transfer Direct Page Register to 16-bit Accumulator",
Op.TRB: "Test and Reset Memory Bits Against Accumulator",
Op.TSB: "Test and Set Memory Bits Against Accumulator",
Op.TSC: "Transfer Stack Pointer to 16-bit Accumulator",
Op.TSX: "Transfer Stack Pointer to Index Register X",
Op.TXA: "Transfer Index Register X to Accumulator",
Op.TXS: "Transfer Index Register X to Stack Pointer",
Op.TXY: "Transfer Index Register X to Index Register Y",
Op.TYA: "Transfer Index Register Y to Accumulator",
Op.TYX: "Transfer Index Register Y to Index Register X",
Op.WAI: "Wait for Interrupt",
Op.WDM: "Reserved for Future Expansion",
Op.XBA: "Exchange B and A 8-bit Accumulators",
Op.XCE: "Exchange Carry and Emulation Flags",
}