OXYGENHUB
◆ rrdc-perf enhanced-x16-emulator / src / debug_ui / panels / disasm_panel.cpp
12.1 KiB · C++ · last changed chore(): initial commit 25 days ago source blame history raw
1
// Commander X16 Emulator — ImGui Disassembly panel.
2
//
3
// Renders an anchored, bank-aware 65C02/65C816 disassembly around the current
4
// PC using the code_map core (src/code_map.*): instruction boundaries are
5
// aligned from live-execution coverage + recorded M/X/E flags, so the listing
6
// stays correct even through variable-width 65C816 code. Features:
7
//   * follow-PC auto-centering (toggle) with current-instruction highlight,
8
//   * a clickable address gutter that toggles breakpoints (with markers),
9
//   * goto-address, mouse-wheel scrolling, and bytes + mnemonic + effective addr.
10
#include "imgui.h"
11
#include "debug_ui_panels.h"
12
#include "debug_ui_bridge.h"
13
#include "debug_ui_widgets.h" // dbgui_value_lines
14
#include "debug_ui_insn_tooltip.h" // dbgui_instruction_tooltip_body
15
16
#include <stdio.h>
17
#include <string.h>
18
#include <stdint.h>
19
#include <stdlib.h>
20
21
namespace {
22
23
bool     s_follow_pc = true;   // auto-scroll to the current instruction
24
uint16_t s_view_center = 0;    // address the listing is anchored on
25
bool     s_view_valid = false; // has s_view_center been initialised?
26
char     s_goto_buf[8] = "";   // hex goto input
27
char     s_sym_buf[64] = "";   // go-to-symbol (label name) input
28
bool     s_show_labels = true; // annotate the listing with .dbg labels
29
30
// The RAM/ROM bank window an address reads from (mirrors getCurrentBank()).
31
int
32
x16bank_for(uint16_t addr, uint8_t bank, uint8_t rambank, uint8_t rombank)
33
{
34
	if (addr >= 0xA000 && bank == 0)
35
		return addr < 0xC000 ? (int)rambank : (int)rombank;
36
	return -1;
37
}
38
39
// Index of a breakpoint at (pc, bank, x16bank), or -1.
40
//
41
// Delegates to the core rather than re-walking breakPoints[] here. Comparing pc
42
// and bank only -- which this did -- ignores the bank selector that identifies a
43
// breakpoint, so the gutter drew a dot for a breakpoint set in a different RAM
44
// bank; clicking it then failed to find one to remove and silently added a
45
// second, once per bank the machine happened to be in.
46
int
47
find_breakpoint(int pc, uint8_t bank, int x16bank)
48
{
49
	return debug_bp_find(pc, bank, x16bank);
50
}
51
52
void
53
toggle_breakpoint(uint16_t addr, uint8_t bank, int x16bank)
54
{
55
	if (!DEBUGRemoveBreakPoint((int)addr, bank, x16bank)) {
56
		struct breakpoint bp = {};
57
		bp.pc      = (int)addr;
58
		bp.bank    = bank;
59
		bp.x16Bank = x16bank;
60
		DEBUGAddBreakPoint(bp);
61
	}
62
}
63
64
// Advance `center` by `delta` instructions (negative = backward).
65
uint16_t
66
move_center(uint16_t center, int delta, uint8_t bank, uint8_t rambank, uint8_t rombank)
67
{
68
	while (delta < 0) {
69
		center = code_map_prev_instruction(center, bank, rambank, rombank);
70
		delta++;
71
	}
72
	while (delta > 0) {
73
		code_map_line_t ln;
74
		uint16_t        next = center;
75
		code_map_disasm_forward(center, bank, rambank, rombank, 1, &ln, 1, &next);
76
		center = next;
77
		delta--;
78
	}
79
	return center;
80
}
81
82
void
83
disasm_panel_render(bool *p_open)
84
{
85
	if (!ImGui::Begin("Disassembly", p_open)) {
86
		dbgui_window_end();
87
		return;
88
	}
89
	dbgui_window_zoom("disasm"); // Ctrl+wheel zooms this window's text only
90
91
	const uint16_t pc      = regs.pc;
92
	const uint8_t  bank    = regs.k;
93
	const uint8_t  rambank = memory_get_ram_bank();
94
	const uint8_t  rombank = memory_get_rom_bank();
95
96
	if (!s_view_valid) {
97
		s_view_center = pc;
98
		s_view_valid  = true;
99
	}
100
101
	// Honor a cross-panel goto request (from Symbols / Call Stack panels).
102
	{
103
		uint16_t gaddr;
104
		uint8_t  gbank;
105
		if (debug_ui_peek_goto(&gaddr, &gbank)) {
106
			s_view_center = gaddr;
107
			s_follow_pc   = false;
108
		}
109
	}
110
111
	// ── Toolbar ──────────────────────────────────────────────────────────
112
	ImGui::Checkbox("Follow PC", &s_follow_pc);
113
	ImGui::SameLine();
114
	if (ImGui::Button("Go to PC")) {
115
		s_view_center = pc;
116
		s_follow_pc   = true;
117
	}
118
	ImGui::SameLine();
119
	ImGui::SetNextItemWidth(70.0f);
120
	if (ImGui::InputText("Goto", s_goto_buf, sizeof(s_goto_buf),
121
	                     ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue)) {
122
		if (s_goto_buf[0] != '\0') {
123
			s_view_center = (uint16_t)strtoul(s_goto_buf, nullptr, 16);
124
			s_follow_pc   = false;
125
		}
126
	}
127
	ImGui::SameLine();
128
	ImGui::TextDisabled("K:%02X RAM:%02X ROM:%02X", bank, rambank, rombank);
129
130
	// Go-to-symbol + label toggle (cc65 .dbg labels).
131
	ImGui::SetNextItemWidth(120.0f);
132
	if (ImGui::InputTextWithHint("##sym", "label", s_sym_buf, sizeof(s_sym_buf),
133
	                             ImGuiInputTextFlags_EnterReturnsTrue)) {
134
		uint16_t sym_addr;
135
		if (s_sym_buf[0] != '\0' && debug_ui_label_to_addr(s_sym_buf, &sym_addr)) {
136
			s_view_center = sym_addr;
137
			s_follow_pc   = false;
138
		}
139
	}
140
	ImGui::SameLine();
141
	if (ImGui::Button("Go to sym")) {
142
		uint16_t sym_addr;
143
		if (s_sym_buf[0] != '\0' && debug_ui_label_to_addr(s_sym_buf, &sym_addr)) {
144
			s_view_center = sym_addr;
145
			s_follow_pc   = false;
146
		}
147
	}
148
	ImGui::SameLine();
149
	ImGui::Checkbox("Labels", &s_show_labels);
150
151
	if (s_follow_pc)
152
		s_view_center = pc;
153
154
	ImGui::Separator();
155
156
	// ── Listing ──────────────────────────────────────────────────────────
157
	ImGui::BeginChild("disasm_lines", ImVec2(0, 0), false, ImGuiWindowFlags_NoMove);
158
159
	// Fill the visible area: split the rows above/below the anchor so the
160
	// centered instruction sits in the middle.
161
	const float line_h = ImGui::GetTextLineHeightWithSpacing();
162
	int rows = (int)(ImGui::GetContentRegionAvail().y / (line_h > 0 ? line_h : 1.0f));
163
	if (rows < 8)
164
		rows = 8;
165
	if (rows > 500)
166
		rows = 500;
167
	const int before = rows / 2;
168
	const int after  = rows - before;
169
170
	static code_map_line_t lines[512];
171
	int center_index = -1;
172
	int n = code_map_disasm_window(s_view_center, bank, rambank, rombank,
173
	                               before, after, lines, 512, &center_index);
174
	(void)center_index;
175
176
	ImDrawList  *draw   = ImGui::GetWindowDrawList();
177
	const float  glyph  = ImGui::CalcTextSize("0").x;
178
	const float  gutter = glyph * 2.0f;
179
180
	bool cursor_set = false; // did the mouse hover an instruction row this frame?
181
	for (int i = 0; i < n; i++) {
182
		const code_map_line_t &ln = lines[i];
183
		const bool is_pc  = (ln.addr == pc);
184
		// Same bank the toggle below uses, so the dot and the click agree.
185
		const int  ln_x16 = x16bank_for(ln.addr, bank, rambank, rombank);
186
		const int  bpidx  = find_breakpoint((int)ln.addr, bank, ln_x16);
187
		const bool has_bp = (bpidx >= 0);
188
189
		ImGui::PushID(i);
190
191
		// Clickable gutter → toggle breakpoint on this address.
192
		const ImVec2 row_pos = ImGui::GetCursorScreenPos();
193
		if (ImGui::InvisibleButton("bp", ImVec2(gutter, line_h))) {
194
			toggle_breakpoint(ln.addr, bank, ln_x16);
195
		}
196
		if (has_bp) {
197
			const ImVec2 c(row_pos.x + gutter * 0.5f, row_pos.y + line_h * 0.5f);
198
			draw->AddCircleFilled(c, glyph * 0.45f, IM_COL32(230, 60, 60, 255));
199
		}
200
201
		// Build the fixed-width text: bytes + mnemonic (+ effective address).
202
		char bytes[16];
203
		int  bl = 0;
204
		for (int b = 0; b < ln.size && b < 4; b++)
205
			bl += snprintf(bytes + bl, sizeof(bytes) - (size_t)bl, "%02X ", ln.bytes[b]);
206
		for (; bl < 12 && bl < (int)sizeof(bytes) - 1; bl++)
207
			bytes[bl] = ' ';
208
		bytes[bl] = '\0';
209
210
		char eff[48] = "";
211
		if (ln.eff_addr >= 0) {
212
			const char *elabel = nullptr;
213
			if (s_show_labels && dbg_info_addr_to_label((uint16_t)ln.eff_addr, &elabel) && elabel) {
214
				snprintf(eff, sizeof(eff), "  =%04X %s", (unsigned)ln.eff_addr, elabel);
215
			} else {
216
				snprintf(eff, sizeof(eff), "  =%04X", (unsigned)ln.eff_addr);
217
			}
218
		}
219
220
		// Label at this instruction's own address, shown comment-style at the
221
		// end so the fixed-width columns stay aligned.
222
		char lbl[40] = "";
223
		const char *linelabel = nullptr;
224
		if (s_show_labels && dbg_info_addr_to_label(ln.addr, &linelabel) && linelabel) {
225
			snprintf(lbl, sizeof(lbl), "  ; %s", linelabel);
226
		}
227
228
		char text[160];
229
		snprintf(text, sizeof(text), "%04X: %s %-18s%s%s", ln.addr, bytes, ln.text, eff, lbl);
230
231
		// Row on the same line as the gutter; the Selectable also toggles the
232
		// breakpoint so clicking anywhere on the address line works.
233
		// Colour by whether the address carries live-execution evidence, not by
234
		// whether the row is a whole instruction. A row cut short by a boundary
235
		// carries start_recorded = true with recorded = false, and tinting it
236
		// as "inferred" would deny evidence that exists. The tint tracks a
237
		// currently believed anchor, which is the best signal available -- not
238
		// proof the code on screen is what ran.
239
		ImGui::SameLine(0.0f, 0.0f);
240
		ImVec4 col = is_pc             ? ImVec4(1.00f, 0.92f, 0.40f, 1.0f)   // current PC
241
		           : ln.start_recorded ? ImVec4(0.86f, 0.86f, 0.86f, 1.0f)   // anchored
242
		                               : ImVec4(0.55f, 0.55f, 0.58f, 1.0f);  // inferred
243
		ImGui::PushStyleColor(ImGuiCol_Text, col);
244
		if (ImGui::Selectable(text, is_pc)) {
245
			toggle_breakpoint(ln.addr, bank, x16bank_for(ln.addr, bank, rambank, rombank));
246
		}
247
		ImGui::PopStyleColor();
248
249
		// Publish the hovered instruction as the Ctrl+F10 "run to cursor" target.
250
		if (ImGui::IsItemHovered()) {
251
			debug_ui_set_cursor(ln.addr, bank, true);
252
			cursor_set = true;
253
254
			// Same instruction help the Source panel gives, so hovering here
255
			// explains the instruction and — on the PC's row — predicts what it
256
			// will do. Followed by the operand's live value.
257
			//
258
			// Gated on the row being an instruction at all, and on the lookup
259
			// succeeding -- not merely on a token being extracted. code_map
260
			// renders a row whose decode had to be cut short as its raw data
261
			// bytes (".byte $2c"), which yields a token that is not an
262
			// instruction. Opening a tooltip for it would show an empty box,
263
			// since the body returns immediately and those rows carry no
264
			// effective address either.
265
			//
266
			// This cannot hide a tooltip that had content: the body draws
267
			// nothing at all unless the lookup succeeds, and the operand-value
268
			// section below is gated on eff_addr independently. Swept the whole
269
			// opcode space (both CPUs, both width settings) for mnemonics the
270
			// table lacks: only "dbg" ($DB), which is implied and so has no
271
			// effective address -- it too used to open an empty box.
272
			char mnem[16];
273
			const bool have_mnem = ln.kind == CM_LINE_INSTRUCTION &&
274
			                       dbgui_mnemonic_of(ln.text, mnem, sizeof mnem) &&
275
			                       insn_info_lookup(mnem) != nullptr;
276
277
			if (have_mnem || ln.eff_addr >= 0) {
278
				ImGui::BeginTooltip();
279
				if (have_mnem) {
280
					dbgui_instruction_tooltip_body(mnem, ln.addr, bank, is_pc);
281
				}
282
				if (ln.eff_addr >= 0) {
283
					uint16_t ea = (uint16_t)ln.eff_addr;
284
					uint8_t  b  = debug_ui_read6502(ea, 0, DEBUG_UI_CURRENT_BANK);
285
					uint8_t  b2 = debug_ui_read6502((uint16_t)(ea + 1), 0, DEBUG_UI_CURRENT_BANK);
286
					uint16_t w  = (uint16_t)(b | (b2 << 8));
287
					const char *elabel = nullptr;
288
					if (have_mnem)
289
						ImGui::Separator();
290
					if (dbg_info_addr_to_label(ea, &elabel) && elabel)
291
						ImGui::Text("[$%04X]  %s", ea, elabel);
292
					else
293
						ImGui::Text("[$%04X]", ea);
294
					ImGui::Separator();
295
					ImGui::TextDisabled("byte");
296
					dbgui_value_lines(b, 1);
297
					ImGui::Separator();
298
					ImGui::TextDisabled("word (LE)");
299
					dbgui_value_lines(w, 2);
300
				}
301
				ImGui::EndTooltip();
302
			}
303
		}
304
305
		// Right-click an instruction → run to it (only meaningful while paused).
306
		if (ImGui::BeginPopupContextItem("disasm_ctx")) {
307
			ImGui::BeginDisabled(!DEBUGIsPaused());
308
			if (ImGui::MenuItem("Run to here")) {
309
				DEBUGRunTo(ln.addr, bank, DEBUG_OWNER_UI);
310
			}
311
			ImGui::EndDisabled();
312
			if (ImGui::MenuItem("Toggle breakpoint")) {
313
				toggle_breakpoint(ln.addr, bank, x16bank_for(ln.addr, bank, rambank, rombank));
314
			}
315
			ImGui::EndPopup();
316
		}
317
318
		if (is_pc && (s_follow_pc || ImGui::IsWindowAppearing()))
319
			ImGui::SetScrollHereY(0.5f);
320
321
		ImGui::PopID();
322
	}
323
324
	if (!cursor_set)
325
		debug_ui_set_cursor(0, 0, false); // nothing hovered → invalidate run-to-cursor
326
327
	// Mouse-wheel scrolling re-anchors the view (and drops follow-PC).
328
	if (ImGui::IsWindowHovered()) {
329
		const float wheel = ImGui::GetIO().MouseWheel;
330
		if (wheel != 0.0f) {
331
			s_follow_pc   = false;
332
			s_view_center = move_center(s_view_center, (int)(-wheel * 3.0f), bank, rambank, rombank);
333
		}
334
	}
335
336
	ImGui::EndChild();
337
	dbgui_window_end();
338
}
339
340
DebugPanelRegistration s_reg("Disassembly", disasm_panel_render, true);
341
342
} // namespace
343