From 5a8dbc6347b3541e84fe669b22c17ad3b715e258 Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Wed, 21 Jan 2026 20:22:09 +0100 Subject: Engage! --- samples/test.cpp | 1093 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1093 insertions(+) create mode 100644 samples/test.cpp (limited to 'samples/test.cpp') diff --git a/samples/test.cpp b/samples/test.cpp new file mode 100644 index 0000000..b078f0a --- /dev/null +++ b/samples/test.cpp @@ -0,0 +1,1093 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TB_IMPL +#include "termbox2.h" + +using namespace lldb; + +struct LayoutConfig { + int status_height = 1; + int watch_height = 15; + int sidebar_width = 50; + int log_height = 5; +} layout_config; + +// https://unicodeplus.com +const uint32_t SCROLLBAR_THUMB = 0x2593; // Dark shade +const uint32_t SCROLLBAR_LINE = 0x2502; // Vertical line +const uint32_t BREAKPOINT_CIRCLE = 0x25B6; // Filled triangle + +enum InputMode { + INPUT_MODE_NORMAL, + INPUT_MODE_BREAKPOINT, + INPUT_MODE_VARIABLE, + INPUT_MODE_WATCH, + INPUT_MODE_HELP +}; + +struct LLDBGuard { + LLDBGuard() { SBDebugger::Initialize(); } + ~LLDBGuard() { SBDebugger::Terminate(); } +}; + +struct TermboxGuard { + TermboxGuard() { tb_init(); } + ~TermboxGuard() { tb_shutdown(); } +}; + +struct SourceCache { + std::string path; + std::vector lines; + + const std::vector& get_lines(const std::string& fullpath) { + if (path != fullpath) { + path = fullpath; + lines.clear(); + std::ifstream file(fullpath); + std::string line; + while (std::getline(file, line)) { + lines.push_back(line); + } + } + return lines; + } +}; + +struct VarLine { + std::string text; + int indent; + int prefix_start; + int prefix_end; +}; + +std::string get_timestamp() { + auto now = std::chrono::system_clock::now(); + std::time_t now_c = std::chrono::system_clock::to_time_t(now); + std::tm now_tm = *std::localtime(&now_c); + std::stringstream ss; + ss << std::put_time(&now_tm, "%H:%M:%S"); + return ss.str(); +} + +void log_msg(std::vector& log_buffer, const std::string& msg) { + log_buffer.push_back(get_timestamp() + " " + msg); +} + +void draw_text(int x, int y, uint16_t fg, uint16_t bg, const std::string& text) { + for (char c : text) { + tb_set_cell(x++, y, c, fg, bg); + } +} + +void draw_box(int x, int y, int w, int h, const std::string& title) { + // Corners + tb_set_cell(x, y, 0x250C, TB_DEFAULT, TB_DEFAULT); + tb_set_cell(x + w - 1, y, 0x2510, TB_DEFAULT, TB_DEFAULT); + tb_set_cell(x, y + h - 1, 0x2514, TB_DEFAULT, TB_DEFAULT); + tb_set_cell(x + w - 1, y + h - 1, 0x2518, TB_DEFAULT, TB_DEFAULT); + + // Borders + for (int i = 1; i < w - 1; ++i) { + tb_set_cell(x + i, y, 0x2500, TB_DEFAULT, TB_DEFAULT); + tb_set_cell(x + i, y + h - 1, 0x2500, TB_DEFAULT, TB_DEFAULT); + } + for (int i = 1; i < h - 1; ++i) { + tb_set_cell(x, y + i, 0x2502, TB_DEFAULT, TB_DEFAULT); + tb_set_cell(x + w - 1, y + i, 0x2502, TB_DEFAULT, TB_DEFAULT); + } + + if (!title.empty()) { + draw_text(x + 2, y, TB_BOLD | TB_GREEN, TB_DEFAULT, " " + title + " "); + } +} + +char get_type_char(SBType type) { + if (!type.IsValid()) return '?'; + + // Resolve typedefs to their underlying canonical type + type = type.GetCanonicalType(); + + if (type.IsPointerType()) return 'p'; + if (type.IsReferenceType()) return '&'; + if (type.IsArrayType()) return 'a'; + + BasicType basic_type = type.GetBasicType(); + switch (basic_type) { + case eBasicTypeInt: + case eBasicTypeUnsignedInt: + return 'i'; + case eBasicTypeChar: + case eBasicTypeUnsignedChar: + return 'c'; + case eBasicTypeFloat: + return 'f'; + case eBasicTypeDouble: + return 'd'; + case eBasicTypeBool: + return 'b'; + case eBasicTypeLong: + case eBasicTypeUnsignedLong: + case eBasicTypeLongLong: + case eBasicTypeUnsignedLongLong: + return 'l'; + case eBasicTypeShort: + case eBasicTypeUnsignedShort: + return 's'; + case eBasicTypeVoid: + return 'v'; + default: + break; + } + + TypeClass type_class = type.GetTypeClass(); + if (type_class & eTypeClassStruct) return 's'; + if (type_class & eTypeClassClass) return 'c'; + if (type_class & eTypeClassEnumeration) return 'e'; + + const char* name = type.GetName(); + if (name && *name) return name[0]; + + return '?'; +} + +void collect_variables_recursive(SBValue val, int indent, std::vector& lines, int width, const std::string& name_override = "") { + if (indent > 3) return; + + std::string original_name = name_override.empty() ? (val.GetName() ? val.GetName() : "") : name_override; + char type_char = get_type_char(val.GetType()); + std::string prefix = std::string("(") + type_char + ") "; + + std::string val_str = val.GetValue() ? val.GetValue() : ""; + std::string summary_str = val.GetSummary() ? val.GetSummary() : ""; + std::string value; + + if (!val.IsValid()) value = "(invalid)"; + else { + if (!val_str.empty() && !summary_str.empty()) value = val_str + " " + summary_str; + else if (!val_str.empty()) value = val_str; + else if (!summary_str.empty()) value = summary_str; + } + + std::string indent_str(indent * 2, ' '); + std::string content = original_name; + if (!value.empty()) content += " = " + value; + + std::string line_text = indent_str + prefix + content; + if ((int)line_text.length() > width) line_text = line_text.substr(0, width - 3) + "..."; + + VarLine vl; + vl.text = line_text; + vl.indent = indent; + vl.prefix_start = indent * 2; + vl.prefix_end = vl.prefix_start + 4; // length of "(x) " + lines.push_back(vl); + + if (val.GetNumChildren() > 0) { + uint32_t n = val.GetNumChildren(); + for (uint32_t i = 0; i < n; ++i) { + collect_variables_recursive(val.GetChildAtIndex(i), indent + 1, lines, width); + } + } +} + +void format_variable_log(SBValue val, std::vector& log_buffer, int indent, const std::string& name_override = "") { + if (indent > 3) return; + + std::string name = name_override.empty() ? (val.GetName() ? val.GetName() : "") : name_override; + char type_char = get_type_char(val.GetType()); + + std::string val_str = val.GetValue() ? val.GetValue() : ""; + std::string summary_str = val.GetSummary() ? val.GetSummary() : ""; + std::string value; + + if (!val.IsValid()) value = "(invalid)"; + else { + if (!val_str.empty() && !summary_str.empty()) value = val_str + " " + summary_str; + else if (!val_str.empty()) value = val_str; + else if (!summary_str.empty()) value = summary_str; + } + + std::string indent_str(indent * 2, ' '); + std::string line = get_timestamp() + " " + indent_str + "(" + type_char + ") " + name; + if (!value.empty()) line += " = " + value; + + log_buffer.push_back(line); + + if (val.GetNumChildren() > 0) { + uint32_t n = val.GetNumChildren(); + for (uint32_t i = 0; i < n; ++i) { + format_variable_log(val.GetChildAtIndex(i), log_buffer, indent + 1); + } + } +} + +void draw_variables_view(SBFrame &frame, int x, int y, int w, int h, int scroll_offset) { + draw_box(x, y, w, h, "Locals"); + + // Content area + int cx = x + 1; + int cy = y + 1; + int ch = h - 2; + int cw = w - 2; + + if (!frame.IsValid()) { + draw_text(cx, cy, TB_RED, TB_DEFAULT, "No frame selected."); + return; + } + + std::vector lines; + SBValueList vars = frame.GetVariables(true, true, false, true); + for (uint32_t i = 0; i < vars.GetSize(); ++i) { + collect_variables_recursive(vars.GetValueAtIndex(i), 0, lines, cw); + } + + int total_lines = (int)lines.size(); + int display_count = std::min(total_lines, ch); + + for (int i = 0; i < display_count; ++i) { + int line_idx = scroll_offset + i; + if (line_idx < 0 || line_idx >= total_lines) continue; + + const VarLine& vl = lines[line_idx]; + for (int j = 0; j < (int)vl.text.length() && j < cw; ++j) { + uint16_t fg = TB_DEFAULT; + if (j >= vl.prefix_start && j < vl.prefix_end) { + fg = TB_BLACK | TB_BOLD; + } + tb_set_cell(cx + j, cy + i, vl.text[j], fg, TB_DEFAULT); + } + } + + // Draw scrollbar + if (total_lines > ch) { + int thumb_height = std::max(1, (ch * ch) / total_lines); + int max_scroll = total_lines - ch; + double scroll_percent = (double)scroll_offset / (double)max_scroll; + int thumb_pos = (ch - thumb_height) * scroll_percent; + + for (int i = 0; i < ch; ++i) { + uint32_t cell_char = SCROLLBAR_LINE; + uint16_t fg = TB_DEFAULT; + if (i >= thumb_pos && i < thumb_pos + thumb_height) { + cell_char = SCROLLBAR_THUMB; + fg = TB_WHITE; + } + tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT); + } + } +} + +std::string get_breakpoint_name(SBBreakpoint bp) { + if (!bp.IsValid()) return "???"; + + std::string name = "???"; + if (bp.GetNumLocations() > 0) { + SBBreakpointLocation loc = bp.GetLocationAtIndex(0); + SBAddress addr = loc.GetAddress(); + + SBFunction func = addr.GetFunction(); + if (func.IsValid()) { + const char* n = func.GetName(); + if (n) name = n; + } else { + SBSymbol sym = addr.GetSymbol(); + if (sym.IsValid()) { + const char* n = sym.GetName(); + if (n) name = n; + } + } + + SBLineEntry line_entry = addr.GetLineEntry(); + if (line_entry.IsValid()) { + std::string file_name; + SBFileSpec fs = line_entry.GetFileSpec(); + if (fs.IsValid()) file_name = fs.GetFilename(); + + if (name == "???") { + if (!file_name.empty()) name = file_name + ":" + std::to_string(line_entry.GetLine()); + } else { + if (!file_name.empty()) name += " (" + file_name + ":" + std::to_string(line_entry.GetLine()) + ")"; + } + } + } + return name; +} + +void draw_source_view(SBFrame &frame, int x, int y, int w, int h, SourceCache& cache, int scroll_offset) { + draw_box(x, y, w, h, "Source"); + + int cx = x + 1; + int cy = y + 1; + int ch = h - 2; + int cw = w - 2; + + if (!frame.IsValid()) { + draw_text(cx, cy, TB_RED, TB_DEFAULT, "No frame selected."); + return; + } + + SBLineEntry line_entry = frame.GetLineEntry(); + if (!line_entry.IsValid()) { + draw_text(cx, cy, TB_RED, TB_DEFAULT, "No line entry info."); + return; + } + + SBFileSpec file_spec = line_entry.GetFileSpec(); + if (!file_spec.IsValid()) return; + + // Construct full path + std::string fullpath; + if (file_spec.GetDirectory()) { + fullpath = std::string(file_spec.GetDirectory()) + "/" + file_spec.GetFilename(); + } else { + fullpath = file_spec.GetFilename(); + } + + SBAddress addr = frame.GetPCAddress(); + SBTarget target = frame.GetThread().GetProcess().GetTarget(); + + const std::vector& lines = cache.get_lines(fullpath); + if (lines.empty() && !std::ifstream(fullpath).good()) { + draw_text(cx, cy, TB_RED | TB_BOLD, TB_DEFAULT, "Could not open source: " + fullpath); + + SBFunction func = frame.GetFunction(); + std::string func_name = func.IsValid() ? func.GetName() : "???"; + + char addr_buf[64]; + snprintf(addr_buf, sizeof(addr_buf), "At address: 0x%lx", (unsigned long)addr.GetLoadAddress(target)); + + draw_text(cx, cy + 2, TB_WHITE, TB_DEFAULT, "Function: " + func_name); + draw_text(cx, cy + 3, TB_WHITE, TB_DEFAULT, addr_buf); + draw_text(cx, cy + 5, TB_YELLOW, TB_DEFAULT, "Press 'n' (Step Over) or 'o' (Step Out) to return to your code."); + + // Disassembly fallback + SBInstructionList instructions = target.ReadInstructions(addr, (uint32_t)(ch - 8)); + if (instructions.IsValid()) { + for (uint32_t i = 0; i < instructions.GetSize() && (int)i < ch - 8; ++i) { + SBInstruction insn = instructions.GetInstructionAtIndex(i); + std::string dis = insn.GetMnemonic(target); + dis += " "; + dis += insn.GetOperands(target); + + uint16_t fg = (insn.GetAddress() == addr) ? TB_WHITE | TB_BOLD : TB_DEFAULT; + uint16_t bg = (insn.GetAddress() == addr) ? TB_BLUE : TB_DEFAULT; + + char insn_addr_buf[32]; + snprintf(insn_addr_buf, sizeof(insn_addr_buf), "0x%lx: ", (unsigned long)insn.GetAddress().GetLoadAddress(target)); + + draw_text(cx, cy + 7 + i, fg, bg, std::string(insn_addr_buf) + dis); + } + } + return; + } + + // Get breakpoints for this file + std::vector bp_lines; + uint32_t num_breakpoints = target.GetNumBreakpoints(); + for (uint32_t i = 0; i < num_breakpoints; ++i) { + SBBreakpoint bp = target.GetBreakpointAtIndex(i); + uint32_t num_locs = bp.GetNumLocations(); + for (uint32_t j = 0; j < num_locs; ++j) { + SBBreakpointLocation loc = bp.GetLocationAtIndex(j); + SBLineEntry le = loc.GetAddress().GetLineEntry(); + if (le.IsValid()) { + SBFileSpec fs = le.GetFileSpec(); + if (fs.IsValid()) { + std::string bp_path; + if (fs.GetDirectory()) { + bp_path = std::string(fs.GetDirectory()) + "/" + fs.GetFilename(); + } else { + bp_path = fs.GetFilename(); + } + if (bp_path == fullpath) { + bp_lines.push_back(le.GetLine()); + } + } + } + } + } + + int total_lines = (int)lines.size(); + int current_line = line_entry.GetLine(); + for (int i = 0; i < ch; ++i) { + int line_idx = scroll_offset + i + 1; + if (line_idx > total_lines) break; + + std::string src = lines[line_idx - 1]; + // Handle basic tab expansion (simple version) + std::string expanded; + for (char c : src) { + if (c == '\t') expanded += " "; + else expanded += c; + } + src = expanded; + + bool is_current = (line_idx == current_line); + bool has_breakpoint = std::find(bp_lines.begin(), bp_lines.end(), (uint32_t)line_idx) != bp_lines.end(); + + char buf[32]; + snprintf(buf, sizeof(buf), "%4d ", line_idx); + std::string num_str(buf); + + uint16_t bg = is_current ? TB_BLUE : TB_DEFAULT; + uint16_t fg = is_current ? TB_WHITE | TB_BOLD : TB_DEFAULT; + + // Draw breakpoint indicator + if (has_breakpoint) { + tb_set_cell(cx, cy + i, BREAKPOINT_CIRCLE, TB_RED | TB_BOLD, bg); + } else { + tb_set_cell(cx, cy + i, ' ', fg, bg); + } + + draw_text(cx + 1, cy + i, fg, bg, num_str); + + int src_max_len = cw - (int)num_str.length() - 1; + if ((int)src.length() > src_max_len) { + src = src.substr(0, src_max_len); + } + draw_text(cx + 1 + num_str.length(), cy + i, fg, bg, src); + + if (is_current) { + for (int k = cx + 1 + num_str.length() + src.length(); k < cx + cw; ++k) { + tb_set_cell(k, cy + i, ' ', fg, bg); + } + } + } + + // Draw scrollbar + if (total_lines > ch) { + int thumb_height = std::max(1, (ch * ch) / total_lines); + int max_scroll = total_lines - ch; + double scroll_percent = (double)scroll_offset / (double)max_scroll; + int thumb_pos = (ch - thumb_height) * scroll_percent; + + for (int i = 0; i < ch; ++i) { + uint32_t cell_char = SCROLLBAR_LINE; + uint16_t fg = TB_DEFAULT; + if (i >= thumb_pos && i < thumb_pos + thumb_height) { + cell_char = SCROLLBAR_THUMB; + fg = TB_WHITE; + } + tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT); + } + } +} + +void draw_breakpoints_view(SBTarget& target, int x, int y, int w, int h) { + draw_box(x, y, w, h, "Breakpoints"); + int cx = x + 1; + int cy = y + 1; + int mh = h - 2; + + if (!target.IsValid()) return; + + int num_bps = target.GetNumBreakpoints(); + for (int i = 0; i < num_bps && i < mh; ++i) { + SBBreakpoint bp = target.GetBreakpointAtIndex(i); + std::string name = get_breakpoint_name(bp); + + char buf[128]; + snprintf(buf, sizeof(buf), "%d: %s", bp.GetID(), name.c_str()); + std::string line = buf; + draw_text(cx, cy + i, TB_DEFAULT, TB_DEFAULT, line); + } +} + +void draw_watch_view(SBFrame& frame, int x, int y, int w, int h, const std::vector& expressions, int scroll_offset) { + draw_box(x, y, w, h, "Watch"); + int cx = x + 1; + int cy = y + 1; + int ch = h - 2; + int cw = w - 2; + + if (expressions.empty()) { + draw_text(cx, cy, TB_DEFAULT, TB_DEFAULT, "No watch expressions."); + return; + } + + std::vector lines; + for (const auto& expr : expressions) { + SBValue val = frame.EvaluateExpression(expr.c_str()); + if (val.IsValid() && !val.GetError().Fail()) { + collect_variables_recursive(val, 0, lines, cw, expr); + } else { + VarLine vl; + vl.text = expr + " = (error)"; + if (val.GetError().GetCString()) { + vl.text += ": " + std::string(val.GetError().GetCString()); + } + if ((int)vl.text.length() > cw) vl.text = vl.text.substr(0, cw - 3) + "..."; + vl.indent = 0; + vl.prefix_start = 0; + vl.prefix_end = 0; + lines.push_back(vl); + } + } + + int total_lines = (int)lines.size(); + int display_count = std::min(total_lines, ch); + + for (int i = 0; i < display_count; ++i) { + int line_idx = scroll_offset + i; + if (line_idx < 0 || line_idx >= total_lines) continue; + + const VarLine& vl = lines[line_idx]; + for (int j = 0; j < (int)vl.text.length() && j < cw; ++j) { + uint16_t fg = TB_DEFAULT; + if (j >= vl.prefix_start && j < vl.prefix_end) { + fg = TB_BLACK | TB_BOLD; + } + tb_set_cell(cx + j, cy + i, vl.text[j], fg, TB_DEFAULT); + } + } + + // Draw scrollbar + if (total_lines > ch) { + int thumb_height = std::max(1, (ch * ch) / total_lines); + int max_scroll = total_lines - ch; + double scroll_percent = (double)scroll_offset / (double)max_scroll; + int thumb_pos = (ch - thumb_height) * scroll_percent; + + for (int i = 0; i < ch; ++i) { + uint32_t cell_char = SCROLLBAR_LINE; + uint16_t fg = TB_DEFAULT; + if (i >= thumb_pos && i < thumb_pos + thumb_height) { + cell_char = SCROLLBAR_THUMB; + fg = TB_WHITE; + } + tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT); + } + } +} + +SBBreakpoint create_breakpoint(SBTarget& target, const std::string& input) { + SBBreakpoint bp; + size_t colon_pos = input.rfind(':'); + + if (colon_pos != std::string::npos && colon_pos < input.length() - 1) { + std::string line_str = input.substr(colon_pos + 1); + bool is_number = !line_str.empty() && std::all_of(line_str.begin(), line_str.end(), ::isdigit); + + if (is_number) { + std::string filename = input.substr(0, colon_pos); + uint32_t line_no = (uint32_t)std::stoi(line_str); + bp = target.BreakpointCreateByLocation(filename.c_str(), line_no); + if (bp.IsValid() && bp.GetNumLocations() > 0) return bp; + } + } + + return target.BreakpointCreateByName(input.c_str()); +} + +void draw_log_view(int x, int y, int w, int h, const std::vector& log_buffer, InputMode mode, const std::string& input_buffer, int scroll_offset) { + bool input_mode = (mode == INPUT_MODE_BREAKPOINT || mode == INPUT_MODE_VARIABLE || mode == INPUT_MODE_WATCH); + std::string title = input_mode ? "Input (Esc to Cancel)" : "Logs"; + if (!input_mode && scroll_offset > 0) { + title += " (Scrolled up: " + std::to_string(scroll_offset) + ")"; + } + draw_box(x, y, w, h, title); + + int cx = x + 1; + int cy = y + 1; + int ch = h - 2; + int cw = w - 2; + + if (input_mode) { + std::string prompt; + if (mode == INPUT_MODE_BREAKPOINT) prompt = "Add Breakpoint: "; + else if (mode == INPUT_MODE_VARIABLE) prompt = "Print Variable: "; + else if (mode == INPUT_MODE_WATCH) prompt = "Watch Variable: "; + + prompt += input_buffer; + if ((int)prompt.length() > cw) prompt = prompt.substr(prompt.length() - cw); + draw_text(cx, cy, TB_WHITE | TB_BOLD, TB_DEFAULT, prompt); + tb_set_cell(cx + prompt.length(), cy, '_', TB_WHITE | TB_BOLD | TB_REVERSE, TB_DEFAULT); + } else { + int total_logs = log_buffer.size(); + int display_count = std::min(total_logs, ch); + + for (int i = 0; i < display_count; ++i) { + int log_idx = total_logs - display_count - scroll_offset + i; + if (log_idx < 0 || log_idx >= total_logs) continue; + + const std::string& msg = log_buffer[log_idx]; + std::string disp = msg; + if ((int)disp.length() > cw) disp = disp.substr(0, cw); + draw_text(cx, cy + i, TB_DEFAULT, TB_DEFAULT, disp); + } + + // Draw scrollbar + if (total_logs > ch) { + int thumb_height = std::max(1, (ch * ch) / total_logs); + int max_scroll = total_logs - ch; + double scroll_percent = (double)scroll_offset / (double)max_scroll; + int thumb_pos = (ch - thumb_height) * (1.0 - scroll_percent); + + for (int i = 0; i < ch; ++i) { + uint32_t cell_char = SCROLLBAR_LINE; + uint16_t fg = TB_DEFAULT; + if (i >= thumb_pos && i < thumb_pos + thumb_height) { + cell_char = SCROLLBAR_THUMB; + fg = TB_WHITE; + } + tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT); + } + } + } +} + +void draw_help_view(int width, int height) { + int w = 60; + int h = 18; + int x = (width - w) / 2; + int y = (height - h) / 2; + + // Fill background + for (int i = 0; i < h; ++i) { + for (int j = 0; j < w; ++j) { + tb_set_cell(x + j, y + i, ' ', TB_DEFAULT, TB_DEFAULT); + } + } + + draw_box(x, y, w, h, "Help / Keybinds"); + + int ty = y + 2; + int tx = x + 3; + + auto d = [&](const std::string& key, const std::string& desc) { + draw_text(tx, ty, TB_YELLOW | TB_BOLD, TB_DEFAULT, key); + draw_text(tx + 13, ty, TB_DEFAULT, TB_DEFAULT, desc); + ty++; + }; + + d("r", "Run / Launch program"); + d("b", "Add breakpoint (file:line or func)"); + d("p", "Print variable / Evaluate expr"); + d("w", "Add watch expression"); + d("n", "Step Over (next line)"); + d("s", "Step Into (into function)"); + d("o", "Step Out (to caller)"); + d("c", "Continue execution"); + d("h", "Toggle help window"); + d("q", "Quit debugger"); + d("Esc", "Cancel input / Close help"); + d("Ctrl+Arrows", "Resize layout"); + d("Mouse Wheel", "Scroll active window"); + + draw_text(x + (w - 24) / 2, y + h - 2, TB_BLACK, TB_WHITE, " Press any key to close "); +} + +void draw_status_bar(SBProcess &process, InputMode mode, int width, int height) { + std::string state_str = "Status: "; + if (!process.IsValid()) { + state_str += "Not Running"; + } else { + StateType state = process.GetState(); + if (state == eStateStopped) state_str += "Stopped"; + else if (state == eStateRunning) state_str += "Running"; + else if (state == eStateExited) state_str += "Exited"; + else state_str += "Unknown"; + } + + state_str += (mode == INPUT_MODE_NORMAL) + ? " | r=Run, b=Add bp, p=Print, w=Watch, n=Step, s=Step In, o=Step Out, c=Cont, h=Help, q=Quit" + : (mode == INPUT_MODE_HELP ? " | Press any key to close help" : " | Enter=Confirm, Esc=Cancel"); + + for (int x = 0; x < width; ++x) { + tb_set_cell(x, height - 1, ' ', TB_BLACK, TB_WHITE); + } + + draw_text(1, height - 1, TB_BLACK, TB_WHITE, state_str); +} + +SBProcess launch_target(SBTarget& target, const std::string& target_path, const std::vector& debuggee_args, const std::vector& target_env, std::vector& log_buffer) { + if (target.GetNumBreakpoints() == 0) { + SBBreakpoint bp = target.BreakpointCreateByName("main"); + if (bp.IsValid() && bp.GetNumLocations() > 0) { + log_msg(log_buffer, "No breakpoints. Added breakpoint at 'main'"); + } else { + log_msg(log_buffer, "No breakpoints. Failed to add breakpoint at 'main'"); + } + } + log_msg(log_buffer, "Launching..."); + + std::vector launch_argv; + launch_argv.push_back(target_path.c_str()); + for (const auto& arg : debuggee_args) { + launch_argv.push_back(arg.c_str()); + } + launch_argv.push_back(nullptr); + + std::vector launch_env; + for (const auto& env : target_env) { + launch_env.push_back(env.c_str()); + } + launch_env.push_back(nullptr); + + SBLaunchInfo launch_info(launch_argv.data()); + launch_info.SetEnvironmentEntries(launch_env.data(), true); + launch_info.SetWorkingDirectory("."); + + SBError error; + SBProcess process = target.Launch(launch_info, error); + + if (!process.IsValid() || error.Fail()) { + std::string err_msg = "Launch failed"; + if (error.GetCString()) { + err_msg += ": "; + err_msg += error.GetCString(); + } + log_msg(log_buffer, err_msg); + } else { + log_msg(log_buffer, "Launched"); + } + return process; +} + +int main(int argc, char** argv) { + std::vector target_env; + std::vector startup_breakpoints; + std::vector debuggee_args; + std::string target_path; + bool auto_run = false; + + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "-e" && i + 1 < argc) { + target_env.push_back(argv[++i]); + } else if (arg == "-b" && i + 1 < argc) { + startup_breakpoints.push_back(argv[++i]); + } else if (arg == "-run") { + auto_run = true; + } else if (arg == "-h" || arg == "--help") { + std::cout << "Usage: " << argv[0] << " [options] [-- arg1 arg2 ...]\n\n" + << "Options:\n" + << " -e KEY=VALUE Set environment variable\n" + << " -b BREAKPOINT Set startup breakpoint (name or file:line)\n" + << " -run Automatically run the target on startup\n" + << " -h, --help Show this help message\n"; + return 0; + } else if (arg == "--") { + for (int j = i + 1; j < argc; ++j) { + debuggee_args.push_back(argv[j]); + } + break; + } else if (target_path.empty()) { + target_path = arg; + } else { + debuggee_args.push_back(arg); + } + } + + if (target_path.empty()) { + std::cerr << "Usage: " << argv[0] << " [-e KEY=VALUE] [-b BREAKPOINT] [-run] ... [-- arg1 arg2 ...]\n"; + return 1; + } + + int log_fd = open("tdbg.log", O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (log_fd != -1) { + dup2(log_fd, STDERR_FILENO); + close(log_fd); + } + + LLDBGuard lldb_guard; + SBDebugger debugger = SBDebugger::Create(); + debugger.SetAsync(false); + + SBTarget target = debugger.CreateTarget(target_path.c_str()); + if (!target.IsValid()) { + std::cerr << "Failed to create target for " << target_path << "\n"; + return 1; + } + + std::vector log_buffer; + for (const auto& bp_spec : startup_breakpoints) { + SBBreakpoint bp = create_breakpoint(target, bp_spec); + if (bp.IsValid() && bp.GetNumLocations() > 0) { + log_msg(log_buffer, "Set startup breakpoint: " + bp_spec); + } else { + log_msg(log_buffer, "Failed to set startup breakpoint: " + bp_spec); + } + } + + SBProcess process; + if (auto_run) { + process = launch_target(target, target_path, debuggee_args, target_env, log_buffer); + } + + SBThread thread; + TermboxGuard tb_guard; + + bool running = true; + InputMode mode = INPUT_MODE_NORMAL; + std::string input_buffer; + std::string current_source_filename; + std::vector watch_expressions; + int log_scroll_offset = 0; + int locals_scroll_offset = 0; + int watch_scroll_offset = 0; + int source_scroll_offset = 0; + uint64_t last_pc = 0; + SourceCache source_cache; + log_buffer.push_back("Debugger started. Press 'b' to add breakpoint, 'r' to run."); + + tb_set_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE); + + while (running) { + tb_clear(); + + int width = tb_width(); + int height = tb_height(); + int main_window_height = height - layout_config.log_height - layout_config.status_height; + int split_x = width - layout_config.sidebar_width; + int locals_window_height = main_window_height - layout_config.watch_height; + + SBFrame frame; + if (process.IsValid() && process.GetState() != eStateExited) { + thread = process.GetSelectedThread(); + if (thread.IsValid()) { + frame = thread.GetSelectedFrame(); + if (frame.IsValid()) { + uint64_t current_pc = frame.GetPC(); + if (current_pc != last_pc) { + last_pc = current_pc; + SBLineEntry le = frame.GetLineEntry(); + if (le.IsValid()) { + std::string fullpath; + if (le.GetFileSpec().GetDirectory()) { + fullpath = std::string(le.GetFileSpec().GetDirectory()) + "/" + le.GetFileSpec().GetFilename(); + current_source_filename = le.GetFileSpec().GetFilename(); + } else { + fullpath = le.GetFileSpec().GetFilename(); + current_source_filename = fullpath; + } + const std::vector& lines = source_cache.get_lines(fullpath); + int total_lines = (int)lines.size(); + int ch = main_window_height - 2; + source_scroll_offset = std::max(0, (int)le.GetLine() - ch / 2 - 1); + if (source_scroll_offset + ch > total_lines) { + source_scroll_offset = std::max(0, total_lines - ch); + } + } + } + } + } + } + + draw_source_view(frame, 0, 0, split_x, main_window_height, source_cache, source_scroll_offset); + draw_variables_view(frame, split_x, 0, layout_config.sidebar_width, locals_window_height, locals_scroll_offset); + draw_watch_view(frame, split_x, locals_window_height, layout_config.sidebar_width, layout_config.watch_height, watch_expressions, watch_scroll_offset); + draw_log_view(0, main_window_height, split_x, layout_config.log_height, log_buffer, mode, input_buffer, log_scroll_offset); + draw_breakpoints_view(target, split_x, main_window_height, layout_config.sidebar_width, layout_config.log_height); + draw_status_bar(process, mode, width, height); + + if (mode == INPUT_MODE_HELP) { + draw_help_view(width, height); + } + + tb_present(); + + struct tb_event ev; + if (tb_poll_event(&ev) == 0) { + if (ev.type == TB_EVENT_KEY) { + if (mode == INPUT_MODE_NORMAL) { + if (ev.ch == 'q') { + running = false; + } else if (ev.ch == 'r') { + if (!process.IsValid() || process.GetState() == eStateExited) { + process = launch_target(target, target_path, debuggee_args, target_env, log_buffer); + } else { + log_msg(log_buffer, "Already running"); + } + } else if (ev.ch == 'b') { + mode = INPUT_MODE_BREAKPOINT; + if (!current_source_filename.empty()) { + input_buffer = current_source_filename + ":"; + } else { + input_buffer.clear(); + } + } else if (ev.ch == 'p') { + mode = INPUT_MODE_VARIABLE; + input_buffer.clear(); + } else if (ev.ch == 'w') { + mode = INPUT_MODE_WATCH; + input_buffer.clear(); + } else if (ev.ch == 'h') { + mode = INPUT_MODE_HELP; + } else { + if (process.IsValid() && process.GetState() == eStateStopped) { + switch (ev.ch) { + case 'n': if (thread.IsValid()) thread.StepOver(); break; + case 's': if (thread.IsValid()) thread.StepInto(); break; + case 'o': if (thread.IsValid()) thread.StepOut(); break; + case 'c': process.Continue(); break; + } + } + + if (ev.key == TB_KEY_ARROW_LEFT && (ev.mod & TB_MOD_CTRL)) { + layout_config.sidebar_width = std::min(width - 20, layout_config.sidebar_width + 2); + } else if (ev.key == TB_KEY_ARROW_RIGHT && (ev.mod & TB_MOD_CTRL)) { + layout_config.sidebar_width = std::max(20, layout_config.sidebar_width - 2); + } else if (ev.key == TB_KEY_ARROW_UP && (ev.mod & TB_MOD_CTRL)) { + layout_config.log_height = std::min(height - 10, layout_config.log_height + 1); + } else if (ev.key == TB_KEY_ARROW_DOWN && (ev.mod & TB_MOD_CTRL)) { + layout_config.log_height = std::max(5, layout_config.log_height - 1); + } + } + } else if (mode == INPUT_MODE_BREAKPOINT || mode == INPUT_MODE_VARIABLE || mode == INPUT_MODE_WATCH) { + if (ev.key == TB_KEY_ESC) { + mode = INPUT_MODE_NORMAL; + input_buffer.clear(); + } else if (ev.key == TB_KEY_ENTER) { + if (!input_buffer.empty()) { + if (mode == INPUT_MODE_BREAKPOINT) { + SBBreakpoint bp = create_breakpoint(target, input_buffer); + if (bp.IsValid() && bp.GetNumLocations() > 0) { + log_msg(log_buffer, "Breakpoint added: " + input_buffer); + } else { + log_msg(log_buffer, "Failed/Invalid breakpoint: " + input_buffer); + } + } else if (mode == INPUT_MODE_VARIABLE) { + if (!frame.IsValid()) { + log_msg(log_buffer, "Error: No stack frame available to evaluate '" + input_buffer + "'"); + } else { + SBValue val = frame.EvaluateExpression(input_buffer.c_str()); + if (val.IsValid() && !val.GetError().Fail()) { + format_variable_log(val, log_buffer, 0, input_buffer); + } else { + std::string err = "Error evaluating '" + input_buffer + "'"; + if (val.GetError().GetCString()) { + err += ": "; + err += val.GetError().GetCString(); + } + log_msg(log_buffer, err); + } + } + } else if (mode == INPUT_MODE_WATCH) { + watch_expressions.push_back(input_buffer); + log_msg(log_buffer, "Added to watch: " + input_buffer); + } + } + mode = INPUT_MODE_NORMAL; + input_buffer.clear(); + } else if (ev.key == TB_KEY_BACKSPACE || ev.key == TB_KEY_BACKSPACE2) { + if (!input_buffer.empty()) input_buffer.pop_back(); + } else if (ev.ch != 0) { + input_buffer += (char)ev.ch; + } + } else if (mode == INPUT_MODE_HELP) { + mode = INPUT_MODE_NORMAL; + } + } else if (ev.type == TB_EVENT_MOUSE) { + int main_window_height = tb_height() - layout_config.log_height - layout_config.status_height; + + // Log window scrolling + int log_start_y = main_window_height; + int log_end_y = tb_height() - layout_config.status_height; + if (ev.x < split_x && ev.y >= log_start_y && ev.y < log_end_y) { + if (ev.key == TB_KEY_MOUSE_WHEEL_UP) { + int max_scroll = std::max(0, (int)log_buffer.size() - (layout_config.log_height - 2)); + if (log_scroll_offset < max_scroll) { + log_scroll_offset++; + } + } else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) { + if (log_scroll_offset > 0) { + log_scroll_offset--; + } + } + } + + // Source window scrolling + if (ev.x < split_x && ev.y < main_window_height) { + SBLineEntry le = frame.GetLineEntry(); + if (le.IsValid()) { + std::string fullpath; + if (le.GetFileSpec().GetDirectory()) { + fullpath = std::string(le.GetFileSpec().GetDirectory()) + "/" + le.GetFileSpec().GetFilename(); + } else { + fullpath = le.GetFileSpec().GetFilename(); + } + const std::vector& lines = source_cache.get_lines(fullpath); + int total_lines = (int)lines.size(); + int ch = main_window_height - 2; + int max_scroll = std::max(0, total_lines - ch); + + if (ev.key == TB_KEY_MOUSE_WHEEL_UP) { + if (source_scroll_offset > 0) { + source_scroll_offset--; + } + } else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) { + if (source_scroll_offset < max_scroll) { + source_scroll_offset++; + } + } + } + } + + // Locals window scrolling + int split_x = tb_width() - layout_config.sidebar_width; + int locals_window_height = main_window_height - layout_config.watch_height; + if (ev.x >= split_x && ev.y < locals_window_height) { + std::vector lines; + if (frame.IsValid()) { + SBValueList vars = frame.GetVariables(true, true, false, true); + for (uint32_t i = 0; i < vars.GetSize(); ++i) { + collect_variables_recursive(vars.GetValueAtIndex(i), 0, lines, layout_config.sidebar_width - 2); + } + } + int max_scroll = std::max(0, (int)lines.size() - (locals_window_height - 2)); + + if (ev.key == TB_KEY_MOUSE_WHEEL_UP) { + if (locals_scroll_offset > 0) { + locals_scroll_offset--; + } + } else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) { + if (locals_scroll_offset < max_scroll) { + locals_scroll_offset++; + } + } + } + + // Watch window scrolling + if (ev.x >= split_x && ev.y >= locals_window_height && ev.y < main_window_height) { + std::vector lines; + for (const auto& expr : watch_expressions) { + SBValue val = frame.EvaluateExpression(expr.c_str()); + if (val.IsValid() && !val.GetError().Fail()) { + collect_variables_recursive(val, 0, lines, layout_config.sidebar_width - 2, expr); + } else { + VarLine vl; + vl.text = expr + " = (error)"; + lines.push_back(vl); + } + } + int max_scroll = std::max(0, (int)lines.size() - (layout_config.watch_height - 2)); + + if (ev.key == TB_KEY_MOUSE_WHEEL_UP) { + if (watch_scroll_offset > 0) { + watch_scroll_offset--; + } + } else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) { + if (watch_scroll_offset < max_scroll) { + watch_scroll_offset++; + } + } + } + } + } + } + + return 0; +} -- cgit v1.2.3