yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
rom_debug_agent.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <iomanip>
5#include <sstream>
6
7#include "absl/strings/str_format.h"
8#include "absl/strings/str_join.h"
9#include "absl/strings/str_split.h"
10#include "util/log.h"
11
12namespace yaze {
13namespace cli {
14namespace agent {
15
16using namespace yaze::agent;
17
18namespace {
19
20// Helper to format a 24-bit SNES address
21std::string FormatSnesAddress(uint32_t address) {
22 return absl::StrFormat("$%02X:%04X", (address >> 16) & 0xFF, address & 0xFFFF);
23}
24
25// Helper to check if an opcode is a branch/jump instruction
26bool IsBranchOrJump(uint8_t opcode) {
27 // 65816 branch and jump opcodes
28 static const std::set<uint8_t> branch_jump_opcodes = {
29 0x10, // BPL
30 0x30, // BMI
31 0x50, // BVC
32 0x70, // BVS
33 0x80, // BRA
34 0x82, // BRL
35 0x90, // BCC
36 0xB0, // BCS
37 0xD0, // BNE
38 0xF0, // BEQ
39 0x4C, // JMP abs
40 0x5C, // JML long
41 0x6C, // JMP (abs)
42 0x7C, // JMP (abs,X)
43 0xDC, // JML [abs]
44 0x20, // JSR abs
45 0x22, // JSL long
46 0xFC, // JSR (abs,X)
47 };
48 return branch_jump_opcodes.count(opcode) > 0;
49}
50
51// Helper to check if an opcode is a return instruction
52bool IsReturn(uint8_t opcode) {
53 return opcode == 0x60 || // RTS
54 opcode == 0x6B || // RTL
55 opcode == 0x40; // RTI
56}
57
58// Helper to check if an opcode modifies the stack
59bool ModifiesStack(uint8_t opcode) {
60 static const std::set<uint8_t> stack_opcodes = {
61 0x48, 0x08, // PHA, PHP
62 0x68, 0x28, // PLA, PLP
63 0xDA, 0x5A, // PHX, PHY
64 0xFA, 0x7A, // PLX, PLY
65 0x8B, // PHB
66 0xAB, // PLB
67 0x0B, // PHD
68 0x2B, // PLD
69 0x4B, // PHK
70 0x62, // PER
71 0xD4, // PEI
72 0xF4, // PEA
73 };
74 return stack_opcodes.count(opcode) > 0;
75}
76
77} // namespace
78
80 : emulator_service_(emulator_service),
81 disassembler_(std::make_unique<Disassembler65816>()),
82 symbol_provider_(std::make_unique<yaze::emu::debug::SymbolProvider>()) {
83 // Initialize with default M and X flags (8-bit mode)
84 disassembler_->SetFlags(true, true);
85}
86
87absl::StatusOr<RomDebugAgent::BreakpointAnalysis> RomDebugAgent::AnalyzeBreakpoint(
88 const yaze::agent::BreakpointHitResponse& hit) {
89 BreakpointAnalysis analysis;
90
91 // Basic information from the hit
92 if (hit.has_breakpoint()) {
93 analysis.address = hit.breakpoint().address();
94 } else {
95 analysis.address = hit.cpu_state().pc();
96 }
97
98 // Get symbol or format address
99 if (symbol_provider_->HasSymbols()) {
100 analysis.location_description = symbol_provider_->FormatAddress(analysis.address);
101 } else {
102 analysis.location_description = FormatSnesAddress(analysis.address);
103 }
104
105 // Extract registers from the hit response
106 const auto& cpu = hit.cpu_state();
107 analysis.registers["A"] = cpu.a();
108 analysis.registers["X"] = cpu.x();
109 analysis.registers["Y"] = cpu.y();
110 analysis.registers["S"] = cpu.sp();
111 analysis.registers["PC"] = cpu.pc();
112 analysis.registers["P"] = cpu.status();
113 analysis.registers["DB"] = cpu.db();
114 analysis.registers["PB"] = cpu.pb();
115
116 // Disassemble the current instruction
117 grpc::ServerContext context;
118 yaze::agent::DisassemblyRequest disasm_req;
119 disasm_req.set_start_address(analysis.address);
120 disasm_req.set_count(1);
121 yaze::agent::DisassemblyResponse disasm_resp;
122
123 auto status = emulator_service_->GetDisassembly(&context, &disasm_req, &disasm_resp);
124 if (!status.ok()) {
125 return absl::InternalError("Failed to get disassembly");
126 }
127
128 if (disasm_resp.lines_size() > 0) {
129 const auto& inst = disasm_resp.lines(0);
130 analysis.disassembly = inst.mnemonic() + " " + inst.operand_str();
131
132 // Analyze instruction for AI explanation
133 auto explanation = AnalyzeInstruction(analysis.address, nullptr, 0); // We don't have raw bytes here easily without reading memory
134 if (explanation.ok()) {
135 analysis.instruction_explanation = *explanation;
136 }
137 }
138
139 // Get context (surrounding lines)
140 analysis.context_lines = GetDisassemblyContext(analysis.address, 5, 5);
141
142 // Build call stack
143 analysis.call_stack = BuildCallStack(cpu.pc());
144
145 // Detect issues
146 auto issue = DetectIssuePattern(analysis.address, nullptr, 0);
147 if (issue) {
148 analysis.suggestions.push_back(issue->suggested_fix);
149 }
150
151 return analysis;
152}
153
154absl::StatusOr<RomDebugAgent::MemoryAnalysis> RomDebugAgent::AnalyzeMemory(
155 uint32_t address, size_t length) {
156 MemoryAnalysis analysis;
157 analysis.address = address;
158 analysis.length = length;
159
160 // Read the memory
161 grpc::ServerContext context;
162 MemoryRequest mem_req;
163 mem_req.set_address(address);
164 mem_req.set_size(length);
165 MemoryResponse mem_resp;
166
167 auto status = emulator_service_->ReadMemory(&context, &mem_req, &mem_resp);
168 if (!status.ok()) {
169 return absl::InternalError("Failed to read memory");
170 }
171
172 analysis.data.assign(mem_resp.data().begin(), mem_resp.data().end());
173
174 // Identify the data type and structure
175 analysis.data_type = IdentifyDataType(address);
176
177 // Get structure information if available
178 auto struct_info = GetStructureInfo(address);
179 if (struct_info.has_value()) {
180 analysis.structure_name = struct_info.value();
181 }
182
183 // Generate description
184 analysis.description = DescribeMemoryLocation(address);
185
186 // Parse fields for known structures
187 if (address >= SPRITE_TABLE_START && address < SPRITE_TABLE_END) {
188 // Parse sprite data
189 uint32_t sprite_index = (address - SPRITE_TABLE_START) / 0x10;
190 analysis.fields["sprite_index"] = sprite_index;
191
192 if (length >= 0x10) {
193 analysis.fields["state"] = analysis.data[0x00];
194 analysis.fields["x_pos_low"] = analysis.data[0x01];
195 analysis.fields["x_pos_high"] = analysis.data[0x02];
196 analysis.fields["y_pos_low"] = analysis.data[0x03];
197 analysis.fields["y_pos_high"] = analysis.data[0x04];
198
199 // Check for anomalies
200 if (analysis.data[0x00] == 0x00) {
201 analysis.anomalies.push_back("Sprite is inactive (state = 0)");
202 }
203 if (analysis.data[0x02] > 0x01) {
204 analysis.anomalies.push_back("Sprite X position exceeds screen bounds");
205 }
206 }
207 } else if (address >= SRAM_START && address <= SRAM_END) {
208 // Parse save data
209 if (address == PLAYER_HEALTH) {
210 analysis.fields["current_health"] = analysis.data[0];
211 if (analysis.data[0] == 0) {
212 analysis.anomalies.push_back("Player health is zero - death state");
213 }
214 } else if (address == PLAYER_MAX_HEALTH) {
215 analysis.fields["max_health"] = analysis.data[0];
216 } else if (address >= INVENTORY_START && address < INVENTORY_START + 0x40) {
217 uint32_t item_slot = address - INVENTORY_START;
218 analysis.fields["inventory_slot"] = item_slot;
219 analysis.fields["item_value"] = analysis.data[0];
220 }
221 } else if (address >= DMA0_CONTROL && address < DMA0_CONTROL + 0x80) {
222 // Parse DMA registers
223 uint32_t dma_channel = (address - DMA0_CONTROL) / 0x10;
224 analysis.fields["dma_channel"] = dma_channel;
225
226 uint32_t offset = (address - DMA0_CONTROL) % 0x10;
227 switch (offset) {
228 case 0x00:
229 analysis.fields["dma_control"] = analysis.data[0];
230 break;
231 case 0x01:
232 analysis.fields["dma_destination"] = analysis.data[0];
233 break;
234 case 0x02:
235 case 0x03:
236 case 0x04:
237 // Source address bytes
238 break;
239 case 0x05:
240 case 0x06:
241 // Transfer size
242 break;
243 }
244
245 // Check for DMA issues
246 if ((analysis.data[0] & 0x80) && address == DMA0_CONTROL) {
247 analysis.anomalies.push_back("DMA channel enabled during active display - may cause glitches");
248 }
249 }
250
251 // Check for common issues
252 if (analysis.data_type == "sprite" && length >= 16) {
253 // Check sprite corruption patterns
254 bool all_zero = std::all_of(analysis.data.begin(),
255 analysis.data.begin() + std::min(size_t(16), length),
256 [](uint8_t b) { return b == 0; });
257 bool all_ff = std::all_of(analysis.data.begin(),
258 analysis.data.begin() + std::min(size_t(16), length),
259 [](uint8_t b) { return b == 0xFF; });
260
261 if (all_zero) {
262 analysis.anomalies.push_back("Sprite data is all zeros - likely uninitialized");
263 }
264 if (all_ff) {
265 analysis.anomalies.push_back("Sprite data is all 0xFF - possible corruption");
266 }
267 }
268
269 return analysis;
270}
271
272absl::StatusOr<std::string> RomDebugAgent::ExplainExecutionTrace(
273 const std::vector<ExecutionTraceBuffer::TraceEntry>& trace) {
274 std::stringstream explanation;
275
276 explanation << "Execution Trace Analysis:\n";
277 explanation << "=========================\n\n";
278
279 // Track subroutine calls and returns
280 std::vector<std::string> call_stack;
281 int indent_level = 0;
282
283 for (size_t i = 0; i < trace.size(); ++i) {
284 const auto& entry = trace[i];
285
286 // Format the entry with indentation for call hierarchy
287 std::string indent(indent_level * 2, ' ');
288
289 // Check if this is a subroutine call
290 if (entry.opcode == 0x20 || entry.opcode == 0x22 || entry.opcode == 0xFC) {
291 // JSR, JSL, JSR (abs,X)
292 uint32_t target_addr = 0;
293 if (entry.operands.size() >= 2) {
294 target_addr = entry.operands[0] | (entry.operands[1] << 8);
295 if (entry.opcode == 0x22 && entry.operands.size() >= 3) {
296 target_addr |= (entry.operands[2] << 16);
297 }
298 }
299
300 std::string target_name = symbol_provider_->HasSymbols()
301 ? symbol_provider_->FormatAddress(target_addr)
302 : FormatSnesAddress(target_addr);
303
304 explanation << indent << "→ CALL " << target_name << " from "
305 << FormatSnesAddress(entry.address) << "\n";
306
307 call_stack.push_back(target_name);
308 indent_level++;
309
310 } else if (IsReturn(entry.opcode)) {
311 // Return instruction
312 if (!call_stack.empty()) {
313 indent_level = std::max(0, indent_level - 1);
314 indent = std::string(indent_level * 2, ' ');
315 explanation << indent << "← RETURN from " << call_stack.back() << "\n";
316 call_stack.pop_back();
317 }
318
319 } else if (IsBranchOrJump(entry.opcode)) {
320 // Branch or jump
321 std::string condition;
322 switch (entry.opcode) {
323 case 0x10: condition = " (if plus)"; break;
324 case 0x30: condition = " (if minus)"; break;
325 case 0x50: condition = " (if overflow clear)"; break;
326 case 0x70: condition = " (if overflow set)"; break;
327 case 0x90: condition = " (if carry clear)"; break;
328 case 0xB0: condition = " (if carry set)"; break;
329 case 0xD0: condition = " (if not zero)"; break;
330 case 0xF0: condition = " (if zero)"; break;
331 case 0x80:
332 case 0x82:
333 case 0x4C:
334 case 0x5C: condition = " (unconditional)"; break;
335 }
336
337 explanation << indent << " " << entry.mnemonic << " " << entry.operand_str
338 << condition << "\n";
339
340 } else {
341 // Regular instruction - only show significant ones
342 bool is_significant = false;
343 std::string description;
344
345 // Memory access instructions
346 if (entry.mnemonic.find("LD") != std::string::npos ||
347 entry.mnemonic.find("ST") != std::string::npos) {
348 is_significant = true;
349
350 // Try to identify what's being accessed
351 if (entry.operands.size() >= 2) {
352 uint32_t addr = entry.operands[0] | (entry.operands[1] << 8);
353 if (entry.operands.size() >= 3) {
354 addr |= (entry.operands[2] << 16);
355 }
356 description = " ; " + DescribeMemoryLocation(addr);
357 }
358 }
359
360 // Stack operations
361 if (ModifiesStack(entry.opcode)) {
362 is_significant = true;
363 description = " ; Stack: ";
364 if (entry.opcode == 0x48 || entry.opcode == 0x08) {
365 description += "pushing";
366 } else if (entry.opcode == 0x68 || entry.opcode == 0x28) {
367 description += "pulling";
368 }
369 }
370
371 // Arithmetic/logic that changes flags significantly
372 if (entry.mnemonic.find("CMP") != std::string::npos ||
373 entry.mnemonic.find("BIT") != std::string::npos ||
374 entry.mnemonic.find("TST") != std::string::npos) {
375 is_significant = true;
376 description = " ; Testing/comparing";
377 }
378
379 if (is_significant) {
380 explanation << indent << " " << FormatSnesAddress(entry.address)
381 << ": " << entry.mnemonic << " " << entry.operand_str
382 << description << "\n";
383 }
384 }
385
386 // Check for patterns
387 if (i > 0) {
388 // Detect infinite loops
389 if (entry.address == trace[i-1].address && IsBranchOrJump(entry.opcode)) {
390 explanation << indent << " ⚠️ POSSIBLE INFINITE LOOP DETECTED\n";
391 }
392
393 // Detect rapid DMA
394 if (entry.address >= DMA0_CONTROL && entry.address < DMA0_CONTROL + 0x80) {
395 if (i > 0 && trace[i-1].address >= DMA0_CONTROL &&
396 trace[i-1].address < DMA0_CONTROL + 0x80) {
397 explanation << indent << " ⚠️ RAPID DMA OPERATIONS - CHECK TIMING\n";
398 }
399 }
400 }
401 }
402
403 // Summary
404 explanation << "\nSummary:\n";
405 explanation << "--------\n";
406 explanation << "Total instructions: " << trace.size() << "\n";
407 explanation << "Subroutine depth: " << indent_level << "\n";
408 if (!call_stack.empty()) {
409 explanation << "Unmatched calls: " << absl::StrJoin(call_stack, ", ") << "\n";
410 }
411
412 return explanation.str();
413}
414
415absl::StatusOr<RomDebugAgent::PatchComparisonResult> RomDebugAgent::ComparePatch(
416 uint32_t address, size_t length, const std::vector<uint8_t>& original) {
418 result.address = address;
419 result.length = length;
420 result.original_code = original;
421 result.is_safe = true; // Assume safe until proven otherwise
422
423 // Read the patched code from emulator
424 grpc::ServerContext context;
425 MemoryRequest mem_req;
426 mem_req.set_address(address);
427 mem_req.set_size(length);
428 MemoryResponse mem_resp;
429
430 auto status = emulator_service_->ReadMemory(&context, &mem_req, &mem_resp);
431 if (!status.ok()) {
432 return absl::InternalError("Failed to read patched memory");
433 }
434
435 result.patched_code.assign(mem_resp.data().begin(), mem_resp.data().end());
436
437 // Disassemble both versions
438 std::stringstream orig_disasm, patch_disasm;
439 size_t orig_offset = 0, patch_offset = 0;
440
441 while (orig_offset < original.size() && patch_offset < result.patched_code.size()) {
442 // Disassemble original
443 std::string orig_mnem, orig_operand;
444 std::vector<uint8_t> orig_operands;
445 uint8_t orig_size = disassembler_->DisassembleInstruction(
446 address + orig_offset,
447 original.data() + orig_offset,
448 orig_mnem, orig_operand, orig_operands);
449
450 orig_disasm << FormatSnesAddress(address + orig_offset) << ": "
451 << orig_mnem << " " << orig_operand << "\n";
452
453 // Disassemble patched
454 std::string patch_mnem, patch_operand;
455 std::vector<uint8_t> patch_operands;
456 uint8_t patch_size = disassembler_->DisassembleInstruction(
457 address + patch_offset,
458 result.patched_code.data() + patch_offset,
459 patch_mnem, patch_operand, patch_operands);
460
461 patch_disasm << FormatSnesAddress(address + patch_offset) << ": "
462 << patch_mnem << " " << patch_operand << "\n";
463
464 // Compare instructions
465 if (orig_mnem != patch_mnem || orig_operand != patch_operand) {
466 result.differences.push_back(absl::StrFormat(
467 "At %s: '%s %s' → '%s %s'",
468 FormatSnesAddress(address + orig_offset),
469 orig_mnem, orig_operand,
470 patch_mnem, patch_operand));
471
472 // Check for potential issues
473
474 // Check if jump target is valid
475 if (IsBranchOrJump(result.patched_code[patch_offset])) {
476 uint32_t target = 0;
477 if (patch_operands.size() >= 2) {
478 target = patch_operands[0] | (patch_operands[1] << 8);
479 if (patch_operands.size() >= 3) {
480 target |= (patch_operands[2] << 16);
481 }
482 }
483
484 if (!IsValidJumpTarget(target)) {
485 result.potential_issues.push_back(absl::StrFormat(
486 "Invalid jump target at %s: %s",
487 FormatSnesAddress(address + patch_offset),
488 FormatSnesAddress(target)));
489 result.is_safe = false;
490 }
491 }
492
493 // Check for stack imbalance
494 bool orig_modifies_stack = ModifiesStack(original[orig_offset]);
495 bool patch_modifies_stack = ModifiesStack(result.patched_code[patch_offset]);
496
497 if (orig_modifies_stack != patch_modifies_stack) {
498 result.potential_issues.push_back(
499 "Stack modification mismatch - may cause stack imbalance");
500 result.is_safe = false;
501 }
502 }
503
504 orig_offset += orig_size;
505 patch_offset += patch_size;
506 }
507
508 result.original_disassembly = orig_disasm.str();
509 result.patched_disassembly = patch_disasm.str();
510
511 // Additional safety checks
512
513 // Check if patch overwrites critical areas
514 if (IsCriticalMemoryArea(address)) {
515 result.potential_issues.push_back(
516 "Patch modifies critical system area - verify this is intentional");
517 result.is_safe = false;
518 }
519
520 // Check for NOP slides (common in bad patches)
521 int nop_count = 0;
522 for (uint8_t byte : result.patched_code) {
523 if (byte == 0xEA) { // NOP
524 nop_count++;
525 }
526 }
527 if (nop_count > 5) {
528 result.potential_issues.push_back(absl::StrFormat(
529 "Patch contains %d NOPs - possible padding or removed code", nop_count));
530 }
531
532 // Check for BRK instructions (usually indicates problems)
533 for (size_t i = 0; i < result.patched_code.size(); ++i) {
534 if (result.patched_code[i] == 0x00) { // BRK
535 result.potential_issues.push_back(absl::StrFormat(
536 "BRK instruction at %s - usually indicates error",
537 FormatSnesAddress(address + i)));
538 result.is_safe = false;
539 }
540 }
541
542 return result;
543}
544
545std::vector<RomDebugAgent::DetectedIssue> RomDebugAgent::ScanForIssues(
546 uint32_t start_address, uint32_t end_address) {
547 std::vector<DetectedIssue> issues;
548
549 // Read the code region
550 grpc::ServerContext context;
551 MemoryRequest mem_req;
552 mem_req.set_address(start_address);
553 mem_req.set_size(end_address - start_address);
554 MemoryResponse mem_resp;
555
556 auto status = emulator_service_->ReadMemory(&context, &mem_req, &mem_resp);
557 if (!status.ok()) {
558 return issues;
559 }
560
561 const uint8_t* code = reinterpret_cast<const uint8_t*>(mem_resp.data().data());
562 size_t code_size = mem_resp.data().size();
563
564 size_t offset = 0;
565 while (offset < code_size) {
566 uint32_t current_addr = start_address + offset;
567
568 // Check for specific patterns
569 auto issue = DetectIssuePattern(current_addr, code + offset, code_size - offset);
570 if (issue.has_value()) {
571 issues.push_back(issue.value());
572 }
573
574 // Get instruction size to advance
575 uint8_t inst_size = disassembler_->GetInstructionSize(code[offset]);
576 if (inst_size == 0) {
577 // Invalid opcode
578 issues.push_back({
580 current_addr,
581 absl::StrFormat("Invalid opcode $%02X at %s",
582 code[offset], FormatSnesAddress(current_addr)),
583 "Check if this is data being executed as code",
584 5
585 });
586 offset++;
587 } else {
588 offset += inst_size;
589 }
590 }
591
592 return issues;
593}
594
595bool RomDebugAgent::IsValidJumpTarget(uint32_t address) const {
596 // Check if address is in valid ROM or RAM range
597 if (address < 0x008000) {
598 // Low RAM - generally okay for some routines
599 return true;
600 }
601 if (address >= 0x008000 && address < 0x7E0000) {
602 // ROM space - valid
603 return true;
604 }
605 if (address >= WRAM_START && address <= WRAM_END) {
606 // WRAM - valid but unusual for code
607 return true;
608 }
609 if (address >= 0x800000 && address < 0xC00000) {
610 // Extended ROM banks - valid
611 return true;
612 }
613
614 // Invalid ranges
615 return false;
616}
617
618bool RomDebugAgent::HasStackImbalance(uint32_t routine_start, uint32_t routine_end) {
619 // Read the routine
620 grpc::ServerContext context;
621 MemoryRequest mem_req;
622 mem_req.set_address(routine_start);
623 mem_req.set_size(routine_end - routine_start);
624 MemoryResponse mem_resp;
625
626 auto status = emulator_service_->ReadMemory(&context, &mem_req, &mem_resp);
627 if (!status.ok()) {
628 return false;
629 }
630
631 const uint8_t* code = reinterpret_cast<const uint8_t*>(mem_resp.data().data());
632 size_t code_size = mem_resp.data().size();
633
634 // Track stack depth
635 int stack_depth = 0;
636 size_t offset = 0;
637
638 while (offset < code_size) {
639 uint8_t opcode = code[offset];
640
641 // Track pushes and pulls
642 switch (opcode) {
643 case 0x48: // PHA
644 case 0x08: // PHP
645 case 0xDA: // PHX
646 case 0x5A: // PHY
647 case 0x8B: // PHB
648 case 0x0B: // PHD
649 case 0x4B: // PHK
650 stack_depth++;
651 break;
652
653 case 0x68: // PLA
654 case 0x28: // PLP
655 case 0xFA: // PLX
656 case 0x7A: // PLY
657 case 0xAB: // PLB
658 case 0x2B: // PLD
659 stack_depth--;
660 break;
661
662 case 0x62: // PER (push effective address)
663 case 0xD4: // PEI
664 case 0xF4: // PEA
665 stack_depth += 2; // These push 16-bit values
666 break;
667 }
668
669 // Check for return
670 if (IsReturn(opcode)) {
671 // At return, stack should be balanced
672 return stack_depth != 0;
673 }
674
675 uint8_t inst_size = disassembler_->GetInstructionSize(opcode);
676 offset += (inst_size > 0) ? inst_size : 1;
677 }
678
679 // If we didn't find a return, check final depth
680 return stack_depth != 0;
681}
682
683bool RomDebugAgent::IsMemoryWriteSafe(uint32_t address, size_t length) const {
684 // Check if write touches critical areas
685 uint32_t end_address = address + length;
686
687 // Check system vectors
688 if ((address <= 0x00FFFF && end_address > 0x00FFE0)) {
689 return false; // Writing to interrupt vectors
690 }
691
692 // Check NMI flag
693 if (address <= NMI_FLAG && end_address > NMI_FLAG) {
694 return false; // Modifying NMI flag can break frame timing
695 }
696
697 // Check PPU registers during active display
698 if (address >= 0x002100 && address <= 0x00213F) {
699 // Some PPU registers are unsafe during active display
700 // This would need frame timing info to be accurate
701 return true; // Assume safe for now
702 }
703
704 // Check DMA registers
705 if (address >= DMA0_CONTROL && address < DMA0_CONTROL + 0x80) {
706 // DMA writes during active display can cause issues
707 return true; // Assume safe for now
708 }
709
710 // Check critical WRAM areas
711 if (address >= 0x7E0000 && address < 0x7E2000) {
712 // Low WRAM has critical system variables
713 if (address < 0x7E0100) {
714 return false; // Direct page and stack area
715 }
716 }
717
718 return true;
719}
720
721std::string RomDebugAgent::DescribeMemoryLocation(uint32_t address) const {
722 // Check cache first
723 auto it = address_description_cache_.find(address);
724 if (it != address_description_cache_.end()) {
725 return it->second;
726 }
727
728 std::string description;
729
730 // System areas
731 if (address < 0x100) {
732 description = "Direct Page";
733 } else if (address >= 0x100 && address < 0x200) {
734 description = "Stack";
735 } else if (address >= GAME_MODE && address == GAME_MODE) {
736 description = "Game Mode";
737 } else if (address == SUBMODULE) {
738 description = "Submodule";
739 } else if (address == NMI_FLAG) {
740 description = "NMI Flag";
741 } else if (address == FRAME_COUNTER) {
742 description = "Frame Counter";
743 }
744 // Player/Link
745 else if (address == LINK_X_POS) {
746 description = "Link X Position";
747 } else if (address == LINK_Y_POS) {
748 description = "Link Y Position";
749 } else if (address == LINK_STATE) {
750 description = "Link State";
751 } else if (address == LINK_DIRECTION) {
752 description = "Link Facing Direction";
753 }
754 // Sprites
755 else if (address >= SPRITE_TABLE_START && address < SPRITE_TABLE_END) {
756 uint32_t offset = address - SPRITE_TABLE_START;
757 uint32_t sprite_num = offset / 0x10;
758 description = absl::StrFormat("Sprite %d Data", sprite_num);
759 }
760 // OAM
761 else if (address >= OAM_BUFFER && address <= OAM_BUFFER_END) {
762 description = "OAM Buffer (sprite attributes)";
763 }
764 // DMA
765 else if (address >= DMA0_CONTROL && address < DMA0_CONTROL + 0x80) {
766 uint32_t channel = (address - DMA0_CONTROL) / 0x10;
767 description = absl::StrFormat("DMA Channel %d", channel);
768 } else if (address == DMA_ENABLE) {
769 description = "DMA Enable Register";
770 } else if (address == HDMA_ENABLE) {
771 description = "HDMA Enable Register";
772 }
773 // PPU
774 else if (address == PPU_INIDISP) {
775 description = "Screen Display Register";
776 } else if (address == PPU_BGMODE) {
777 description = "BG Mode Register";
778 } else if (address == PPU_CGADD) {
779 description = "CGRAM Address";
780 } else if (address == PPU_CGDATA) {
781 description = "CGRAM Data";
782 }
783 // Audio
784 else if (address >= APU_PORT0 && address <= APU_PORT3) {
785 description = absl::StrFormat("APU Port %d", address - APU_PORT0);
786 }
787 // Save data
788 else if (address >= SRAM_START && address <= SRAM_END) {
789 if (address >= PLAYER_NAME && address < PLAYER_NAME + 6) {
790 description = "Player Name";
791 } else if (address == PLAYER_HEALTH) {
792 description = "Player Current Health";
793 } else if (address == PLAYER_MAX_HEALTH) {
794 description = "Player Max Health";
795 } else if (address >= INVENTORY_START && address < INVENTORY_START + 0x40) {
796 description = absl::StrFormat("Inventory Slot %d", address - INVENTORY_START);
797 } else {
798 description = "Save Data";
799 }
800 }
801 // ROM banks
802 else if (address >= 0x008000 && address < 0x00FFFF) {
803 description = absl::StrFormat("ROM Bank $%02X", (address >> 16) & 0xFF);
804 } else if (address >= WRAM_START && address <= WRAM_END) {
805 description = "WRAM";
806 } else if (address >= 0x800000 && address < 0xC00000) {
807 description = absl::StrFormat("Extended ROM Bank $%02X", (address >> 16) & 0xFF);
808 } else {
809 description = "Unknown";
810 }
811
812 // Cache the result
814 return description;
815}
816
817std::string RomDebugAgent::IdentifyDataType(uint32_t address) const {
818 auto it = data_type_cache_.find(address);
819 if (it != data_type_cache_.end()) {
820 return it->second;
821 }
822
823 std::string type;
824
825 if (address >= SPRITE_TABLE_START && address < SPRITE_TABLE_END) {
826 type = "sprite";
827 } else if (address >= OAM_BUFFER && address <= OAM_BUFFER_END) {
828 type = "oam";
829 } else if (address >= DMA0_CONTROL && address < DMA0_CONTROL + 0x80) {
830 type = "dma";
831 } else if (address >= 0x002100 && address <= 0x00213F) {
832 type = "ppu";
833 } else if (address >= APU_PORT0 && address <= APU_PORT3) {
834 type = "audio";
835 } else if (address >= SRAM_START && address <= SRAM_END) {
836 type = "save";
837 } else if (address >= INVENTORY_START && address < INVENTORY_START + 0x40) {
838 type = "inventory";
839 } else if (address >= 0x008000 && address < 0x7E0000) {
840 type = "code";
841 } else if (address >= WRAM_START && address <= WRAM_END) {
842 type = "ram";
843 } else {
844 type = "unknown";
845 }
846
847 data_type_cache_[address] = type;
848 return type;
849}
850
852 const std::map<std::string, uint16_t>& regs) const {
853 std::stringstream ss;
854 ss << "Registers: ";
855 ss << absl::StrFormat("A=%04X ", regs.at("A"));
856 ss << absl::StrFormat("X=%04X ", regs.at("X"));
857 ss << absl::StrFormat("Y=%04X ", regs.at("Y"));
858 ss << absl::StrFormat("S=%04X ", regs.at("S"));
859 ss << absl::StrFormat("PC=%04X ", regs.at("PC"));
860 ss << absl::StrFormat("P=%02X ", regs.at("P"));
861 ss << absl::StrFormat("DB=%02X ", regs.at("DB"));
862 ss << absl::StrFormat("PB=%02X", regs.at("PB"));
863 return ss.str();
864}
865
866absl::Status RomDebugAgent::LoadSymbols(const std::string& symbol_file) {
867 return symbol_provider_->LoadSymbolFile(symbol_file);
868}
869
870void RomDebugAgent::SetOriginalRom(const std::vector<uint8_t>& rom_data) {
871 original_rom_ = rom_data;
872}
873
874// Private helper methods
875
876absl::StatusOr<std::string> RomDebugAgent::AnalyzeInstruction(
877 uint32_t address, const uint8_t* code, size_t max_length) {
878 if (max_length == 0) {
879 return absl::InvalidArgumentError("No code provided");
880 }
881
882 uint8_t opcode = code[0];
883 std::string explanation;
884
885 // Analyze based on opcode
886 switch (opcode) {
887 // Load instructions
888 case 0xA9: explanation = "Load immediate value into accumulator"; break;
889 case 0xA2: explanation = "Load immediate value into X register"; break;
890 case 0xA0: explanation = "Load immediate value into Y register"; break;
891 case 0xAD: explanation = "Load accumulator from absolute address"; break;
892 case 0xAE: explanation = "Load X register from absolute address"; break;
893 case 0xAC: explanation = "Load Y register from absolute address"; break;
894
895 // Store instructions
896 case 0x8D: explanation = "Store accumulator to absolute address"; break;
897 case 0x8E: explanation = "Store X register to absolute address"; break;
898 case 0x8C: explanation = "Store Y register to absolute address"; break;
899 case 0x9D: explanation = "Store accumulator to address indexed by X"; break;
900 case 0x99: explanation = "Store accumulator to address indexed by Y"; break;
901
902 // Branches
903 case 0x10: explanation = "Branch if plus (N flag clear)"; break;
904 case 0x30: explanation = "Branch if minus (N flag set)"; break;
905 case 0x50: explanation = "Branch if overflow clear"; break;
906 case 0x70: explanation = "Branch if overflow set"; break;
907 case 0x80: explanation = "Branch always"; break;
908 case 0x90: explanation = "Branch if carry clear (less than)"; break;
909 case 0xB0: explanation = "Branch if carry set (greater than or equal)"; break;
910 case 0xD0: explanation = "Branch if not equal (Z flag clear)"; break;
911 case 0xF0: explanation = "Branch if equal (Z flag set)"; break;
912
913 // Jumps and calls
914 case 0x20: explanation = "Call subroutine"; break;
915 case 0x22: explanation = "Call long subroutine (24-bit)"; break;
916 case 0x4C: explanation = "Jump to address"; break;
917 case 0x5C: explanation = "Jump long (24-bit)"; break;
918 case 0x60: explanation = "Return from subroutine"; break;
919 case 0x6B: explanation = "Return from long subroutine"; break;
920
921 // Stack operations
922 case 0x48: explanation = "Push accumulator onto stack"; break;
923 case 0x68: explanation = "Pull accumulator from stack"; break;
924 case 0x08: explanation = "Push processor status onto stack"; break;
925 case 0x28: explanation = "Pull processor status from stack"; break;
926
927 // Arithmetic
928 case 0x69: explanation = "Add to accumulator with carry"; break;
929 case 0xE9: explanation = "Subtract from accumulator with borrow"; break;
930 case 0xC9: explanation = "Compare accumulator with value"; break;
931 case 0xE0: explanation = "Compare X register with value"; break;
932 case 0xC0: explanation = "Compare Y register with value"; break;
933
934 // Logical
935 case 0x29: explanation = "AND accumulator with value"; break;
936 case 0x09: explanation = "OR accumulator with value"; break;
937 case 0x49: explanation = "XOR accumulator with value"; break;
938
939 // Special
940 case 0x00: explanation = "Software interrupt (BRK)"; break;
941 case 0xEA: explanation = "No operation (NOP)"; break;
942 case 0x18: explanation = "Clear carry flag"; break;
943 case 0x38: explanation = "Set carry flag"; break;
944 case 0xC2: explanation = "Clear processor flags (REP)"; break;
945 case 0xE2: explanation = "Set processor flags (SEP)"; break;
946
947 default:
948 explanation = absl::StrFormat("Execute opcode $%02X", opcode);
949 }
950
951 return explanation;
952}
953
955 uint32_t address, int before_lines, int after_lines) {
956 std::vector<std::string> context_lines;
957
958 // Get disassembly from emulator service
959 grpc::ServerContext ctx;
960 DisassemblyRequest req;
961 req.set_start_address(address - (before_lines * 3)); // Estimate 3 bytes per instruction
962 req.set_count(before_lines + after_lines + 1);
963 DisassemblyResponse resp;
964
965 auto status = emulator_service_->GetDisassembly(&ctx, &req, &resp);
966 if (!status.ok()) {
967 return context_lines;
968 }
969
970 for (const auto& inst : resp.lines()) {
971 std::string line = absl::StrFormat("%s: %s %s",
972 FormatSnesAddress(inst.address()),
973 inst.mnemonic(),
974 inst.operand_str());
975 if (inst.address() == address) {
976 line = ">>> " + line + " <<<"; // Highlight current instruction
977 }
978 context_lines.push_back(line);
979 }
980
981 return context_lines;
982}
983
984std::vector<std::string> RomDebugAgent::BuildCallStack(uint32_t current_pc) {
985 std::vector<std::string> stack;
986
987 // Get execution trace to build call stack
988 grpc::ServerContext ctx;
989 yaze::agent::TraceRequest req;
990 req.set_max_entries(100); // Get last 100 instructions
991 yaze::agent::TraceResponse resp;
992
993 auto status = emulator_service_->GetExecutionTrace(&ctx, &req, &resp);
994 if (!status.ok()) {
995 return stack;
996 }
997
998 // Walk backwards through trace to find calls
999 for (int i = resp.entries_size() - 1; i >= 0; --i) {
1000 const auto& entry = resp.entries(i);
1001 uint8_t opcode = entry.opcode();
1002
1003 if (opcode == 0x20 || opcode == 0x22 || opcode == 0xFC) {
1004 // Found a call
1005 std::string caller = symbol_provider_->HasSymbols()
1006 ? symbol_provider_->FormatAddress(entry.address())
1007 : FormatSnesAddress(entry.address());
1008 stack.push_back(caller);
1009 } else if (IsReturn(opcode) && !stack.empty()) {
1010 // Found a return, pop from our reconstructed stack
1011 stack.pop_back();
1012 }
1013 }
1014
1015 // Reverse to get top-down order
1016 std::reverse(stack.begin(), stack.end());
1017
1018 return stack;
1019}
1020
1021std::optional<RomDebugAgent::DetectedIssue> RomDebugAgent::DetectIssuePattern(
1022 uint32_t address, const uint8_t* code, size_t length) {
1023 if (length < 1) {
1024 return std::nullopt;
1025 }
1026
1027 uint8_t opcode = code[0];
1028
1029 // Check for BRK (usually an error)
1030 if (opcode == 0x00) {
1031 return DetectedIssue{
1033 address,
1034 "BRK instruction found - usually indicates an error or unimplemented code",
1035 "Replace with proper implementation or NOP if intentional padding",
1036 4
1037 };
1038 }
1039
1040 // Check for infinite loop (branch to self)
1041 if (opcode == 0x80 && length >= 2 && code[1] == 0xFE) {
1042 // BRA $-2 (branch to self)
1043 return DetectedIssue{
1045 address,
1046 "Infinite loop detected (BRA to self)",
1047 "Add proper exit condition or loop counter",
1048 5
1049 };
1050 }
1051
1052 // Check for writes to vector table
1053 if (opcode == 0x8D && length >= 3) { // STA abs
1054 uint16_t dest = code[1] | (code[2] << 8);
1055 if (dest >= 0xFFE0) {
1056 return DetectedIssue{
1058 address,
1059 absl::StrFormat("Writing to interrupt vector at $%04X", dest),
1060 "Verify this vector modification is intentional",
1061 5
1062 };
1063 }
1064 }
1065
1066 // Check for stack operations without matching pairs
1067 if (ModifiesStack(opcode)) {
1068 // This would need more context to properly detect
1069 // For now, just flag excessive consecutive pushes
1070 int consecutive_pushes = 0;
1071 for (size_t i = 0; i < std::min(size_t(10), length); ++i) {
1072 if (code[i] == 0x48 || code[i] == 0x08 || code[i] == 0xDA || code[i] == 0x5A) {
1073 consecutive_pushes++;
1074 } else if (code[i] == 0x68 || code[i] == 0x28 || code[i] == 0xFA || code[i] == 0x7A) {
1075 consecutive_pushes--;
1076 }
1077 }
1078
1079 if (consecutive_pushes > 5) {
1080 return DetectedIssue{
1082 address,
1083 "Multiple consecutive pushes without pulls - possible stack overflow",
1084 "Verify stack operations are properly balanced",
1085 3
1086 };
1087 }
1088 }
1089
1090 return std::nullopt;
1091}
1092
1093bool RomDebugAgent::IsCriticalMemoryArea(uint32_t address) const {
1094 // Direct page and stack
1095 if (address < 0x200) {
1096 return true;
1097 }
1098
1099 // Interrupt vectors
1100 if (address >= 0xFFE0 && address <= 0xFFFF) {
1101 return true;
1102 }
1103
1104 // NMI handler area
1105 if (address == NMI_FLAG) {
1106 return true;
1107 }
1108
1109 // DMA during critical timing
1110 if (address >= DMA0_CONTROL && address < DMA0_CONTROL + 0x80) {
1111 return true;
1112 }
1113
1114 return false;
1115}
1116
1117std::optional<std::string> RomDebugAgent::GetStructureInfo(uint32_t address) const {
1118 // Sprite structure
1119 if (address >= SPRITE_TABLE_START && address < SPRITE_TABLE_END) {
1120 uint32_t offset = (address - SPRITE_TABLE_START) % 0x10;
1121 switch (offset) {
1122 case 0x00: return "Sprite State";
1123 case 0x01: return "Sprite X Position Low";
1124 case 0x02: return "Sprite X Position High";
1125 case 0x03: return "Sprite Y Position Low";
1126 case 0x04: return "Sprite Y Position High";
1127 case 0x05: return "Sprite Z Position";
1128 case 0x06: return "Sprite Velocity X";
1129 case 0x07: return "Sprite Velocity Y";
1130 case 0x08: return "Sprite Type";
1131 case 0x09: return "Sprite Subtype";
1132 case 0x0A: return "Sprite Graphics";
1133 case 0x0B: return "Sprite Properties";
1134 case 0x0C: return "Sprite Health";
1135 case 0x0D: return "Sprite Damage";
1136 case 0x0E: return "Sprite Timer";
1137 case 0x0F: return "Sprite Flags";
1138 }
1139 }
1140
1141 // OAM structure
1142 if (address >= OAM_BUFFER && address <= OAM_BUFFER_END) {
1143 uint32_t offset = (address - OAM_BUFFER) % 4;
1144 switch (offset) {
1145 case 0: return "OAM X Position";
1146 case 1: return "OAM Y Position";
1147 case 2: return "OAM Tile Number";
1148 case 3: return "OAM Attributes";
1149 }
1150 }
1151
1152 // DMA channel structure
1153 if (address >= DMA0_CONTROL && address < DMA0_CONTROL + 0x80) {
1154 uint32_t offset = (address - DMA0_CONTROL) % 0x10;
1155 switch (offset) {
1156 case 0x00: return "DMA Control";
1157 case 0x01: return "DMA Destination";
1158 case 0x02: return "DMA Source Low";
1159 case 0x03: return "DMA Source High";
1160 case 0x04: return "DMA Source Bank";
1161 case 0x05: return "DMA Size Low";
1162 case 0x06: return "DMA Size High";
1163 case 0x07: return "DMA Indirect Bank";
1164 }
1165 }
1166
1167 return std::nullopt;
1168}
1169
1170} // namespace agent
1171} // namespace cli
1172} // namespace yaze
grpc::Status GetExecutionTrace(grpc::ServerContext *context, const TraceRequest *request, TraceResponse *response) override
grpc::Status GetDisassembly(grpc::ServerContext *context, const DisassemblyRequest *request, DisassemblyResponse *response) override
grpc::Status ReadMemory(grpc::ServerContext *context, const MemoryRequest *request, MemoryResponse *response) override
static constexpr uint32_t FRAME_COUNTER
void SetOriginalRom(const std::vector< uint8_t > &rom_data)
Set the original ROM data for comparison.
static constexpr uint32_t OAM_BUFFER
static constexpr uint32_t PLAYER_HEALTH
static constexpr uint32_t LINK_Y_POS
static constexpr uint32_t OAM_BUFFER_END
static constexpr uint32_t DMA_ENABLE
absl::StatusOr< std::string > ExplainExecutionTrace(const std::vector< ExecutionTraceBuffer::TraceEntry > &trace)
Analyze execution trace and explain program flow.
static constexpr uint32_t WRAM_END
absl::StatusOr< BreakpointAnalysis > AnalyzeBreakpoint(const yaze::agent::BreakpointHitResponse &hit)
Analyze a breakpoint hit with full context.
static constexpr uint32_t APU_PORT0
bool IsMemoryWriteSafe(uint32_t address, size_t length) const
Check if memory write is safe.
static constexpr uint32_t PPU_BGMODE
static constexpr uint32_t LINK_X_POS
std::string FormatRegisterState(const std::map< std::string, uint16_t > &regs) const
Format register state for debugging output.
static constexpr uint32_t SPRITE_TABLE_START
bool IsValidJumpTarget(uint32_t address) const
Check if an address is a valid jump target.
static constexpr uint32_t PPU_CGADD
static constexpr uint32_t INVENTORY_START
static constexpr uint32_t GAME_MODE
RomDebugAgent(yaze::agent::EmulatorServiceImpl *emulator_service)
yaze::agent::EmulatorServiceImpl * emulator_service_
static constexpr uint32_t SRAM_END
std::optional< std::string > GetStructureInfo(uint32_t address) const
Get structure information for a memory address.
std::map< uint32_t, std::string > address_description_cache_
std::vector< DetectedIssue > ScanForIssues(uint32_t start_address, uint32_t end_address)
Scan for common ROM hacking issues in a code region.
static constexpr uint32_t SRAM_START
std::vector< std::string > GetDisassemblyContext(uint32_t address, int before_lines, int after_lines)
Get surrounding context for an address.
std::vector< std::string > BuildCallStack(uint32_t current_pc)
Build call stack from execution trace.
bool HasStackImbalance(uint32_t routine_start, uint32_t routine_end)
Detect stack imbalance in a subroutine.
std::vector< uint8_t > original_rom_
std::map< uint32_t, std::string > data_type_cache_
static constexpr uint32_t APU_PORT3
absl::Status LoadSymbols(const std::string &symbol_file)
Load symbol table for better disassembly.
absl::StatusOr< PatchComparisonResult > ComparePatch(uint32_t address, size_t length, const std::vector< uint8_t > &original)
Compare original ROM code with patched code.
std::unique_ptr< yaze::emu::debug::SymbolProvider > symbol_provider_
std::optional< DetectedIssue > DetectIssuePattern(uint32_t address, const uint8_t *code, size_t length)
Detect pattern of common issues.
static constexpr uint32_t SPRITE_TABLE_END
std::string DescribeMemoryLocation(uint32_t address) const
Get human-readable description of a memory address.
static constexpr uint32_t SUBMODULE
absl::StatusOr< std::string > AnalyzeInstruction(uint32_t address, const uint8_t *code, size_t max_length)
Analyze the instruction at an address.
static constexpr uint32_t PPU_INIDISP
bool IsCriticalMemoryArea(uint32_t address) const
Check if address is in a critical system area.
static constexpr uint32_t NMI_FLAG
absl::StatusOr< MemoryAnalysis > AnalyzeMemory(uint32_t address, size_t length)
Analyze a memory region and identify its purpose.
static constexpr uint32_t LINK_STATE
static constexpr uint32_t PLAYER_NAME
static constexpr uint32_t HDMA_ENABLE
static constexpr uint32_t WRAM_START
static constexpr uint32_t PPU_CGDATA
static constexpr uint32_t PLAYER_MAX_HEALTH
static constexpr uint32_t DMA0_CONTROL
std::unique_ptr< Disassembler65816 > disassembler_
static constexpr uint32_t LINK_DIRECTION
std::string IdentifyDataType(uint32_t address) const
Get the data type at a memory address.
Analysis result for a breakpoint hit.
std::map< std::string, uint32_t > fields