| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
#include "imgui.h" |
| 18 |
#include "debug_ui_panels.h" |
| 19 |
#include "debug_ui_bridge.h" |
| 20 |
#include "debug_ui_widgets.h" |
| 21 |
#include "debug_ui_insn_tooltip.h" |
| 22 |
|
| 23 |
#include <stdio.h> |
| 24 |
#include <stdlib.h> |
| 25 |
#include <string.h> |
| 26 |
#include <stdint.h> |
| 27 |
#include <ctype.h> |
| 28 |
#include <string> |
| 29 |
#include <vector> |
| 30 |
|
| 31 |
|
| 32 |
extern "C" { |
| 33 |
#include "dbg_info.h" |
| 34 |
#include "source_view.h" |
| 35 |
#include "insn_info.h" |
| 36 |
#include "disasm.h" |
| 37 |
extern uint8_t *fsroot_path; |
| 38 |
} |
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
static int |
| 46 |
dbgui_stricmp(const char *a, const char *b) |
| 47 |
{ |
| 48 |
for (;; a++, b++) { |
| 49 |
int ca = tolower((unsigned char)*a); |
| 50 |
int cb = tolower((unsigned char)*b); |
| 51 |
if (ca != cb || !ca) |
| 52 |
return ca - cb; |
| 53 |
} |
| 54 |
} |
| 55 |
|
| 56 |
|
| 57 |
static std::vector<std::string> s_tabs; |
| 58 |
static std::string s_pcFile; |
| 59 |
static int s_pcLine = 0; |
| 60 |
static std::string s_lastPcFile; |
| 61 |
static int s_lastPcLine = 0; |
| 62 |
static bool s_follow = true; |
| 63 |
static bool s_addedDbgPath = false; |
| 64 |
|
| 65 |
|
| 66 |
|
| 67 |
|
| 68 |
|
| 69 |
|
| 70 |
|
| 71 |
|
| 72 |
|
| 73 |
|
| 74 |
static bool s_scrollRequest = false; |
| 75 |
static bool s_scrollForce = false; |
| 76 |
static int s_scrollTarget = 0; |
| 77 |
static std::string s_scrollFile; |
| 78 |
static int s_scrollAge = 0; |
| 79 |
static std::string s_selectFile; |
| 80 |
static int s_gotoLine = 0; |
| 81 |
static std::string s_activeFile; |
| 82 |
|
| 83 |
|
| 84 |
|
| 85 |
|
| 86 |
static std::string s_flashFile; |
| 87 |
static int s_flashLine = 0; |
| 88 |
static double s_flashTime = 0.0; |
| 89 |
constexpr double SRC_FLASH_SECONDS = 3.0; |
| 90 |
|
| 91 |
|
| 92 |
|
| 93 |
|
| 94 |
|
| 95 |
static void |
| 96 |
src_request_scroll(const char *file, int line, bool force, bool flash = true) |
| 97 |
{ |
| 98 |
if (line < 1) |
| 99 |
return; |
| 100 |
s_scrollRequest = true; |
| 101 |
s_scrollForce = force; |
| 102 |
s_scrollTarget = line; |
| 103 |
s_scrollFile = file ? file : ""; |
| 104 |
s_scrollAge = 0; |
| 105 |
|
| 106 |
|
| 107 |
|
| 108 |
|
| 109 |
if (force && flash) { |
| 110 |
s_flashFile = file ? file : ""; |
| 111 |
s_flashLine = line; |
| 112 |
s_flashTime = ImGui::GetTime(); |
| 113 |
} |
| 114 |
} |
| 115 |
|
| 116 |
|
| 117 |
static const char * |
| 118 |
src_basename(const char *f) |
| 119 |
{ |
| 120 |
const char *b = f; |
| 121 |
for (const char *p = f; *p; ++p) |
| 122 |
if (*p == '/' || *p == '\\') |
| 123 |
b = p + 1; |
| 124 |
return b; |
| 125 |
} |
| 126 |
|
| 127 |
|
| 128 |
static void |
| 129 |
src_ensure_tab(const char *file) |
| 130 |
{ |
| 131 |
if (!file || !file[0]) |
| 132 |
return; |
| 133 |
for (const std::string &t : s_tabs) |
| 134 |
if (t == file) |
| 135 |
return; |
| 136 |
s_tabs.push_back(file); |
| 137 |
} |
| 138 |
|
| 139 |
|
| 140 |
static int |
| 141 |
src_find_bp(uint16_t addr) |
| 142 |
{ |
| 143 |
for (int i = 0; i < numBreakpoints; i++) { |
| 144 |
if (breakPoints[i].pc == (int)addr && breakPoints[i].bank == 0) |
| 145 |
return i; |
| 146 |
} |
| 147 |
return -1; |
| 148 |
} |
| 149 |
|
| 150 |
|
| 151 |
static bool |
| 152 |
src_line_has_bp(const char *file, int line) |
| 153 |
{ |
| 154 |
if (numBreakpoints <= 0) |
| 155 |
return false; |
| 156 |
uint16_t addr; |
| 157 |
if (!debug_ui_source_to_addr(file, line, &addr)) |
| 158 |
return false; |
| 159 |
return src_find_bp(addr) >= 0; |
| 160 |
} |
| 161 |
|
| 162 |
|
| 163 |
static void |
| 164 |
src_toggle_bp(const char *file, int line) |
| 165 |
{ |
| 166 |
uint16_t addr; |
| 167 |
if (!file || !file[0] || !debug_ui_source_to_addr(file, line, &addr)) |
| 168 |
return; |
| 169 |
|
| 170 |
int x16Bank = (addr >= 0xA000) |
| 171 |
? (addr < 0xC000 ? (int)memory_get_ram_bank() |
| 172 |
: (int)memory_get_rom_bank()) |
| 173 |
: -1; |
| 174 |
if (!DEBUGRemoveBreakPoint((int)addr, 0, x16Bank)) { |
| 175 |
struct breakpoint bp = {}; |
| 176 |
bp.pc = (int)addr; |
| 177 |
bp.bank = 0; |
| 178 |
bp.x16Bank = x16Bank; |
| 179 |
DEBUGAddBreakPoint(bp); |
| 180 |
} |
| 181 |
} |
| 182 |
|
| 183 |
|
| 184 |
static void |
| 185 |
src_expand_tabs(const char *s, char *out, size_t outsz) |
| 186 |
{ |
| 187 |
size_t col = 0; |
| 188 |
while (*s && col + 1 < outsz) { |
| 189 |
if (*s == '\t') { |
| 190 |
size_t next = (col + 4) & ~(size_t)3; |
| 191 |
while (col < next && col + 1 < outsz) |
| 192 |
out[col++] = ' '; |
| 193 |
s++; |
| 194 |
} else { |
| 195 |
unsigned char c = (unsigned char)*s++; |
| 196 |
out[col++] = (c < 0x20 || c > 0x7E) ? ' ' : (char)c; |
| 197 |
} |
| 198 |
} |
| 199 |
out[col] = '\0'; |
| 200 |
} |
| 201 |
|
| 202 |
|
| 203 |
|
| 204 |
|
| 205 |
static void |
| 206 |
src_render_open_popup(void) |
| 207 |
{ |
| 208 |
if (!ImGui::BeginPopup("src_open")) |
| 209 |
return; |
| 210 |
|
| 211 |
ImGui::TextDisabled("Source files in the loaded debug info:"); |
| 212 |
ImGui::Separator(); |
| 213 |
int fc = dbg_info_file_count(); |
| 214 |
int shown = 0; |
| 215 |
for (int i = 0; i < fc; i++) { |
| 216 |
const char *nm = nullptr; |
| 217 |
if (!dbg_info_file_at(i, &nm) || !nm) |
| 218 |
continue; |
| 219 |
bool isOpen = false; |
| 220 |
for (const std::string &t : s_tabs) |
| 221 |
if (t == nm) { isOpen = true; break; } |
| 222 |
char lbl[300]; |
| 223 |
snprintf(lbl, sizeof lbl, "%s%s##openf%d", nm, isOpen ? " (open)" : "", i); |
| 224 |
if (ImGui::Selectable(lbl)) |
| 225 |
src_ensure_tab(nm); |
| 226 |
shown++; |
| 227 |
} |
| 228 |
if (shown == 0) |
| 229 |
ImGui::TextDisabled(" (none)"); |
| 230 |
|
| 231 |
|
| 232 |
static char paths[64][DBG_INFO_PATH_MAX]; |
| 233 |
int np = 0; |
| 234 |
np += dbg_info_scan_dbg_files(dbg_info_get_dbg_dir(), paths + np, 64 - np); |
| 235 |
if (fsroot_path) |
| 236 |
np += dbg_info_scan_dbg_files((const char *)fsroot_path, paths + np, 64 - np); |
| 237 |
if (np > 0) { |
| 238 |
ImGui::Separator(); |
| 239 |
ImGui::TextDisabled("Load another .dbg module (for not-yet-loaded code):"); |
| 240 |
for (int i = 0; i < np; i++) { |
| 241 |
char lbl[DBG_INFO_PATH_MAX + 24]; |
| 242 |
snprintf(lbl, sizeof lbl, "Load %s##loaddbg%d", src_basename(paths[i]), i); |
| 243 |
if (ImGui::Selectable(lbl)) { |
| 244 |
if (dbg_info_load(paths[i]) == 0) |
| 245 |
source_view_invalidate(); |
| 246 |
} |
| 247 |
if (ImGui::IsItemHovered()) |
| 248 |
ImGui::SetTooltip("%s", paths[i]); |
| 249 |
} |
| 250 |
} |
| 251 |
|
| 252 |
ImGui::EndPopup(); |
| 253 |
} |
| 254 |
|
| 255 |
|
| 256 |
|
| 257 |
|
| 258 |
|
| 259 |
static void |
| 260 |
src_hover_token_tooltip(const char *line, float text_x, float glyph_w, bool is_pc_line) |
| 261 |
{ |
| 262 |
ImVec2 mp = ImGui::GetIO().MousePos; |
| 263 |
float rel = mp.x - text_x; |
| 264 |
if (rel < 0 || glyph_w <= 0) |
| 265 |
return; |
| 266 |
int col = (int)(rel / glyph_w); |
| 267 |
int len = (int)strlen(line); |
| 268 |
if (col < 0 || col >= len) |
| 269 |
return; |
| 270 |
|
| 271 |
auto is_ident = [](char c) { |
| 272 |
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || |
| 273 |
c == '_' || c == '$'; |
| 274 |
}; |
| 275 |
if (!is_ident(line[col])) |
| 276 |
return; |
| 277 |
|
| 278 |
int s = col, e = col; |
| 279 |
while (s > 0 && is_ident(line[s - 1])) |
| 280 |
s--; |
| 281 |
while (e < len && is_ident(line[e])) |
| 282 |
e++; |
| 283 |
char tok[64]; |
| 284 |
int tl = 0; |
| 285 |
for (int k = s; k < e && tl < 63; k++) |
| 286 |
tok[tl++] = line[k]; |
| 287 |
tok[tl] = '\0'; |
| 288 |
if (tl == 0) |
| 289 |
return; |
| 290 |
|
| 291 |
|
| 292 |
uint32_t num = 0; |
| 293 |
bool is_num = false; |
| 294 |
if (tok[0] == '$') { |
| 295 |
char *endp = nullptr; |
| 296 |
num = (uint32_t)strtoul(tok + 1, &endp, 16); |
| 297 |
is_num = (endp && *endp == '\0' && endp != tok + 1); |
| 298 |
} else if (tok[0] >= '0' && tok[0] <= '9') { |
| 299 |
char *endp = nullptr; |
| 300 |
num = (uint32_t)strtoul(tok, &endp, 10); |
| 301 |
is_num = (endp && *endp == '\0'); |
| 302 |
} |
| 303 |
if (is_num) { |
| 304 |
ImGui::BeginTooltip(); |
| 305 |
ImGui::TextUnformatted(tok); |
| 306 |
ImGui::Separator(); |
| 307 |
dbgui_value_lines(num, num > 0xFF ? 2 : 1); |
| 308 |
ImGui::EndTooltip(); |
| 309 |
return; |
| 310 |
} |
| 311 |
|
| 312 |
|
| 313 |
|
| 314 |
|
| 315 |
const insn_info_t *insn = insn_info_lookup(tok); |
| 316 |
if (insn) { |
| 317 |
int first = 0; |
| 318 |
while (first < len && (line[first] == ' ' || line[first] == '\t')) |
| 319 |
first++; |
| 320 |
|
| 321 |
if (first < len) { |
| 322 |
int c = first; |
| 323 |
while (c < len && is_ident(line[c])) |
| 324 |
c++; |
| 325 |
if (c < len && line[c] == ':') { |
| 326 |
c++; |
| 327 |
while (c < len && (line[c] == ' ' || line[c] == '\t')) |
| 328 |
c++; |
| 329 |
first = c; |
| 330 |
} |
| 331 |
} |
| 332 |
if (s != first) |
| 333 |
insn = nullptr; |
| 334 |
} |
| 335 |
if (insn) { |
| 336 |
ImGui::BeginTooltip(); |
| 337 |
dbgui_instruction_tooltip_body(tok, regs.pc, regs.k, is_pc_line); |
| 338 |
ImGui::EndTooltip(); |
| 339 |
return; |
| 340 |
} |
| 341 |
|
| 342 |
|
| 343 |
|
| 344 |
|
| 345 |
uint16_t addr; |
| 346 |
int kind = DBG_NAME_LABEL; |
| 347 |
if (debug_ui_name_to_value(tok, &addr, &kind)) { |
| 348 |
const bool is_equ = (kind == DBG_NAME_EQUATE); |
| 349 |
uint8_t b = debug_ui_read6502(addr, 0, DEBUG_UI_CURRENT_BANK); |
| 350 |
uint8_t b2 = debug_ui_read6502((uint16_t)(addr + 1), 0, DEBUG_UI_CURRENT_BANK); |
| 351 |
uint16_t w = (uint16_t)(b | (b2 << 8)); |
| 352 |
|
| 353 |
ImGui::BeginTooltip(); |
| 354 |
ImGui::Text("%s = $%04X", tok, addr); |
| 355 |
ImGui::SameLine(); |
| 356 |
ImGui::TextDisabled(is_equ ? "(equate)" : "(label)"); |
| 357 |
|
| 358 |
|
| 359 |
|
| 360 |
const char *at = nullptr; |
| 361 |
if (dbg_info_addr_to_label(addr, &at) && at && dbgui_stricmp(at, tok) != 0) { |
| 362 |
ImGui::TextDisabled("also known as %s", at); |
| 363 |
} |
| 364 |
|
| 365 |
ImGui::Separator(); |
| 366 |
ImGui::TextDisabled("byte [$%04X]", addr); |
| 367 |
dbgui_value_lines(b, 1); |
| 368 |
ImGui::Separator(); |
| 369 |
ImGui::TextDisabled("word [$%04X] (LE)", addr); |
| 370 |
dbgui_value_lines(w, 2); |
| 371 |
ImGui::EndTooltip(); |
| 372 |
} |
| 373 |
} |
| 374 |
|
| 375 |
|
| 376 |
|
| 377 |
static void |
| 378 |
src_render_body(const char *file, bool isPcFile, int pcLine, bool pcMoved) |
| 379 |
{ |
| 380 |
s_activeFile = file ? file : ""; |
| 381 |
const ImVec4 orange(1.0f, 0.78f, 0.35f, 1.0f); |
| 382 |
|
| 383 |
const source_file_t *sf = source_view_get(file); |
| 384 |
int lineCount = (sf && sf->found) ? sf->count : 0; |
| 385 |
|
| 386 |
if (!sf || !sf->found) { |
| 387 |
ImGui::TextColored(orange, "%s (source file not found)", src_basename(file)); |
| 388 |
ImGui::TextWrapped("Could not locate '%s' on disk. Add a search directory " |
| 389 |
"with -srcpath <dir>.", |
| 390 |
file); |
| 391 |
return; |
| 392 |
} |
| 393 |
|
| 394 |
if (isPcFile) |
| 395 |
ImGui::TextColored(orange, "%s line %d / %d", src_basename(file), pcLine, lineCount); |
| 396 |
else |
| 397 |
ImGui::TextColored(orange, "%s %d lines", src_basename(file), lineCount); |
| 398 |
ImGui::Separator(); |
| 399 |
|
| 400 |
|
| 401 |
|
| 402 |
|
| 403 |
|
| 404 |
|
| 405 |
|
| 406 |
|
| 407 |
|
| 408 |
|
| 409 |
if (isPcFile && s_follow && pcMoved) { |
| 410 |
const char *why = debug_server_last_stop_reason(); |
| 411 |
const bool stepping = (why && strcmp(why, "step") == 0); |
| 412 |
src_request_scroll(file, pcLine, !stepping, false); |
| 413 |
} |
| 414 |
|
| 415 |
ImGui::BeginChild("srcbody", ImVec2(0, 0), 0, ImGuiWindowFlags_None); |
| 416 |
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); |
| 417 |
|
| 418 |
ImDrawList *dl = ImGui::GetWindowDrawList(); |
| 419 |
float rowH = ImGui::GetTextLineHeight(); |
| 420 |
|
| 421 |
char maxnum[16]; |
| 422 |
snprintf(maxnum, sizeof maxnum, "%d", lineCount > 0 ? lineCount : 1); |
| 423 |
float markerW = ImGui::CalcTextSize("0").x * 1.8f; |
| 424 |
float digitsW = ImGui::CalcTextSize(maxnum).x; |
| 425 |
float spaceW = ImGui::CalcTextSize(" ").x; |
| 426 |
float glyphW = ImGui::CalcTextSize("0").x; |
| 427 |
float textX = markerW + digitsW + spaceW * 2.0f; |
| 428 |
|
| 429 |
const ImU32 colNum = IM_COL32(130, 130, 130, 255); |
| 430 |
const ImU32 colText = IM_COL32(210, 210, 210, 255); |
| 431 |
const ImU32 colCur = IM_COL32(255, 240, 120, 255); |
| 432 |
const ImU32 curBg = IM_COL32(38, 60, 120, 255); |
| 433 |
const ImU32 hoverBg = IM_COL32(70, 70, 70, 150); |
| 434 |
const ImU32 bpCol = IM_COL32(230, 70, 70, 255); |
| 435 |
|
| 436 |
|
| 437 |
|
| 438 |
|
| 439 |
|
| 440 |
if (s_scrollRequest && s_scrollTarget >= 1 && |
| 441 |
(s_scrollFile.empty() || s_scrollFile == file)) { |
| 442 |
int target_line = s_scrollTarget; |
| 443 |
if (target_line > lineCount) |
| 444 |
target_line = lineCount > 0 ? lineCount : 1; |
| 445 |
|
| 446 |
float childH = ImGui::GetWindowHeight(); |
| 447 |
float sY = ImGui::GetScrollY(); |
| 448 |
float top = (target_line - 1) * rowH; |
| 449 |
bool visible = (top >= sY + rowH) && (top <= sY + childH - 2 * rowH); |
| 450 |
if (s_scrollForce || !visible) { |
| 451 |
float target = top - childH * 0.5f + rowH * 0.5f; |
| 452 |
if (target < 0) |
| 453 |
target = 0; |
| 454 |
ImGui::SetScrollY(target); |
| 455 |
} |
| 456 |
s_scrollRequest = false; |
| 457 |
s_scrollFile.clear(); |
| 458 |
} |
| 459 |
|
| 460 |
ImGuiListClipper clipper; |
| 461 |
clipper.Begin(lineCount, rowH); |
| 462 |
while (clipper.Step()) { |
| 463 |
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i) { |
| 464 |
int lineNo = i + 1; |
| 465 |
bool isCur = isPcFile && (lineNo == pcLine); |
| 466 |
|
| 467 |
const double flashAge = ImGui::GetTime() - s_flashTime; |
| 468 |
const bool isFlash = (s_flashLine == lineNo) && (s_flashFile == file) && |
| 469 |
(flashAge >= 0.0) && (flashAge < SRC_FLASH_SECONDS); |
| 470 |
|
| 471 |
ImGui::PushID(i); |
| 472 |
ImVec2 p0 = ImGui::GetCursorScreenPos(); |
| 473 |
float rowW = ImGui::GetContentRegionAvail().x; |
| 474 |
if (rowW < textX + 10.0f) |
| 475 |
rowW = textX + 10.0f; |
| 476 |
|
| 477 |
bool pressed = ImGui::InvisibleButton("row", ImVec2(rowW, rowH)); |
| 478 |
bool hovered = ImGui::IsItemHovered(); |
| 479 |
|
| 480 |
|
| 481 |
|
| 482 |
if (ImGui::BeginPopupContextItem("srcctx")) { |
| 483 |
uint16_t addr; |
| 484 |
bool mappable = debug_ui_source_to_addr(file, lineNo, &addr); |
| 485 |
ImGui::BeginDisabled(!mappable || !DEBUGIsPaused()); |
| 486 |
if (ImGui::MenuItem("Run to here")) |
| 487 |
DEBUGRunTo(addr, 0, DEBUG_OWNER_UI); |
| 488 |
ImGui::EndDisabled(); |
| 489 |
ImGui::BeginDisabled(!mappable); |
| 490 |
if (ImGui::MenuItem("Toggle breakpoint")) |
| 491 |
src_toggle_bp(file, lineNo); |
| 492 |
ImGui::EndDisabled(); |
| 493 |
ImGui::EndPopup(); |
| 494 |
} |
| 495 |
|
| 496 |
|
| 497 |
|
| 498 |
|
| 499 |
if (isCur) { |
| 500 |
dl->AddRectFilled(p0, ImVec2(p0.x + rowW, p0.y + rowH), curBg); |
| 501 |
} else if (isFlash) { |
| 502 |
const float k = 1.0f - (float)(flashAge / SRC_FLASH_SECONDS); |
| 503 |
const int a = (int)(40.0f + 150.0f * k); |
| 504 |
dl->AddRectFilled(p0, ImVec2(p0.x + rowW, p0.y + rowH), |
| 505 |
IM_COL32(150, 110, 30, a)); |
| 506 |
dl->AddRect(p0, ImVec2(p0.x + rowW, p0.y + rowH), |
| 507 |
IM_COL32(240, 200, 90, (int)(80.0f + 175.0f * k))); |
| 508 |
} else if (hovered) { |
| 509 |
dl->AddRectFilled(p0, ImVec2(p0.x + rowW, p0.y + rowH), hoverBg); |
| 510 |
} |
| 511 |
|
| 512 |
|
| 513 |
|
| 514 |
|
| 515 |
ImVec2 center(p0.x + markerW * 0.5f, p0.y + rowH * 0.5f); |
| 516 |
float radius = rowH * 0.30f; |
| 517 |
const float mouseX = ImGui::GetIO().MousePos.x - p0.x; |
| 518 |
const bool inGutter = hovered && mouseX >= 0.0f && mouseX < markerW; |
| 519 |
if (src_line_has_bp(file, lineNo)) { |
| 520 |
dl->AddCircleFilled(center, radius, bpCol); |
| 521 |
} else if (inGutter) { |
| 522 |
uint16_t tmp; |
| 523 |
if (debug_ui_source_to_addr(file, lineNo, &tmp)) |
| 524 |
dl->AddCircle(center, radius, IM_COL32(230, 70, 70, 150)); |
| 525 |
} |
| 526 |
|
| 527 |
|
| 528 |
char num[16]; |
| 529 |
snprintf(num, sizeof num, "%d", lineNo); |
| 530 |
float numRight = p0.x + markerW + digitsW; |
| 531 |
float numW = ImGui::CalcTextSize(num).x; |
| 532 |
dl->AddText(ImVec2(numRight - numW, p0.y), colNum, num); |
| 533 |
|
| 534 |
|
| 535 |
char buf[1024]; |
| 536 |
src_expand_tabs(sf->lines[lineNo - 1], buf, sizeof buf); |
| 537 |
dl->AddText(ImVec2(p0.x + textX, p0.y), isCur ? colCur : colText, buf); |
| 538 |
|
| 539 |
|
| 540 |
|
| 541 |
if (hovered) |
| 542 |
src_hover_token_tooltip(buf, p0.x + textX, glyphW, isCur); |
| 543 |
|
| 544 |
ImGui::PopID(); |
| 545 |
|
| 546 |
|
| 547 |
|
| 548 |
|
| 549 |
|
| 550 |
|
| 551 |
if (pressed) { |
| 552 |
const float clickX = ImGui::GetIO().MousePos.x - p0.x; |
| 553 |
if (clickX >= 0.0f && clickX < markerW) |
| 554 |
src_toggle_bp(file, lineNo); |
| 555 |
} |
| 556 |
} |
| 557 |
} |
| 558 |
clipper.End(); |
| 559 |
|
| 560 |
ImGui::PopStyleVar(); |
| 561 |
ImGui::EndChild(); |
| 562 |
} |
| 563 |
|
| 564 |
static void |
| 565 |
source_panel_render(bool *p_open) |
| 566 |
{ |
| 567 |
if (!ImGui::Begin("Source", p_open)) { |
| 568 |
dbgui_window_end(); |
| 569 |
return; |
| 570 |
} |
| 571 |
dbgui_window_zoom("source"); |
| 572 |
|
| 573 |
const ImVec4 orange(1.0f, 0.78f, 0.35f, 1.0f); |
| 574 |
|
| 575 |
|
| 576 |
if (!dbg_info_is_loaded()) { |
| 577 |
ImGui::TextColored(orange, "No debug info loaded."); |
| 578 |
ImGui::Spacing(); |
| 579 |
ImGui::TextWrapped( |
| 580 |
"The Source view needs cc65 debug info (a .dbg file) that maps " |
| 581 |
"addresses to source lines."); |
| 582 |
ImGui::Spacing(); |
| 583 |
ImGui::BulletText("Start the emulator with: -dbgfile <program.dbg>"); |
| 584 |
ImGui::BulletText("If the .s/.c files aren't beside the .dbg: -srcpath <dir>"); |
| 585 |
ImGui::BulletText("A LOADed program's matching .dbg is picked up automatically."); |
| 586 |
dbgui_window_end(); |
| 587 |
return; |
| 588 |
} |
| 589 |
|
| 590 |
|
| 591 |
|
| 592 |
const char *pcf = nullptr; |
| 593 |
int pcl = 0; |
| 594 |
bool mapping = (regs.k == 0) && |
| 595 |
dbg_info_addr_to_source_banked(regs.pc, (int)memory_get_ram_bank(), &pcf, &pcl); |
| 596 |
|
| 597 |
|
| 598 |
|
| 599 |
|
| 600 |
|
| 601 |
|
| 602 |
bool viaCaller = false; |
| 603 |
uint16_t callerPc = 0; |
| 604 |
if (!mapping) { |
| 605 |
const bool native = regs.is65c816 && !regs.e; |
| 606 |
uint32_t sp = regs.sp; |
| 607 |
uint32_t from = native ? (sp + 1) : (0x100 + (sp & 0xFF) + 1); |
| 608 |
uint32_t to = native ? (sp + 1 + 256) : 0x200; |
| 609 |
for (uint32_t a = from; a + 1 < to && !viaCaller; a++) { |
| 610 |
uint8_t lo = debug_ui_read6502((uint16_t)a, 0, DEBUG_UI_CURRENT_BANK); |
| 611 |
uint8_t hi = debug_ui_read6502((uint16_t)(a + 1), 0, DEBUG_UI_CURRENT_BANK); |
| 612 |
uint16_t pushed = (uint16_t)(lo | (hi << 8)); |
| 613 |
uint8_t opc = debug_ui_read6502((uint16_t)(pushed - 2), regs.k, DEBUG_UI_CURRENT_BANK); |
| 614 |
if (opc != 0x20 && opc != 0xFC) |
| 615 |
continue; |
| 616 |
uint16_t ret = (uint16_t)(pushed + 1); |
| 617 |
const char *cf = nullptr; |
| 618 |
int cl = 0; |
| 619 |
if (dbg_info_addr_to_source_banked(ret, (int)memory_get_ram_bank(), &cf, &cl) && cf) { |
| 620 |
pcf = cf; pcl = cl; callerPc = ret; |
| 621 |
viaCaller = true; |
| 622 |
} |
| 623 |
} |
| 624 |
} |
| 625 |
|
| 626 |
if ((mapping || viaCaller) && pcf) { |
| 627 |
if (!s_addedDbgPath) { |
| 628 |
source_view_add_path(dbg_info_get_dbg_dir()); |
| 629 |
s_addedDbgPath = true; |
| 630 |
} |
| 631 |
|
| 632 |
|
| 633 |
|
| 634 |
|
| 635 |
|
| 636 |
|
| 637 |
|
| 638 |
|
| 639 |
|
| 640 |
|
| 641 |
if (viaCaller) { |
| 642 |
const source_file_t *sf = source_view_get(pcf); |
| 643 |
if (!sf || !sf->found) { |
| 644 |
viaCaller = false; |
| 645 |
pcf = nullptr; |
| 646 |
} |
| 647 |
} |
| 648 |
if (pcf) { |
| 649 |
s_pcFile = pcf; |
| 650 |
s_pcLine = pcl; |
| 651 |
src_ensure_tab(s_pcFile.c_str()); |
| 652 |
} |
| 653 |
} |
| 654 |
|
| 655 |
bool pcMoved = (mapping || viaCaller) && (s_pcLine != s_lastPcLine || s_pcFile != s_lastPcFile); |
| 656 |
|
| 657 |
|
| 658 |
|
| 659 |
|
| 660 |
{ |
| 661 |
uint16_t gaddr; |
| 662 |
uint8_t gbank; |
| 663 |
if (debug_ui_peek_goto(&gaddr, &gbank)) { |
| 664 |
const char *gf = nullptr; |
| 665 |
int gl = 0; |
| 666 |
if (dbg_info_addr_to_source_banked(gaddr, (int)memory_get_ram_bank(), &gf, &gl) && gf) { |
| 667 |
if (!s_addedDbgPath) { |
| 668 |
source_view_add_path(dbg_info_get_dbg_dir()); |
| 669 |
s_addedDbgPath = true; |
| 670 |
} |
| 671 |
src_ensure_tab(gf); |
| 672 |
s_selectFile = gf; |
| 673 |
src_request_scroll(gf, gl, true); |
| 674 |
} |
| 675 |
} |
| 676 |
} |
| 677 |
|
| 678 |
|
| 679 |
ImGui::Checkbox("Follow PC", &s_follow); |
| 680 |
if (ImGui::IsItemHovered()) |
| 681 |
ImGui::SetTooltip("Select and scroll to the PC's file and line on every stop."); |
| 682 |
if (dbg_info_has_high_level()) { |
| 683 |
|
| 684 |
|
| 685 |
|
| 686 |
ImGui::SameLine(); |
| 687 |
bool byLine = DEBUGGetSourceStep(); |
| 688 |
if (ImGui::Checkbox("Step by line", &byLine)) |
| 689 |
DEBUGSetSourceStep(byLine); |
| 690 |
if (ImGui::IsItemHovered()) |
| 691 |
ImGui::SetTooltip("F10/F11 run a whole C statement instead of one\n" |
| 692 |
"6502 instruction. Off steps by instruction, which\n" |
| 693 |
"is what the Disassembly panel shows."); |
| 694 |
} |
| 695 |
ImGui::SameLine(); |
| 696 |
if (ImGui::Button("Go to PC")) { |
| 697 |
|
| 698 |
if ((mapping || viaCaller) && !s_pcFile.empty()) { |
| 699 |
src_ensure_tab(s_pcFile.c_str()); |
| 700 |
s_selectFile = s_pcFile; |
| 701 |
src_request_scroll(s_pcFile.c_str(), s_pcLine, true); |
| 702 |
} |
| 703 |
} |
| 704 |
if (ImGui::IsItemHovered()) |
| 705 |
ImGui::SetTooltip("Jump to the line the PC maps to (or its nearest\n" |
| 706 |
"mapped caller when the PC has no source)."); |
| 707 |
ImGui::SameLine(0, 16); |
| 708 |
if (ImGui::Button("Open...")) |
| 709 |
ImGui::OpenPopup("src_open"); |
| 710 |
src_render_open_popup(); |
| 711 |
ImGui::SameLine(0, 16); |
| 712 |
ImGui::TextUnformatted("Goto line:"); |
| 713 |
ImGui::SameLine(); |
| 714 |
ImGui::SetNextItemWidth(90); |
| 715 |
bool go = false; |
| 716 |
ImGui::InputInt("##gotoln", &s_gotoLine, 0, 0); |
| 717 |
if (dbgui_committed_with_enter()) |
| 718 |
go = true; |
| 719 |
ImGui::SameLine(); |
| 720 |
if (ImGui::Button("Go")) |
| 721 |
go = true; |
| 722 |
if (go && s_gotoLine >= 1) { |
| 723 |
|
| 724 |
|
| 725 |
|
| 726 |
|
| 727 |
const std::string &target = !s_activeFile.empty() ? s_activeFile : s_pcFile; |
| 728 |
if (!target.empty()) { |
| 729 |
src_ensure_tab(target.c_str()); |
| 730 |
s_selectFile = target; |
| 731 |
src_request_scroll(target.c_str(), s_gotoLine, true); |
| 732 |
} else { |
| 733 |
src_request_scroll(nullptr, s_gotoLine, true); |
| 734 |
} |
| 735 |
} |
| 736 |
if (!mapping && regs.k != 0) { |
| 737 |
ImGui::SameLine(0, 16); |
| 738 |
ImGui::TextDisabled("PC in bank %02X (no source map)", regs.k); |
| 739 |
} else if (viaCaller) { |
| 740 |
ImGui::SameLine(0, 16); |
| 741 |
ImGui::TextColored(ImVec4(0.95f, 0.75f, 0.35f, 1.0f), |
| 742 |
"PC $%04X has no source (KERNAL/trampoline) — showing caller $%04X", |
| 743 |
regs.pc, callerPc); |
| 744 |
} else if (!mapping) { |
| 745 |
ImGui::SameLine(0, 16); |
| 746 |
ImGui::TextDisabled("PC $%04X — no source mapping", regs.pc); |
| 747 |
} |
| 748 |
|
| 749 |
ImGui::Separator(); |
| 750 |
|
| 751 |
if (s_tabs.empty()) { |
| 752 |
ImGui::TextWrapped("No source file open yet. Use \"Open...\" to pick one, or " |
| 753 |
"step into code covered by the loaded .dbg."); |
| 754 |
s_lastPcLine = s_pcLine; |
| 755 |
s_lastPcFile = s_pcFile; |
| 756 |
dbgui_window_end(); |
| 757 |
return; |
| 758 |
} |
| 759 |
|
| 760 |
|
| 761 |
int closeIdx = -1; |
| 762 |
if (ImGui::BeginTabBar("src_tabs", |
| 763 |
ImGuiTabBarFlags_AutoSelectNewTabs | |
| 764 |
ImGuiTabBarFlags_Reorderable | |
| 765 |
ImGuiTabBarFlags_DrawSelectedOverline | |
| 766 |
ImGuiTabBarFlags_TabListPopupButton)) { |
| 767 |
for (int i = 0; i < (int)s_tabs.size(); i++) { |
| 768 |
const std::string &f = s_tabs[i]; |
| 769 |
bool isPc = (mapping || viaCaller) && (f == s_pcFile); |
| 770 |
|
| 771 |
ImGuiTabItemFlags flags = 0; |
| 772 |
if (s_follow && isPc && pcMoved) |
| 773 |
flags |= ImGuiTabItemFlags_SetSelected; |
| 774 |
if (!s_selectFile.empty() && f == s_selectFile) |
| 775 |
flags |= ImGuiTabItemFlags_SetSelected; |
| 776 |
|
| 777 |
|
| 778 |
|
| 779 |
char label[300]; |
| 780 |
snprintf(label, sizeof label, "%s%s###srctab%s", isPc ? "> " : "", |
| 781 |
src_basename(f.c_str()), f.c_str()); |
| 782 |
|
| 783 |
bool open = true; |
| 784 |
ImGui::PushID(f.c_str()); |
| 785 |
if (ImGui::BeginTabItem(label, &open, flags)) { |
| 786 |
src_render_body(f.c_str(), isPc, s_pcLine, pcMoved); |
| 787 |
ImGui::EndTabItem(); |
| 788 |
} |
| 789 |
ImGui::PopID(); |
| 790 |
if (!open) |
| 791 |
closeIdx = i; |
| 792 |
} |
| 793 |
ImGui::EndTabBar(); |
| 794 |
} |
| 795 |
s_selectFile.clear(); |
| 796 |
if (closeIdx >= 0) |
| 797 |
s_tabs.erase(s_tabs.begin() + closeIdx); |
| 798 |
|
| 799 |
|
| 800 |
|
| 801 |
if (s_scrollRequest && ++s_scrollAge > 4) { |
| 802 |
s_scrollRequest = false; |
| 803 |
s_scrollFile.clear(); |
| 804 |
} |
| 805 |
|
| 806 |
s_lastPcLine = s_pcLine; |
| 807 |
s_lastPcFile = s_pcFile; |
| 808 |
|
| 809 |
dbgui_window_end(); |
| 810 |
} |
| 811 |
|
| 812 |
static DebugPanelRegistration s_reg("Source", source_panel_render, true); |