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