yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
step_controller.cc
Go to the documentation of this file.
2
3#include "absl/strings/str_format.h"
4
5namespace yaze {
6namespace emu {
7namespace debug {
8
10 return opcode == opcode::JSR ||
11 opcode == opcode::JSL ||
12 opcode == opcode::JSR_X;
13}
14
16 return opcode == opcode::RTS ||
17 opcode == opcode::RTL ||
18 opcode == opcode::RTI;
19}
20
22 return opcode == opcode::BCC ||
23 opcode == opcode::BCS ||
24 opcode == opcode::BEQ ||
25 opcode == opcode::BMI ||
26 opcode == opcode::BNE ||
27 opcode == opcode::BPL ||
28 opcode == opcode::BVC ||
29 opcode == opcode::BVS ||
30 opcode == opcode::BRA ||
31 opcode == opcode::BRL ||
32 opcode == opcode::JMP_ABS ||
33 opcode == opcode::JMP_IND ||
34 opcode == opcode::JMP_ABS_X ||
35 opcode == opcode::JMP_LONG ||
36 opcode == opcode::JMP_IND_L;
37}
38
39uint8_t StepController::GetInstructionSize(uint8_t opcode, bool m_flag,
40 bool x_flag) {
41 // Simplified instruction size calculation
42 // For a full implementation, refer to the Disassembler65816 class
43
44 switch (opcode) {
45 // Implied (1 byte)
46 case 0x00: // BRK
47 case 0x18: // CLC
48 case 0x38: // SEC
49 case 0x58: // CLI
50 case 0x78: // SEI
51 case 0xB8: // CLV
52 case 0xD8: // CLD
53 case 0xF8: // SED
54 case 0x1A: // INC A
55 case 0x3A: // DEC A
56 case 0x1B: // TCS
57 case 0x3B: // TSC
58 case 0x4A: // LSR A
59 case 0x5B: // TCD
60 case 0x6A: // ROR A
61 case 0x7B: // TDC
62 case 0x0A: // ASL A
63 case 0x2A: // ROL A
64 case 0x40: // RTI
65 case 0x60: // RTS
66 case 0x6B: // RTL
67 case 0x8A: // TXA
68 case 0x9A: // TXS
69 case 0x9B: // TXY
70 case 0xAA: // TAX
71 case 0xBA: // TSX
72 case 0xBB: // TYX
73 case 0xCA: // DEX
74 case 0xDA: // PHX
75 case 0xEA: // NOP
76 case 0xFA: // PLX
77 case 0xCB: // WAI
78 case 0xDB: // STP
79 case 0xEB: // XBA
80 case 0xFB: // XCE
81 case 0x08: // PHP
82 case 0x28: // PLP
83 case 0x48: // PHA
84 case 0x68: // PLA
85 case 0x88: // DEY
86 case 0x98: // TYA
87 case 0xA8: // TAY
88 case 0xC8: // INY
89 case 0xE8: // INX
90 case 0x5A: // PHY
91 case 0x7A: // PLY
92 case 0x0B: // PHD
93 case 0x2B: // PLD
94 case 0x4B: // PHK
95 case 0x8B: // PHB
96 case 0xAB: // PLB
97 return 1;
98
99 // Relative branch (2 bytes)
100 case 0x10: // BPL
101 case 0x30: // BMI
102 case 0x50: // BVC
103 case 0x70: // BVS
104 case 0x80: // BRA
105 case 0x90: // BCC
106 case 0xB0: // BCS
107 case 0xD0: // BNE
108 case 0xF0: // BEQ
109 return 2;
110
111 // Relative long (3 bytes)
112 case 0x82: // BRL
113 return 3;
114
115 // JSR absolute (3 bytes)
116 case 0x20: // JSR
117 case 0xFC: // JSR (abs,X)
118 return 3;
119
120 // JSL long (4 bytes)
121 case 0x22: // JSL
122 return 4;
123
124 // Absolute (3 bytes)
125 case 0x4C: // JMP abs
126 case 0x6C: // JMP (abs)
127 case 0x7C: // JMP (abs,X)
128 return 3;
129
130 // Absolute long (4 bytes)
131 case 0x5C: // JMP long
132 case 0xDC: // JMP [abs]
133 return 4;
134
135 default:
136 // For other instructions, use reasonable defaults
137 // This is a simplification - for full accuracy use Disassembler65816
138 return 3;
139 }
140}
141
143 uint8_t opcode) const {
144 // Return address is pushed onto stack and is the address of the
145 // instruction following the call
146 uint8_t size = GetInstructionSize(opcode, true, true);
147 uint32_t bank = pc & 0xFF0000;
148
149 if (opcode == opcode::JSL) {
150 // JSL pushes PB along with PC+3, so return is full 24-bit
151 return pc + size;
152 } else {
153 // JSR only pushes 16-bit PC, so return stays in same bank
154 return bank | ((pc + size) & 0xFFFF);
155 }
156}
157
159 uint8_t opcode) const {
160 if (!read_byte_) return 0;
161
162 uint32_t bank = pc & 0xFF0000;
163
164 switch (opcode) {
165 case opcode::JSR:
166 // JSR abs - 16-bit address in current bank
167 return bank | (read_byte_(pc + 1) | (read_byte_(pc + 2) << 8));
168
169 case opcode::JSL:
170 // JSL long - full 24-bit address
171 return read_byte_(pc + 1) |
172 (read_byte_(pc + 2) << 8) |
173 (read_byte_(pc + 3) << 16);
174
175 case opcode::JSR_X:
176 // JSR (abs,X) - indirect, can't easily determine target
177 return 0;
178
179 default:
180 return 0;
181 }
182}
183
185 if (!read_byte_) return;
186
187 uint8_t opcode = read_byte_(pc);
188
189 if (IsCallInstruction(opcode)) {
190 // Push call onto stack
191 uint32_t target = CalculateCallTarget(pc, opcode);
192 uint32_t return_addr = CalculateReturnAddress(pc, opcode);
193 bool is_long = (opcode == opcode::JSL);
194
195 call_stack_.emplace_back(pc, target, return_addr, is_long);
196 } else if (IsReturnInstruction(opcode)) {
197 // Pop from call stack if we have entries
198 if (!call_stack_.empty()) {
199 call_stack_.pop_back();
200 }
201 }
202}
203
205 StepResult result;
206 result.success = false;
207 result.instructions_executed = 0;
208
209 if (!step_ || !get_pc_ || !read_byte_) {
210 result.message = "Step controller not properly configured";
211 return result;
212 }
213
214 uint32_t pc_before = get_pc_();
215 uint8_t opcode = read_byte_(pc_before);
216
217 // Track if this is a call
218 std::optional<CallStackEntry> call_made;
219 if (IsCallInstruction(opcode)) {
220 uint32_t target = CalculateCallTarget(pc_before, opcode);
221 uint32_t return_addr = CalculateReturnAddress(pc_before, opcode);
222 bool is_long = (opcode == opcode::JSL);
223 call_made = CallStackEntry(pc_before, target, return_addr, is_long);
224 call_stack_.push_back(*call_made);
225 }
226
227 // Track if this is a return
228 std::optional<CallStackEntry> return_made;
229 if (IsReturnInstruction(opcode) && !call_stack_.empty()) {
230 return_made = call_stack_.back();
231 call_stack_.pop_back();
232 }
233
234 // Execute the instruction
235 step_();
236 result.instructions_executed = 1;
237
238 uint32_t pc_after = get_pc_();
239 result.new_pc = pc_after;
240 result.success = true;
241 result.call = call_made;
242 result.ret = return_made;
243
244 if (call_made) {
245 result.message = absl::StrFormat("Called $%06X from $%06X",
246 call_made->target_address,
247 call_made->call_address);
248 } else if (return_made) {
249 result.message = absl::StrFormat("Returned to $%06X", pc_after);
250 } else {
251 result.message = absl::StrFormat("Stepped to $%06X", pc_after);
252 }
253
254 return result;
255}
256
257StepResult StepController::StepOver(uint32_t max_instructions) {
258 StepResult result;
259 result.success = false;
260 result.instructions_executed = 0;
261
262 if (!step_ || !get_pc_ || !read_byte_) {
263 result.message = "Step controller not properly configured";
264 return result;
265 }
266
267 uint32_t pc = get_pc_();
268 uint8_t opcode = read_byte_(pc);
269
270 // If not a call instruction, just do a single step
271 if (!IsCallInstruction(opcode)) {
272 return StepInto();
273 }
274
275 // It's a call instruction - execute until we return
276 size_t initial_depth = call_stack_.size();
277 uint32_t return_address = CalculateReturnAddress(pc, opcode);
278
279 // Execute the call
280 auto step_result = StepInto();
281 result.instructions_executed = step_result.instructions_executed;
282 result.call = step_result.call;
283
284 if (!step_result.success) {
285 return step_result;
286 }
287
288 // Now run until we return to the expected depth
289 while (result.instructions_executed < max_instructions) {
290 pc = get_pc_();
291
292 // Check if we've returned to our expected depth
293 if (call_stack_.size() <= initial_depth) {
294 result.success = true;
295 result.new_pc = pc;
296 result.message = absl::StrFormat(
297 "Stepped over subroutine, returned to $%06X after %u instructions",
298 pc, result.instructions_executed);
299 return result;
300 }
301
302 // Check if we hit a breakpoint or error condition
303 uint8_t current_opcode = read_byte_(pc);
304
305 // Step one instruction
306 step_();
307 result.instructions_executed++;
308
309 // Update call stack based on instruction
310 if (IsCallInstruction(current_opcode)) {
311 uint32_t target = CalculateCallTarget(pc, current_opcode);
312 uint32_t ret = CalculateReturnAddress(pc, current_opcode);
313 bool is_long = (current_opcode == opcode::JSL);
314 call_stack_.emplace_back(pc, target, ret, is_long);
315 } else if (IsReturnInstruction(current_opcode) && !call_stack_.empty()) {
316 call_stack_.pop_back();
317 }
318 }
319
320 // Timeout
321 result.success = false;
322 result.new_pc = get_pc_();
323 result.message = absl::StrFormat(
324 "Step over timed out after %u instructions", max_instructions);
325 return result;
326}
327
328StepResult StepController::StepOut(uint32_t max_instructions) {
329 StepResult result;
330 result.success = false;
331 result.instructions_executed = 0;
332
333 if (!step_ || !get_pc_ || !read_byte_) {
334 result.message = "Step controller not properly configured";
335 return result;
336 }
337
338 if (call_stack_.empty()) {
339 result.message = "Cannot step out - call stack is empty";
340 return result;
341 }
342
343 // Target depth is one less than current
344 size_t target_depth = call_stack_.size() - 1;
345
346 // Run until we return to the target depth
347 while (result.instructions_executed < max_instructions) {
348 uint32_t pc = get_pc_();
349 uint8_t opcode = read_byte_(pc);
350
351 // Step one instruction
352 step_();
353 result.instructions_executed++;
354
355 // Update call stack based on instruction
356 if (IsCallInstruction(opcode)) {
357 uint32_t target = CalculateCallTarget(pc, opcode);
358 uint32_t ret = CalculateReturnAddress(pc, opcode);
359 bool is_long = (opcode == opcode::JSL);
360 call_stack_.emplace_back(pc, target, ret, is_long);
361 } else if (IsReturnInstruction(opcode) && !call_stack_.empty()) {
362 CallStackEntry returned = call_stack_.back();
363 call_stack_.pop_back();
364 result.ret = returned;
365
366 // Check if we've returned to target depth
367 if (call_stack_.size() <= target_depth) {
368 result.success = true;
369 result.new_pc = get_pc_();
370 result.message = absl::StrFormat(
371 "Stepped out to $%06X after %u instructions",
372 result.new_pc, result.instructions_executed);
373 return result;
374 }
375 }
376 }
377
378 // Timeout
379 result.success = false;
380 result.new_pc = get_pc_();
381 result.message = absl::StrFormat(
382 "Step out timed out after %u instructions", max_instructions);
383 return result;
384}
385
386} // namespace debug
387} // namespace emu
388} // namespace yaze
StepResult StepInto()
Step a single instruction and update call stack.
uint32_t CalculateCallTarget(uint32_t pc, uint8_t opcode) const
uint32_t CalculateReturnAddress(uint32_t pc, uint8_t opcode) const
StepResult StepOver(uint32_t max_instructions=1000000)
Step over the current instruction.
static bool IsReturnInstruction(uint8_t opcode)
Check if an opcode is a return instruction (RTS/RTL/RTI)
static bool IsCallInstruction(uint8_t opcode)
Check if an opcode is a call instruction (JSR/JSL)
static bool IsBranchInstruction(uint8_t opcode)
Check if an opcode is a branch instruction.
static uint8_t GetInstructionSize(uint8_t opcode, bool m_flag, bool x_flag)
Get instruction size for step over calculations.
std::vector< CallStackEntry > call_stack_
StepResult StepOut(uint32_t max_instructions=1000000)
Step out of the current subroutine.
constexpr uint8_t JMP_IND
constexpr uint8_t JMP_LONG
constexpr uint8_t JMP_IND_L
constexpr uint8_t JMP_ABS
constexpr uint8_t JMP_ABS_X
Tracks call stack for intelligent stepping.
Result of a step operation.
std::optional< CallStackEntry > call
std::optional< CallStackEntry > ret