Files
yaze/src/app/emu/debug/step_controller.h

201 lines
6.2 KiB
C++

#ifndef YAZE_APP_EMU_DEBUG_STEP_CONTROLLER_H_
#define YAZE_APP_EMU_DEBUG_STEP_CONTROLLER_H_
#include <cstdint>
#include <functional>
#include <optional>
#include <stack>
#include <string>
#include <vector>
namespace yaze {
namespace emu {
namespace debug {
/**
* @brief Tracks call stack for intelligent stepping
*
* The 65816 uses these instructions for subroutine calls:
* - JSR (opcode 0x20): Jump to Subroutine (16-bit address, pushes PC+2)
* - JSL (opcode 0x22): Jump to Subroutine Long (24-bit address, pushes PB + PC+3)
*
* And these for returns:
* - RTS (opcode 0x60): Return from Subroutine (pulls PC)
* - RTL (opcode 0x6B): Return from Subroutine Long (pulls PB + PC)
* - RTI (opcode 0x40): Return from Interrupt (pulls status, PC, PB)
*/
struct CallStackEntry {
uint32_t call_address; // Address where the call was made
uint32_t target_address; // Target of the call (subroutine start)
uint32_t return_address; // Expected return address
bool is_long; // True for JSL, false for JSR
std::string symbol; // Symbol name at target (if available)
CallStackEntry(uint32_t call, uint32_t target, uint32_t ret, bool long_call)
: call_address(call),
target_address(target),
return_address(ret),
is_long(long_call) {}
};
/**
* @brief Result of a step operation
*/
struct StepResult {
bool success;
uint32_t new_pc; // New program counter
uint32_t instructions_executed; // Number of instructions stepped
std::string message;
std::optional<CallStackEntry> call; // If a call was made
std::optional<CallStackEntry> ret; // If a return was made
};
/**
* @brief Controller for intelligent step operations
*
* Provides step-over, step-out, and step-into functionality by tracking
* the call stack during execution.
*
* Usage:
* StepController controller;
* controller.SetMemoryReader([&](uint32_t addr) { return mem.ReadByte(addr); });
* controller.SetSingleStepper([&]() { cpu.ExecuteInstruction(); });
*
* // Step over a JSR - will run until it returns
* auto result = controller.StepOver(current_pc);
*
* // Step out of current subroutine
* auto result = controller.StepOut(current_pc, call_depth);
*/
class StepController {
public:
using MemoryReader = std::function<uint8_t(uint32_t)>;
using SingleStepper = std::function<void()>;
using PcGetter = std::function<uint32_t()>;
StepController() = default;
void SetMemoryReader(MemoryReader reader) { read_byte_ = reader; }
void SetSingleStepper(SingleStepper stepper) { step_ = stepper; }
void SetPcGetter(PcGetter getter) { get_pc_ = getter; }
/**
* @brief Step a single instruction and update call stack
* @return Step result with call stack info
*/
StepResult StepInto();
/**
* @brief Step over the current instruction
*
* If the current instruction is JSR/JSL, this executes until
* the subroutine returns. Otherwise, it's equivalent to StepInto.
*
* @param max_instructions Maximum instructions before timeout
* @return Step result
*/
StepResult StepOver(uint32_t max_instructions = 1000000);
/**
* @brief Step out of the current subroutine
*
* Executes until RTS/RTL returns to a higher call level.
*
* @param max_instructions Maximum instructions before timeout
* @return Step result
*/
StepResult StepOut(uint32_t max_instructions = 1000000);
/**
* @brief Get the current call stack
*/
const std::vector<CallStackEntry>& GetCallStack() const {
return call_stack_;
}
/**
* @brief Get the current call depth
*/
size_t GetCallDepth() const { return call_stack_.size(); }
/**
* @brief Clear the call stack (e.g., on reset)
*/
void ClearCallStack() { call_stack_.clear(); }
/**
* @brief Check if an opcode is a call instruction (JSR/JSL)
*/
static bool IsCallInstruction(uint8_t opcode);
/**
* @brief Check if an opcode is a return instruction (RTS/RTL/RTI)
*/
static bool IsReturnInstruction(uint8_t opcode);
/**
* @brief Check if an opcode is a branch instruction
*/
static bool IsBranchInstruction(uint8_t opcode);
/**
* @brief Get instruction size for step over calculations
*/
static uint8_t GetInstructionSize(uint8_t opcode, bool m_flag, bool x_flag);
private:
// Process instruction and update call stack
void ProcessInstruction(uint32_t pc);
// Calculate return address for call
uint32_t CalculateReturnAddress(uint32_t pc, uint8_t opcode) const;
// Calculate target address for call
uint32_t CalculateCallTarget(uint32_t pc, uint8_t opcode) const;
MemoryReader read_byte_;
SingleStepper step_;
PcGetter get_pc_;
std::vector<CallStackEntry> call_stack_;
};
// Static helper functions for opcode classification
namespace opcode {
// Call instructions
constexpr uint8_t JSR = 0x20; // Jump to Subroutine (absolute)
constexpr uint8_t JSL = 0x22; // Jump to Subroutine Long
constexpr uint8_t JSR_X = 0xFC; // Jump to Subroutine (absolute,X)
// Return instructions
constexpr uint8_t RTS = 0x60; // Return from Subroutine
constexpr uint8_t RTL = 0x6B; // Return from Subroutine Long
constexpr uint8_t RTI = 0x40; // Return from Interrupt
// Branch instructions (conditional)
constexpr uint8_t BCC = 0x90; // Branch if Carry Clear
constexpr uint8_t BCS = 0xB0; // Branch if Carry Set
constexpr uint8_t BEQ = 0xF0; // Branch if Equal (Z=1)
constexpr uint8_t BMI = 0x30; // Branch if Minus (N=1)
constexpr uint8_t BNE = 0xD0; // Branch if Not Equal (Z=0)
constexpr uint8_t BPL = 0x10; // Branch if Plus (N=0)
constexpr uint8_t BVC = 0x50; // Branch if Overflow Clear
constexpr uint8_t BVS = 0x70; // Branch if Overflow Set
constexpr uint8_t BRA = 0x80; // Branch Always (relative)
constexpr uint8_t BRL = 0x82; // Branch Long (relative long)
// Jump instructions
constexpr uint8_t JMP_ABS = 0x4C; // Jump Absolute
constexpr uint8_t JMP_IND = 0x6C; // Jump Indirect
constexpr uint8_t JMP_ABS_X = 0x7C; // Jump Absolute Indexed Indirect
constexpr uint8_t JMP_LONG = 0x5C; // Jump Long
constexpr uint8_t JMP_IND_L = 0xDC; // Jump Indirect Long
} // namespace opcode
} // namespace debug
} // namespace emu
} // namespace yaze
#endif // YAZE_APP_EMU_DEBUG_STEP_CONTROLLER_H_