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