yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
disassembly_viewer.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <fstream>
5#include <iomanip>
6#include <sstream>
7
8#include "absl/strings/str_format.h"
9#include "app/gui/style.h"
10#include "imgui/imgui.h"
11
12namespace yaze {
13namespace emu {
14namespace debug {
15
16namespace {
17
18// Color scheme for retro hacker aesthetic
19constexpr ImVec4 kColorAddress(0.4f, 0.8f, 1.0f, 1.0f); // Cyan for addresses
20constexpr ImVec4 kColorOpcode(0.8f, 0.8f, 0.8f, 1.0f); // Light gray for opcodes
21constexpr ImVec4 kColorMnemonic(1.0f, 0.8f, 0.2f, 1.0f); // Gold for mnemonics
22constexpr ImVec4 kColorOperand(0.6f, 1.0f, 0.6f, 1.0f); // Light green for operands
23constexpr ImVec4 kColorComment(0.5f, 0.5f, 0.5f, 1.0f); // Gray for comments
24constexpr ImVec4 kColorCurrentPC(1.0f, 0.3f, 0.3f, 1.0f); // Red for current PC
25constexpr ImVec4 kColorBreakpoint(1.0f, 0.0f, 0.0f, 1.0f); // Bright red for breakpoints
26constexpr ImVec4 kColorHotPath(1.0f, 0.6f, 0.0f, 1.0f); // Orange for hot paths
27
28} // namespace
29
30void DisassemblyViewer::RecordInstruction(uint32_t address, uint8_t opcode,
31 const std::vector<uint8_t>& operands,
32 const std::string& mnemonic,
33 const std::string& operand_str) {
34 // Skip if recording disabled (for performance)
35 if (!recording_enabled_) {
36 return;
37 }
38
39 auto it = instructions_.find(address);
40 if (it != instructions_.end()) {
41 // Instruction already recorded, just increment execution count
42 it->second.execution_count++;
43 } else {
44 // Check if we're at the limit
45 if (instructions_.size() >= max_instructions_) {
46 // Trim to 80% of max to avoid constant trimming
48 }
49
50 // New instruction, add to map
51 DisassemblyEntry entry;
52 entry.address = address;
53 entry.opcode = opcode;
54 entry.operands = operands;
55 entry.mnemonic = mnemonic;
56 entry.operand_str = operand_str;
57 entry.size = 1 + operands.size();
58 entry.execution_count = 1;
59 entry.is_breakpoint = false;
60 entry.is_current_pc = false;
61
62 instructions_[address] = entry;
63 }
64}
65
66void DisassemblyViewer::TrimToSize(size_t target_size) {
67 if (instructions_.size() <= target_size) {
68 return;
69 }
70
71 // Keep most-executed instructions
72 // Remove least-executed ones
73 std::vector<std::pair<uint32_t, uint64_t>> addr_counts;
74 for (const auto& [addr, entry] : instructions_) {
75 addr_counts.push_back({addr, entry.execution_count});
76 }
77
78 // Sort by execution count (ascending)
79 std::sort(addr_counts.begin(), addr_counts.end(),
80 [](const auto& a, const auto& b) { return a.second < b.second; });
81
82 // Remove least-executed instructions
83 size_t to_remove = instructions_.size() - target_size;
84 for (size_t i = 0; i < to_remove && i < addr_counts.size(); i++) {
85 instructions_.erase(addr_counts[i].first);
86 }
87}
88
89void DisassemblyViewer::Render(uint32_t current_pc,
90 const std::vector<uint32_t>& breakpoints) {
91 // Update current PC and breakpoint flags
92 for (auto& [addr, entry] : instructions_) {
93 entry.is_current_pc = (addr == current_pc);
94 entry.is_breakpoint = std::find(breakpoints.begin(), breakpoints.end(), addr)
95 != breakpoints.end();
96 }
97
100 RenderDisassemblyTable(current_pc, breakpoints);
101}
102
104 if (ImGui::BeginTable("##DisasmToolbar", 6, ImGuiTableFlags_None)) {
105 ImGui::TableNextColumn();
106 if (ImGui::Button(ICON_MD_CLEAR_ALL " Clear")) {
107 Clear();
108 }
109 if (ImGui::IsItemHovered()) {
110 ImGui::SetTooltip("Clear all recorded instructions");
111 }
112
113 ImGui::TableNextColumn();
114 if (ImGui::Button(ICON_MD_SAVE " Export")) {
115 // TODO: Open file dialog and export
116 ExportToFile("disassembly.asm");
117 }
118 if (ImGui::IsItemHovered()) {
119 ImGui::SetTooltip("Export disassembly to file");
120 }
121
122 ImGui::TableNextColumn();
123 if (ImGui::Checkbox("Auto-scroll", &auto_scroll_)) {
124 // Toggle auto-scroll
125 }
126 if (ImGui::IsItemHovered()) {
127 ImGui::SetTooltip("Auto-scroll to current PC");
128 }
129
130 ImGui::TableNextColumn();
131 if (ImGui::Checkbox("Exec Count", &show_execution_counts_)) {
132 // Toggle execution count display
133 }
134 if (ImGui::IsItemHovered()) {
135 ImGui::SetTooltip("Show execution counts");
136 }
137
138 ImGui::TableNextColumn();
139 if (ImGui::Checkbox("Hex Dump", &show_hex_dump_)) {
140 // Toggle hex dump display
141 }
142 if (ImGui::IsItemHovered()) {
143 ImGui::SetTooltip("Show hex dump of instruction bytes");
144 }
145
146 ImGui::TableNextColumn();
147 ImGui::Text(ICON_MD_MEMORY " %zu instructions", instructions_.size());
148
149 ImGui::EndTable();
150 }
151
152 ImGui::Separator();
153}
154
156 ImGui::PushItemWidth(-1.0f);
157 if (ImGui::InputTextWithHint("##DisasmSearch", ICON_MD_SEARCH " Search (address, mnemonic, operand)...",
158 search_filter_, IM_ARRAYSIZE(search_filter_))) {
159 // Search filter updated
160 }
161 ImGui::PopItemWidth();
162}
163
165 const std::vector<uint32_t>& breakpoints) {
166 // Table flags for professional disassembly view
167 ImGuiTableFlags flags =
168 ImGuiTableFlags_Borders |
169 ImGuiTableFlags_RowBg |
170 ImGuiTableFlags_ScrollY |
171 ImGuiTableFlags_Resizable |
172 ImGuiTableFlags_Sortable |
173 ImGuiTableFlags_Reorderable |
174 ImGuiTableFlags_Hideable;
175
176 // Calculate column count based on optional columns
177 int column_count = 4; // BP, Address, Mnemonic, Operand (always shown)
178 if (show_hex_dump_) column_count++;
179 if (show_execution_counts_) column_count++;
180
181 if (!ImGui::BeginTable("##DisasmTable", column_count, flags, ImVec2(0.0f, 0.0f))) {
182 return;
183 }
184
185 // Setup columns
186 ImGui::TableSetupColumn(ICON_MD_CIRCLE, ImGuiTableColumnFlags_WidthFixed, 25.0f); // Breakpoint indicator
187 ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 80.0f);
188 if (show_hex_dump_) {
189 ImGui::TableSetupColumn("Hex", ImGuiTableColumnFlags_WidthFixed, 100.0f);
190 }
191 ImGui::TableSetupColumn("Mnemonic", ImGuiTableColumnFlags_WidthFixed, 80.0f);
192 ImGui::TableSetupColumn("Operand", ImGuiTableColumnFlags_WidthStretch);
194 ImGui::TableSetupColumn(ICON_MD_TRENDING_UP " Count", ImGuiTableColumnFlags_WidthFixed, 80.0f);
195 }
196
197 ImGui::TableSetupScrollFreeze(0, 1); // Freeze header row
198 ImGui::TableHeadersRow();
199
200 // Render instructions
201 ImGuiListClipper clipper;
202 auto sorted_addrs = GetSortedAddresses();
203 clipper.Begin(sorted_addrs.size());
204
205 while (clipper.Step()) {
206 for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) {
207 uint32_t addr = sorted_addrs[row];
208 const auto& entry = instructions_[addr];
209
210 // Skip if doesn't pass filter
211 if (!PassesFilter(entry)) {
212 continue;
213 }
214
215 ImGui::TableNextRow();
216
217 // Highlight current PC row
218 if (entry.is_current_pc) {
219 ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,
220 ImGui::GetColorU32(ImVec4(0.3f, 0.0f, 0.0f, 0.5f)));
221 }
222
223 // Column 0: Breakpoint indicator
224 ImGui::TableNextColumn();
225 if (entry.is_breakpoint) {
226 ImGui::TextColored(kColorBreakpoint, ICON_MD_STOP);
227 } else {
228 ImGui::TextDisabled(" ");
229 }
230
231 // Column 1: Address (clickable)
232 ImGui::TableNextColumn();
233 ImVec4 addr_color = GetAddressColor(entry, current_pc);
234
235 std::string addr_str = absl::StrFormat("$%02X:%04X",
236 (addr >> 16) & 0xFF, addr & 0xFFFF);
237 if (ImGui::Selectable(addr_str.c_str(), selected_address_ == addr,
238 ImGuiSelectableFlags_SpanAllColumns)) {
239 selected_address_ = addr;
240 }
241
242 // Context menu on right-click
243 if (ImGui::BeginPopupContextItem()) {
244 RenderContextMenu(addr);
245 ImGui::EndPopup();
246 }
247
248 // Column 2: Hex dump (optional)
249 if (show_hex_dump_) {
250 ImGui::TableNextColumn();
251 ImGui::TextColored(kColorOpcode, "%s", FormatHexDump(entry).c_str());
252 }
253
254 // Column 3: Mnemonic (clickable for documentation)
255 ImGui::TableNextColumn();
256 ImVec4 mnemonic_color = GetMnemonicColor(entry);
257 if (ImGui::Selectable(entry.mnemonic.c_str(), false)) {
258 // TODO: Open documentation for this mnemonic
259 }
260 if (ImGui::IsItemHovered()) {
261 ImGui::SetTooltip("Click for instruction documentation");
262 }
263
264 // Column 4: Operand (clickable for jump-to-address)
265 ImGui::TableNextColumn();
266 ImGui::TextColored(kColorOperand, "%s", entry.operand_str.c_str());
267
268 // Column 5: Execution count (optional)
270 ImGui::TableNextColumn();
271
272 // Color-code by execution frequency (hot path highlighting)
273 ImVec4 count_color = kColorComment;
274 if (entry.execution_count > 10000) {
275 count_color = kColorHotPath;
276 } else if (entry.execution_count > 1000) {
277 count_color = ImVec4(0.8f, 0.8f, 0.3f, 1.0f); // Yellow
278 }
279
280 ImGui::TextColored(count_color, "%llu", entry.execution_count);
281 }
282 }
283 }
284
285 // Auto-scroll to current PC
286 if (auto_scroll_ && scroll_to_address_ != current_pc) {
287 // Find row index of current PC
288 auto it = std::find(sorted_addrs.begin(), sorted_addrs.end(), current_pc);
289 if (it != sorted_addrs.end()) {
290 int row_index = std::distance(sorted_addrs.begin(), it);
291 ImGui::SetScrollY((row_index * ImGui::GetTextLineHeightWithSpacing()) -
292 (ImGui::GetWindowHeight() * 0.5f));
293 scroll_to_address_ = current_pc;
294 }
295 }
296
297 ImGui::EndTable();
298}
299
301 auto& entry = instructions_[address];
302
303 if (ImGui::MenuItem(ICON_MD_FLAG " Toggle Breakpoint")) {
304 // TODO: Implement breakpoint toggle callback
305 }
306
307 if (ImGui::MenuItem(ICON_MD_MY_LOCATION " Jump to Address")) {
308 JumpToAddress(address);
309 }
310
311 ImGui::Separator();
312
313 if (ImGui::MenuItem(ICON_MD_CONTENT_COPY " Copy Address")) {
314 ImGui::SetClipboardText(absl::StrFormat("$%06X", address).c_str());
315 }
316
317 if (ImGui::MenuItem(ICON_MD_CONTENT_COPY " Copy Instruction")) {
318 std::string instr = absl::StrFormat("%s %s", entry.mnemonic.c_str(),
319 entry.operand_str.c_str());
320 ImGui::SetClipboardText(instr.c_str());
321 }
322
323 ImGui::Separator();
324
325 if (ImGui::MenuItem(ICON_MD_INFO " Show Info")) {
326 // TODO: Show detailed instruction info
327 }
328}
329
331 uint32_t current_pc) const {
332 if (entry.is_current_pc) {
333 return kColorCurrentPC;
334 }
335 if (entry.is_breakpoint) {
336 return kColorBreakpoint;
337 }
338 return kColorAddress;
339}
340
342 // Color-code by instruction type
343 const std::string& mnemonic = entry.mnemonic;
344
345 // Branches and jumps
346 if (mnemonic.find('B') == 0 || mnemonic == "JMP" || mnemonic == "JSR" ||
347 mnemonic == "RTL" || mnemonic == "RTS" || mnemonic == "RTI") {
348 return ImVec4(0.8f, 0.4f, 1.0f, 1.0f); // Purple for control flow
349 }
350
351 // Loads
352 if (mnemonic.find("LD") == 0) {
353 return ImVec4(0.4f, 1.0f, 0.4f, 1.0f); // Green for loads
354 }
355
356 // Stores
357 if (mnemonic.find("ST") == 0) {
358 return ImVec4(1.0f, 0.6f, 0.4f, 1.0f); // Orange for stores
359 }
360
361 return kColorMnemonic;
362}
363
364std::string DisassemblyViewer::FormatHexDump(const DisassemblyEntry& entry) const {
365 std::stringstream ss;
366 ss << std::hex << std::uppercase << std::setfill('0');
367
368 // Opcode
369 ss << std::setw(2) << static_cast<int>(entry.opcode);
370
371 // Operands
372 for (const auto& operand_byte : entry.operands) {
373 ss << " " << std::setw(2) << static_cast<int>(operand_byte);
374 }
375
376 // Pad to consistent width (3 bytes max)
377 for (size_t i = entry.operands.size(); i < 2; i++) {
378 ss << " ";
379 }
380
381 return ss.str();
382}
383
385 if (search_filter_[0] == '\0') {
386 return true; // No filter active
387 }
388
389 std::string filter_lower(search_filter_);
390 std::transform(filter_lower.begin(), filter_lower.end(),
391 filter_lower.begin(), ::tolower);
392
393 // Check address
394 std::string addr_str = absl::StrFormat("%06x", entry.address);
395 if (addr_str.find(filter_lower) != std::string::npos) {
396 return true;
397 }
398
399 // Check mnemonic
400 std::string mnemonic_lower = entry.mnemonic;
401 std::transform(mnemonic_lower.begin(), mnemonic_lower.end(),
402 mnemonic_lower.begin(), ::tolower);
403 if (mnemonic_lower.find(filter_lower) != std::string::npos) {
404 return true;
405 }
406
407 // Check operand
408 std::string operand_lower = entry.operand_str;
409 std::transform(operand_lower.begin(), operand_lower.end(),
410 operand_lower.begin(), ::tolower);
411 if (operand_lower.find(filter_lower) != std::string::npos) {
412 return true;
413 }
414
415 return false;
416}
417
423
424bool DisassemblyViewer::ExportToFile(const std::string& filepath) const {
425 std::ofstream out(filepath);
426 if (!out.is_open()) {
427 return false;
428 }
429
430 out << "; YAZE Disassembly Export\n";
431 out << "; Total instructions: " << instructions_.size() << "\n";
432 out << "; Generated: " << __DATE__ << " " << __TIME__ << "\n\n";
433
434 auto sorted_addrs = GetSortedAddresses();
435 for (uint32_t addr : sorted_addrs) {
436 const auto& entry = instructions_.at(addr);
437
438 out << absl::StrFormat("$%02X:%04X: %-8s %-6s %-20s ; exec=%llu\n",
439 (addr >> 16) & 0xFF,
440 addr & 0xFFFF,
441 FormatHexDump(entry).c_str(),
442 entry.mnemonic.c_str(),
443 entry.operand_str.c_str(),
444 entry.execution_count);
445 }
446
447 out.close();
448 return true;
449}
450
451void DisassemblyViewer::JumpToAddress(uint32_t address) {
452 selected_address_ = address;
453 scroll_to_address_ = 0; // Force scroll update
454 auto_scroll_ = false; // Disable auto-scroll temporarily
455}
456
457std::vector<uint32_t> DisassemblyViewer::GetSortedAddresses() const {
458 std::vector<uint32_t> addrs;
459 addrs.reserve(instructions_.size());
460
461 for (const auto& [addr, _] : instructions_) {
462 addrs.push_back(addr);
463 }
464
465 std::sort(addrs.begin(), addrs.end());
466 return addrs;
467}
468
469} // namespace debug
470} // namespace emu
471} // namespace yaze
472
void Clear()
Clear all recorded instructions.
std::vector< uint32_t > GetSortedAddresses() const
Get sorted list of addresses for rendering.
void JumpToAddress(uint32_t address)
Jump to a specific address in the viewer.
std::string FormatHexDump(const DisassemblyEntry &entry) const
std::map< uint32_t, DisassemblyEntry > instructions_
ImVec4 GetMnemonicColor(const DisassemblyEntry &entry) const
void TrimToSize(size_t target_size)
Clear old instructions to save memory.
void RecordInstruction(uint32_t address, uint8_t opcode, const std::vector< uint8_t > &operands, const std::string &mnemonic, const std::string &operand_str)
Record an instruction execution.
bool PassesFilter(const DisassemblyEntry &entry) const
ImVec4 GetAddressColor(const DisassemblyEntry &entry, uint32_t current_pc) const
void RenderDisassemblyTable(uint32_t current_pc, const std::vector< uint32_t > &breakpoints)
bool ExportToFile(const std::string &filepath) const
Export disassembly to file.
void Render(uint32_t current_pc, const std::vector< uint32_t > &breakpoints)
Render the disassembly viewer UI.
#define ICON_MD_MY_LOCATION
Definition icons.h:1268
#define ICON_MD_INFO
Definition icons.h:991
#define ICON_MD_MEMORY
Definition icons.h:1193
#define ICON_MD_SEARCH
Definition icons.h:1671
#define ICON_MD_TRENDING_UP
Definition icons.h:2014
#define ICON_MD_CIRCLE
Definition icons.h:409
#define ICON_MD_STOP
Definition icons.h:1860
#define ICON_MD_CLEAR_ALL
Definition icons.h:415
#define ICON_MD_FLAG
Definition icons.h:782
#define ICON_MD_SAVE
Definition icons.h:1642
#define ICON_MD_CONTENT_COPY
Definition icons.h:463
constexpr ImVec4 kColorCurrentPC(1.0f, 0.3f, 0.3f, 1.0f)
constexpr ImVec4 kColorMnemonic(1.0f, 0.8f, 0.2f, 1.0f)
constexpr ImVec4 kColorAddress(0.4f, 0.8f, 1.0f, 1.0f)
constexpr ImVec4 kColorHotPath(1.0f, 0.6f, 0.0f, 1.0f)
constexpr ImVec4 kColorComment(0.5f, 0.5f, 0.5f, 1.0f)
constexpr ImVec4 kColorBreakpoint(1.0f, 0.0f, 0.0f, 1.0f)
constexpr ImVec4 kColorOpcode(0.8f, 0.8f, 0.8f, 1.0f)
constexpr ImVec4 kColorOperand(0.6f, 1.0f, 0.6f, 1.0f)
Main namespace for the application.
Represents a single disassembled instruction with metadata.