#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 == "--") { 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; }