| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
#include "imgui.h" |
| 11 |
#include "debug_ui_panels.h" |
| 12 |
#include "debug_ui_bridge.h" |
| 13 |
#include "debug_ui_widgets.h" |
| 14 |
|
| 15 |
#include <stdint.h> |
| 16 |
#include <stdio.h> |
| 17 |
|
| 18 |
extern "C" { |
| 19 |
#include "dbg_info.h" |
| 20 |
} |
| 21 |
|
| 22 |
namespace { |
| 23 |
|
| 24 |
struct Frame { |
| 25 |
uint16_t addr; |
| 26 |
bool is_pc; |
| 27 |
}; |
| 28 |
|
| 29 |
void |
| 30 |
callstack_panel_render(bool *p_open) |
| 31 |
{ |
| 32 |
if (!ImGui::Begin("Call Stack", p_open)) { |
| 33 |
dbgui_window_end(); |
| 34 |
return; |
| 35 |
} |
| 36 |
dbgui_window_zoom("callstack"); |
| 37 |
|
| 38 |
const uint8_t bank = regs.k; |
| 39 |
const bool native = regs.is65c816 && !regs.e; |
| 40 |
|
| 41 |
|
| 42 |
Frame frames[64]; |
| 43 |
int nf = 0; |
| 44 |
frames[nf++] = {regs.pc, true}; |
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
uint32_t sp = regs.sp; |
| 49 |
uint32_t start = native ? (sp + 1) : (0x100 + (sp & 0xFF) + 1); |
| 50 |
uint32_t end = native ? (sp + 1 + 256) : 0x200; |
| 51 |
|
| 52 |
for (uint32_t a = start; a + 1 < end && nf < 64;) { |
| 53 |
uint8_t lo = debug_ui_read6502((uint16_t)a, 0, DEBUG_UI_CURRENT_BANK); |
| 54 |
uint8_t hi = debug_ui_read6502((uint16_t)(a + 1), 0, DEBUG_UI_CURRENT_BANK); |
| 55 |
uint16_t pushed = (uint16_t)(lo | (hi << 8)); |
| 56 |
|
| 57 |
uint8_t opc = debug_ui_read6502((uint16_t)(pushed - 2), bank, DEBUG_UI_CURRENT_BANK); |
| 58 |
if (opc == 0x20) { |
| 59 |
frames[nf++] = {(uint16_t)(pushed + 1), false}; |
| 60 |
a += 2; |
| 61 |
} else { |
| 62 |
a += 1; |
| 63 |
} |
| 64 |
} |
| 65 |
|
| 66 |
if (!dbg_info_is_loaded()) { |
| 67 |
ImGui::TextDisabled("(no .dbg loaded — addresses only; load debug info for source/symbols)"); |
| 68 |
} |
| 69 |
ImGui::TextDisabled("Heuristic unwind (JSR-based). Click a frame to go there."); |
| 70 |
|
| 71 |
if (ImGui::BeginTable("callstack", 3, |
| 72 |
ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV | |
| 73 |
ImGuiTableFlags_ScrollY | DBGUI_TABLE_FLAGS_RESIZABLE)) { |
| 74 |
ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_WidthFixed, dbgui_col_width("#", "99")); |
| 75 |
ImGui::TableSetupColumn("Address / Symbol", ImGuiTableColumnFlags_WidthStretch); |
| 76 |
ImGui::TableSetupColumn("Source", ImGuiTableColumnFlags_WidthStretch); |
| 77 |
ImGui::TableHeadersRow(); |
| 78 |
|
| 79 |
for (int i = 0; i < nf; i++) { |
| 80 |
const Frame &f = frames[i]; |
| 81 |
ImGui::TableNextRow(); |
| 82 |
ImGui::PushID(i); |
| 83 |
|
| 84 |
ImGui::TableSetColumnIndex(0); |
| 85 |
if (f.is_pc) |
| 86 |
ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.35f, 1.0f), "%d", i); |
| 87 |
else |
| 88 |
ImGui::Text("%d", i); |
| 89 |
|
| 90 |
ImGui::TableSetColumnIndex(1); |
| 91 |
const char *label = nullptr; |
| 92 |
char buf[128]; |
| 93 |
bool has_src = |
| 94 |
dbg_info_addr_to_source_banked(f.addr, (int)memory_get_ram_bank(), nullptr, nullptr); |
| 95 |
const char *nosrc = has_src ? "" : " (no src)"; |
| 96 |
|
| 97 |
|
| 98 |
|
| 99 |
|
| 100 |
|
| 101 |
|
| 102 |
|
| 103 |
uint16_t label_at = 0; |
| 104 |
if (dbg_info_addr_to_label(f.addr, &label) && label) { |
| 105 |
snprintf(buf, sizeof buf, "$%04X %s%s", f.addr, label, nosrc); |
| 106 |
} else if (has_src && debug_ui_addr_to_label_nearest(f.addr, &label, &label_at) && label) { |
| 107 |
|
| 108 |
|
| 109 |
snprintf(buf, sizeof buf, "$%04X %s+%u", f.addr, label, |
| 110 |
(unsigned)(f.addr - label_at)); |
| 111 |
} else { |
| 112 |
snprintf(buf, sizeof buf, "$%04X%s", f.addr, nosrc); |
| 113 |
} |
| 114 |
if (ImGui::Selectable(buf, f.is_pc, ImGuiSelectableFlags_SpanAllColumns)) |
| 115 |
debug_ui_request_goto(f.addr, bank); |
| 116 |
|
| 117 |
ImGui::TableSetColumnIndex(2); |
| 118 |
const char *file = nullptr; |
| 119 |
int line = 0; |
| 120 |
if (dbg_info_addr_to_source_banked(f.addr, (int)memory_get_ram_bank(), &file, &line) && file) { |
| 121 |
const char *base = file; |
| 122 |
for (const char *p = file; *p; ++p) |
| 123 |
if (*p == '/' || *p == '\\') |
| 124 |
base = p + 1; |
| 125 |
ImGui::Text("%s:%d", base, line); |
| 126 |
} else { |
| 127 |
|
| 128 |
|
| 129 |
|
| 130 |
const char *what; |
| 131 |
if (f.addr >= 0xC000) |
| 132 |
what = "[KERNAL/ROM] no source"; |
| 133 |
else if (f.addr >= 0xA000) |
| 134 |
what = "[banked] no source"; |
| 135 |
else if (f.addr < 0x0800) |
| 136 |
what = "[stub/KERNAL RAM] no source"; |
| 137 |
else |
| 138 |
what = "[NOSRC]"; |
| 139 |
ImGui::TextColored(ImVec4(0.70f, 0.70f, 0.78f, 1.0f), "%s", what); |
| 140 |
if (ImGui::IsItemHovered()) |
| 141 |
ImGui::SetTooltip("$%04X is not covered by the loaded .dbg.\n" |
| 142 |
"$C000-$FFFF is banked ROM (KERNAL/BASIC);\n" |
| 143 |
"$0200-$07FF usually holds KERNAL RAM routines\n" |
| 144 |
"such as the JSRFAR far-call trampoline.", f.addr); |
| 145 |
} |
| 146 |
|
| 147 |
ImGui::PopID(); |
| 148 |
} |
| 149 |
ImGui::EndTable(); |
| 150 |
} |
| 151 |
|
| 152 |
dbgui_window_end(); |
| 153 |
} |
| 154 |
|
| 155 |
DebugPanelRegistration s_reg("Call Stack", callstack_panel_render, true); |
| 156 |
|
| 157 |
} |