1#include <lldb/API/LLDB.h>
2
3#include <iostream>
4#include <string>
5#include <chrono>
6#include <fstream>
7#include <vector>
8#include <algorithm>
9#include <fcntl.h>
10#include <unistd.h>
11#include <iomanip>
12#include <ctime>
13#include <sstream>
14
15#define TB_IMPL
16#include "termbox2.h"
17
18using namespace lldb;
19
20struct LayoutConfig {
21 int status_height = 1;
22 int watch_height = 15;
23 int sidebar_width = 50;
24 int log_height = 5;
25} layout_config;
26
27// https://unicodeplus.com
28const uint32_t SCROLLBAR_THUMB = 0x2593; // Dark shade
29const uint32_t SCROLLBAR_LINE = 0x2502; // Vertical line
30const uint32_t BREAKPOINT_CIRCLE = 0x25B6; // Filled triangle
31
32enum InputMode {
33 INPUT_MODE_NORMAL,
34 INPUT_MODE_BREAKPOINT,
35 INPUT_MODE_VARIABLE,
36 INPUT_MODE_WATCH,
37 INPUT_MODE_HELP
38};
39
40struct LLDBGuard {
41 LLDBGuard() { SBDebugger::Initialize(); }
42 ~LLDBGuard() { SBDebugger::Terminate(); }
43};
44
45struct TermboxGuard {
46 TermboxGuard() { tb_init(); }
47 ~TermboxGuard() { tb_shutdown(); }
48};
49
50struct SourceCache {
51 std::string path;
52 std::vector<std::string> lines;
53
54 const std::vector<std::string>& get_lines(const std::string& fullpath) {
55 if (path != fullpath) {
56 path = fullpath;
57 lines.clear();
58 std::ifstream file(fullpath);
59 std::string line;
60 while (std::getline(file, line)) {
61 lines.push_back(line);
62 }
63 }
64 return lines;
65 }
66};
67
68struct VarLine {
69 std::string text;
70 int indent;
71 int prefix_start;
72 int prefix_end;
73};
74
75std::string get_timestamp() {
76 auto now = std::chrono::system_clock::now();
77 std::time_t now_c = std::chrono::system_clock::to_time_t(now);
78 std::tm now_tm = *std::localtime(&now_c);
79 std::stringstream ss;
80 ss << std::put_time(&now_tm, "%H:%M:%S");
81 return ss.str();
82}
83
84void log_msg(std::vector<std::string>& log_buffer, const std::string& msg) {
85 log_buffer.push_back(get_timestamp() + " " + msg);
86}
87
88void draw_text(int x, int y, uint16_t fg, uint16_t bg, const std::string& text) {
89 for (char c : text) {
90 tb_set_cell(x++, y, c, fg, bg);
91 }
92}
93
94void draw_box(int x, int y, int w, int h, const std::string& title) {
95 // Corners
96 tb_set_cell(x, y, 0x250C, TB_DEFAULT, TB_DEFAULT);
97 tb_set_cell(x + w - 1, y, 0x2510, TB_DEFAULT, TB_DEFAULT);
98 tb_set_cell(x, y + h - 1, 0x2514, TB_DEFAULT, TB_DEFAULT);
99 tb_set_cell(x + w - 1, y + h - 1, 0x2518, TB_DEFAULT, TB_DEFAULT);
100
101 // Borders
102 for (int i = 1; i < w - 1; ++i) {
103 tb_set_cell(x + i, y, 0x2500, TB_DEFAULT, TB_DEFAULT);
104 tb_set_cell(x + i, y + h - 1, 0x2500, TB_DEFAULT, TB_DEFAULT);
105 }
106 for (int i = 1; i < h - 1; ++i) {
107 tb_set_cell(x, y + i, 0x2502, TB_DEFAULT, TB_DEFAULT);
108 tb_set_cell(x + w - 1, y + i, 0x2502, TB_DEFAULT, TB_DEFAULT);
109 }
110
111 if (!title.empty()) {
112 draw_text(x + 2, y, TB_BOLD | TB_GREEN, TB_DEFAULT, " " + title + " ");
113 }
114}
115
116char get_type_char(SBType type) {
117 if (!type.IsValid()) return '?';
118
119 // Resolve typedefs to their underlying canonical type
120 type = type.GetCanonicalType();
121
122 if (type.IsPointerType()) return 'p';
123 if (type.IsReferenceType()) return '&';
124 if (type.IsArrayType()) return 'a';
125
126 BasicType basic_type = type.GetBasicType();
127 switch (basic_type) {
128 case eBasicTypeInt:
129 case eBasicTypeUnsignedInt:
130 return 'i';
131 case eBasicTypeChar:
132 case eBasicTypeUnsignedChar:
133 return 'c';
134 case eBasicTypeFloat:
135 return 'f';
136 case eBasicTypeDouble:
137 return 'd';
138 case eBasicTypeBool:
139 return 'b';
140 case eBasicTypeLong:
141 case eBasicTypeUnsignedLong:
142 case eBasicTypeLongLong:
143 case eBasicTypeUnsignedLongLong:
144 return 'l';
145 case eBasicTypeShort:
146 case eBasicTypeUnsignedShort:
147 return 's';
148 case eBasicTypeVoid:
149 return 'v';
150 default:
151 break;
152 }
153
154 TypeClass type_class = type.GetTypeClass();
155 if (type_class & eTypeClassStruct) return 's';
156 if (type_class & eTypeClassClass) return 'c';
157 if (type_class & eTypeClassEnumeration) return 'e';
158
159 const char* name = type.GetName();
160 if (name && *name) return name[0];
161
162 return '?';
163}
164
165void collect_variables_recursive(SBValue val, int indent, std::vector<VarLine>& lines, int width, const std::string& name_override = "") {
166 if (indent > 3) return;
167
168 std::string original_name = name_override.empty() ? (val.GetName() ? val.GetName() : "") : name_override;
169 char type_char = get_type_char(val.GetType());
170 std::string prefix = std::string("(") + type_char + ") ";
171
172 std::string val_str = val.GetValue() ? val.GetValue() : "";
173 std::string summary_str = val.GetSummary() ? val.GetSummary() : "";
174 std::string value;
175
176 if (!val.IsValid()) value = "(invalid)";
177 else {
178 if (!val_str.empty() && !summary_str.empty()) value = val_str + " " + summary_str;
179 else if (!val_str.empty()) value = val_str;
180 else if (!summary_str.empty()) value = summary_str;
181 }
182
183 std::string indent_str(indent * 2, ' ');
184 std::string content = original_name;
185 if (!value.empty()) content += " = " + value;
186
187 std::string line_text = indent_str + prefix + content;
188 if ((int)line_text.length() > width) line_text = line_text.substr(0, width - 3) + "...";
189
190 VarLine vl;
191 vl.text = line_text;
192 vl.indent = indent;
193 vl.prefix_start = indent * 2;
194 vl.prefix_end = vl.prefix_start + 4; // length of "(x) "
195 lines.push_back(vl);
196
197 if (val.GetNumChildren() > 0) {
198 uint32_t n = val.GetNumChildren();
199 for (uint32_t i = 0; i < n; ++i) {
200 collect_variables_recursive(val.GetChildAtIndex(i), indent + 1, lines, width);
201 }
202 }
203}
204
205void format_variable_log(SBValue val, std::vector<std::string>& log_buffer, int indent, const std::string& name_override = "") {
206 if (indent > 3) return;
207
208 std::string name = name_override.empty() ? (val.GetName() ? val.GetName() : "") : name_override;
209 char type_char = get_type_char(val.GetType());
210
211 std::string val_str = val.GetValue() ? val.GetValue() : "";
212 std::string summary_str = val.GetSummary() ? val.GetSummary() : "";
213 std::string value;
214
215 if (!val.IsValid()) value = "(invalid)";
216 else {
217 if (!val_str.empty() && !summary_str.empty()) value = val_str + " " + summary_str;
218 else if (!val_str.empty()) value = val_str;
219 else if (!summary_str.empty()) value = summary_str;
220 }
221
222 std::string indent_str(indent * 2, ' ');
223 std::string line = get_timestamp() + " " + indent_str + "(" + type_char + ") " + name;
224 if (!value.empty()) line += " = " + value;
225
226 log_buffer.push_back(line);
227
228 if (val.GetNumChildren() > 0) {
229 uint32_t n = val.GetNumChildren();
230 for (uint32_t i = 0; i < n; ++i) {
231 format_variable_log(val.GetChildAtIndex(i), log_buffer, indent + 1);
232 }
233 }
234}
235
236void draw_variables_view(SBFrame &frame, int x, int y, int w, int h, int scroll_offset) {
237 draw_box(x, y, w, h, "Locals");
238
239 // Content area
240 int cx = x + 1;
241 int cy = y + 1;
242 int ch = h - 2;
243 int cw = w - 2;
244
245 if (!frame.IsValid()) {
246 draw_text(cx, cy, TB_RED, TB_DEFAULT, "No frame selected.");
247 return;
248 }
249
250 std::vector<VarLine> lines;
251 SBValueList vars = frame.GetVariables(true, true, false, true);
252 for (uint32_t i = 0; i < vars.GetSize(); ++i) {
253 collect_variables_recursive(vars.GetValueAtIndex(i), 0, lines, cw);
254 }
255
256 int total_lines = (int)lines.size();
257 int display_count = std::min(total_lines, ch);
258
259 for (int i = 0; i < display_count; ++i) {
260 int line_idx = scroll_offset + i;
261 if (line_idx < 0 || line_idx >= total_lines) continue;
262
263 const VarLine& vl = lines[line_idx];
264 for (int j = 0; j < (int)vl.text.length() && j < cw; ++j) {
265 uint16_t fg = TB_DEFAULT;
266 if (j >= vl.prefix_start && j < vl.prefix_end) {
267 fg = TB_BLACK | TB_BOLD;
268 }
269 tb_set_cell(cx + j, cy + i, vl.text[j], fg, TB_DEFAULT);
270 }
271 }
272
273 // Draw scrollbar
274 if (total_lines > ch) {
275 int thumb_height = std::max(1, (ch * ch) / total_lines);
276 int max_scroll = total_lines - ch;
277 double scroll_percent = (double)scroll_offset / (double)max_scroll;
278 int thumb_pos = (ch - thumb_height) * scroll_percent;
279
280 for (int i = 0; i < ch; ++i) {
281 uint32_t cell_char = SCROLLBAR_LINE;
282 uint16_t fg = TB_DEFAULT;
283 if (i >= thumb_pos && i < thumb_pos + thumb_height) {
284 cell_char = SCROLLBAR_THUMB;
285 fg = TB_WHITE;
286 }
287 tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT);
288 }
289 }
290}
291
292std::string get_breakpoint_name(SBBreakpoint bp) {
293 if (!bp.IsValid()) return "???";
294
295 std::string name = "???";
296 if (bp.GetNumLocations() > 0) {
297 SBBreakpointLocation loc = bp.GetLocationAtIndex(0);
298 SBAddress addr = loc.GetAddress();
299
300 SBFunction func = addr.GetFunction();
301 if (func.IsValid()) {
302 const char* n = func.GetName();
303 if (n) name = n;
304 } else {
305 SBSymbol sym = addr.GetSymbol();
306 if (sym.IsValid()) {
307 const char* n = sym.GetName();
308 if (n) name = n;
309 }
310 }
311
312 SBLineEntry line_entry = addr.GetLineEntry();
313 if (line_entry.IsValid()) {
314 std::string file_name;
315 SBFileSpec fs = line_entry.GetFileSpec();
316 if (fs.IsValid()) file_name = fs.GetFilename();
317
318 if (name == "???") {
319 if (!file_name.empty()) name = file_name + ":" + std::to_string(line_entry.GetLine());
320 } else {
321 if (!file_name.empty()) name += " (" + file_name + ":" + std::to_string(line_entry.GetLine()) + ")";
322 }
323 }
324 }
325 return name;
326}
327
328void draw_source_view(SBFrame &frame, int x, int y, int w, int h, SourceCache& cache, int scroll_offset) {
329 draw_box(x, y, w, h, "Source");
330
331 int cx = x + 1;
332 int cy = y + 1;
333 int ch = h - 2;
334 int cw = w - 2;
335
336 if (!frame.IsValid()) {
337 draw_text(cx, cy, TB_RED, TB_DEFAULT, "No frame selected.");
338 return;
339 }
340
341 SBLineEntry line_entry = frame.GetLineEntry();
342 if (!line_entry.IsValid()) {
343 draw_text(cx, cy, TB_RED, TB_DEFAULT, "No line entry info.");
344 return;
345 }
346
347 SBFileSpec file_spec = line_entry.GetFileSpec();
348 if (!file_spec.IsValid()) return;
349
350 // Construct full path
351 std::string fullpath;
352 if (file_spec.GetDirectory()) {
353 fullpath = std::string(file_spec.GetDirectory()) + "/" + file_spec.GetFilename();
354 } else {
355 fullpath = file_spec.GetFilename();
356 }
357
358 SBAddress addr = frame.GetPCAddress();
359 SBTarget target = frame.GetThread().GetProcess().GetTarget();
360
361 const std::vector<std::string>& lines = cache.get_lines(fullpath);
362 if (lines.empty() && !std::ifstream(fullpath).good()) {
363 draw_text(cx, cy, TB_RED | TB_BOLD, TB_DEFAULT, "Could not open source: " + fullpath);
364
365 SBFunction func = frame.GetFunction();
366 std::string func_name = func.IsValid() ? func.GetName() : "???";
367
368 char addr_buf[64];
369 snprintf(addr_buf, sizeof(addr_buf), "At address: 0x%lx", (unsigned long)addr.GetLoadAddress(target));
370
371 draw_text(cx, cy + 2, TB_WHITE, TB_DEFAULT, "Function: " + func_name);
372 draw_text(cx, cy + 3, TB_WHITE, TB_DEFAULT, addr_buf);
373 draw_text(cx, cy + 5, TB_YELLOW, TB_DEFAULT, "Press 'n' (Step Over) or 'o' (Step Out) to return to your code.");
374
375 // Disassembly fallback
376 SBInstructionList instructions = target.ReadInstructions(addr, (uint32_t)(ch - 8));
377 if (instructions.IsValid()) {
378 for (uint32_t i = 0; i < instructions.GetSize() && (int)i < ch - 8; ++i) {
379 SBInstruction insn = instructions.GetInstructionAtIndex(i);
380 std::string dis = insn.GetMnemonic(target);
381 dis += " ";
382 dis += insn.GetOperands(target);
383
384 uint16_t fg = (insn.GetAddress() == addr) ? TB_WHITE | TB_BOLD : TB_DEFAULT;
385 uint16_t bg = (insn.GetAddress() == addr) ? TB_BLUE : TB_DEFAULT;
386
387 char insn_addr_buf[32];
388 snprintf(insn_addr_buf, sizeof(insn_addr_buf), "0x%lx: ", (unsigned long)insn.GetAddress().GetLoadAddress(target));
389
390 draw_text(cx, cy + 7 + i, fg, bg, std::string(insn_addr_buf) + dis);
391 }
392 }
393 return;
394 }
395
396 // Get breakpoints for this file
397 std::vector<uint32_t> bp_lines;
398 uint32_t num_breakpoints = target.GetNumBreakpoints();
399 for (uint32_t i = 0; i < num_breakpoints; ++i) {
400 SBBreakpoint bp = target.GetBreakpointAtIndex(i);
401 uint32_t num_locs = bp.GetNumLocations();
402 for (uint32_t j = 0; j < num_locs; ++j) {
403 SBBreakpointLocation loc = bp.GetLocationAtIndex(j);
404 SBLineEntry le = loc.GetAddress().GetLineEntry();
405 if (le.IsValid()) {
406 SBFileSpec fs = le.GetFileSpec();
407 if (fs.IsValid()) {
408 std::string bp_path;
409 if (fs.GetDirectory()) {
410 bp_path = std::string(fs.GetDirectory()) + "/" + fs.GetFilename();
411 } else {
412 bp_path = fs.GetFilename();
413 }
414 if (bp_path == fullpath) {
415 bp_lines.push_back(le.GetLine());
416 }
417 }
418 }
419 }
420 }
421
422 int total_lines = (int)lines.size();
423 int current_line = line_entry.GetLine();
424 for (int i = 0; i < ch; ++i) {
425 int line_idx = scroll_offset + i + 1;
426 if (line_idx > total_lines) break;
427
428 std::string src = lines[line_idx - 1];
429 // Handle basic tab expansion (simple version)
430 std::string expanded;
431 for (char c : src) {
432 if (c == '\t') expanded += " ";
433 else expanded += c;
434 }
435 src = expanded;
436
437 bool is_current = (line_idx == current_line);
438 bool has_breakpoint = std::find(bp_lines.begin(), bp_lines.end(), (uint32_t)line_idx) != bp_lines.end();
439
440 char buf[32];
441 snprintf(buf, sizeof(buf), "%4d ", line_idx);
442 std::string num_str(buf);
443
444 uint16_t bg = is_current ? TB_BLUE : TB_DEFAULT;
445 uint16_t fg = is_current ? TB_WHITE | TB_BOLD : TB_DEFAULT;
446
447 // Draw breakpoint indicator
448 if (has_breakpoint) {
449 tb_set_cell(cx, cy + i, BREAKPOINT_CIRCLE, TB_RED | TB_BOLD, bg);
450 } else {
451 tb_set_cell(cx, cy + i, ' ', fg, bg);
452 }
453
454 draw_text(cx + 1, cy + i, fg, bg, num_str);
455
456 int src_max_len = cw - (int)num_str.length() - 1;
457 if ((int)src.length() > src_max_len) {
458 src = src.substr(0, src_max_len);
459 }
460 draw_text(cx + 1 + num_str.length(), cy + i, fg, bg, src);
461
462 if (is_current) {
463 for (int k = cx + 1 + num_str.length() + src.length(); k < cx + cw; ++k) {
464 tb_set_cell(k, cy + i, ' ', fg, bg);
465 }
466 }
467 }
468
469 // Draw scrollbar
470 if (total_lines > ch) {
471 int thumb_height = std::max(1, (ch * ch) / total_lines);
472 int max_scroll = total_lines - ch;
473 double scroll_percent = (double)scroll_offset / (double)max_scroll;
474 int thumb_pos = (ch - thumb_height) * scroll_percent;
475
476 for (int i = 0; i < ch; ++i) {
477 uint32_t cell_char = SCROLLBAR_LINE;
478 uint16_t fg = TB_DEFAULT;
479 if (i >= thumb_pos && i < thumb_pos + thumb_height) {
480 cell_char = SCROLLBAR_THUMB;
481 fg = TB_WHITE;
482 }
483 tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT);
484 }
485 }
486}
487
488void draw_breakpoints_view(SBTarget& target, int x, int y, int w, int h) {
489 draw_box(x, y, w, h, "Breakpoints");
490 int cx = x + 1;
491 int cy = y + 1;
492 int mh = h - 2;
493
494 if (!target.IsValid()) return;
495
496 int num_bps = target.GetNumBreakpoints();
497 for (int i = 0; i < num_bps && i < mh; ++i) {
498 SBBreakpoint bp = target.GetBreakpointAtIndex(i);
499 std::string name = get_breakpoint_name(bp);
500
501 char buf[128];
502 snprintf(buf, sizeof(buf), "%d: %s", bp.GetID(), name.c_str());
503 std::string line = buf;
504 draw_text(cx, cy + i, TB_DEFAULT, TB_DEFAULT, line);
505 }
506}
507
508void draw_watch_view(SBFrame& frame, int x, int y, int w, int h, const std::vector<std::string>& expressions, int scroll_offset) {
509 draw_box(x, y, w, h, "Watch");
510 int cx = x + 1;
511 int cy = y + 1;
512 int ch = h - 2;
513 int cw = w - 2;
514
515 if (expressions.empty()) {
516 draw_text(cx, cy, TB_DEFAULT, TB_DEFAULT, "No watch expressions.");
517 return;
518 }
519
520 std::vector<VarLine> lines;
521 for (const auto& expr : expressions) {
522 SBValue val = frame.EvaluateExpression(expr.c_str());
523 if (val.IsValid() && !val.GetError().Fail()) {
524 collect_variables_recursive(val, 0, lines, cw, expr);
525 } else {
526 VarLine vl;
527 vl.text = expr + " = (error)";
528 if (val.GetError().GetCString()) {
529 vl.text += ": " + std::string(val.GetError().GetCString());
530 }
531 if ((int)vl.text.length() > cw) vl.text = vl.text.substr(0, cw - 3) + "...";
532 vl.indent = 0;
533 vl.prefix_start = 0;
534 vl.prefix_end = 0;
535 lines.push_back(vl);
536 }
537 }
538
539 int total_lines = (int)lines.size();
540 int display_count = std::min(total_lines, ch);
541
542 for (int i = 0; i < display_count; ++i) {
543 int line_idx = scroll_offset + i;
544 if (line_idx < 0 || line_idx >= total_lines) continue;
545
546 const VarLine& vl = lines[line_idx];
547 for (int j = 0; j < (int)vl.text.length() && j < cw; ++j) {
548 uint16_t fg = TB_DEFAULT;
549 if (j >= vl.prefix_start && j < vl.prefix_end) {
550 fg = TB_BLACK | TB_BOLD;
551 }
552 tb_set_cell(cx + j, cy + i, vl.text[j], fg, TB_DEFAULT);
553 }
554 }
555
556 // Draw scrollbar
557 if (total_lines > ch) {
558 int thumb_height = std::max(1, (ch * ch) / total_lines);
559 int max_scroll = total_lines - ch;
560 double scroll_percent = (double)scroll_offset / (double)max_scroll;
561 int thumb_pos = (ch - thumb_height) * scroll_percent;
562
563 for (int i = 0; i < ch; ++i) {
564 uint32_t cell_char = SCROLLBAR_LINE;
565 uint16_t fg = TB_DEFAULT;
566 if (i >= thumb_pos && i < thumb_pos + thumb_height) {
567 cell_char = SCROLLBAR_THUMB;
568 fg = TB_WHITE;
569 }
570 tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT);
571 }
572 }
573}
574
575SBBreakpoint create_breakpoint(SBTarget& target, const std::string& input) {
576 SBBreakpoint bp;
577 size_t colon_pos = input.rfind(':');
578
579 if (colon_pos != std::string::npos && colon_pos < input.length() - 1) {
580 std::string line_str = input.substr(colon_pos + 1);
581 bool is_number = !line_str.empty() && std::all_of(line_str.begin(), line_str.end(), ::isdigit);
582
583 if (is_number) {
584 std::string filename = input.substr(0, colon_pos);
585 uint32_t line_no = (uint32_t)std::stoi(line_str);
586 bp = target.BreakpointCreateByLocation(filename.c_str(), line_no);
587 if (bp.IsValid() && bp.GetNumLocations() > 0) return bp;
588 }
589 }
590
591 return target.BreakpointCreateByName(input.c_str());
592}
593
594void draw_log_view(int x, int y, int w, int h, const std::vector<std::string>& log_buffer, InputMode mode, const std::string& input_buffer, int scroll_offset) {
595 bool input_mode = (mode == INPUT_MODE_BREAKPOINT || mode == INPUT_MODE_VARIABLE || mode == INPUT_MODE_WATCH);
596 std::string title = input_mode ? "Input (Esc to Cancel)" : "Logs";
597 if (!input_mode && scroll_offset > 0) {
598 title += " (Scrolled up: " + std::to_string(scroll_offset) + ")";
599 }
600 draw_box(x, y, w, h, title);
601
602 int cx = x + 1;
603 int cy = y + 1;
604 int ch = h - 2;
605 int cw = w - 2;
606
607 if (input_mode) {
608 std::string prompt;
609 if (mode == INPUT_MODE_BREAKPOINT) prompt = "Add Breakpoint: ";
610 else if (mode == INPUT_MODE_VARIABLE) prompt = "Print Variable: ";
611 else if (mode == INPUT_MODE_WATCH) prompt = "Watch Variable: ";
612
613 prompt += input_buffer;
614 if ((int)prompt.length() > cw) prompt = prompt.substr(prompt.length() - cw);
615 draw_text(cx, cy, TB_WHITE | TB_BOLD, TB_DEFAULT, prompt);
616 tb_set_cell(cx + prompt.length(), cy, '_', TB_WHITE | TB_BOLD | TB_REVERSE, TB_DEFAULT);
617 } else {
618 int total_logs = log_buffer.size();
619 int display_count = std::min(total_logs, ch);
620
621 for (int i = 0; i < display_count; ++i) {
622 int log_idx = total_logs - display_count - scroll_offset + i;
623 if (log_idx < 0 || log_idx >= total_logs) continue;
624
625 const std::string& msg = log_buffer[log_idx];
626 std::string disp = msg;
627 if ((int)disp.length() > cw) disp = disp.substr(0, cw);
628 draw_text(cx, cy + i, TB_DEFAULT, TB_DEFAULT, disp);
629 }
630
631 // Draw scrollbar
632 if (total_logs > ch) {
633 int thumb_height = std::max(1, (ch * ch) / total_logs);
634 int max_scroll = total_logs - ch;
635 double scroll_percent = (double)scroll_offset / (double)max_scroll;
636 int thumb_pos = (ch - thumb_height) * (1.0 - scroll_percent);
637
638 for (int i = 0; i < ch; ++i) {
639 uint32_t cell_char = SCROLLBAR_LINE;
640 uint16_t fg = TB_DEFAULT;
641 if (i >= thumb_pos && i < thumb_pos + thumb_height) {
642 cell_char = SCROLLBAR_THUMB;
643 fg = TB_WHITE;
644 }
645 tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT);
646 }
647 }
648 }
649}
650
651void draw_help_view(int width, int height) {
652 int w = 60;
653 int h = 18;
654 int x = (width - w) / 2;
655 int y = (height - h) / 2;
656
657 // Fill background
658 for (int i = 0; i < h; ++i) {
659 for (int j = 0; j < w; ++j) {
660 tb_set_cell(x + j, y + i, ' ', TB_DEFAULT, TB_DEFAULT);
661 }
662 }
663
664 draw_box(x, y, w, h, "Help / Keybinds");
665
666 int ty = y + 2;
667 int tx = x + 3;
668
669 auto d = [&](const std::string& key, const std::string& desc) {
670 draw_text(tx, ty, TB_YELLOW | TB_BOLD, TB_DEFAULT, key);
671 draw_text(tx + 13, ty, TB_DEFAULT, TB_DEFAULT, desc);
672 ty++;
673 };
674
675 d("r", "Run / Launch program");
676 d("b", "Add breakpoint (file:line or func)");
677 d("p", "Print variable / Evaluate expr");
678 d("w", "Add watch expression");
679 d("n", "Step Over (next line)");
680 d("s", "Step Into (into function)");
681 d("o", "Step Out (to caller)");
682 d("c", "Continue execution");
683 d("h", "Toggle help window");
684 d("q", "Quit debugger");
685 d("Esc", "Cancel input / Close help");
686 d("Ctrl+Arrows", "Resize layout");
687 d("Mouse Wheel", "Scroll active window");
688
689 draw_text(x + (w - 24) / 2, y + h - 2, TB_BLACK, TB_WHITE, " Press any key to close ");
690}
691
692void draw_status_bar(SBProcess &process, InputMode mode, int width, int height) {
693 std::string state_str = "Status: ";
694 if (!process.IsValid()) {
695 state_str += "Not Running";
696 } else {
697 StateType state = process.GetState();
698 if (state == eStateStopped) state_str += "Stopped";
699 else if (state == eStateRunning) state_str += "Running";
700 else if (state == eStateExited) state_str += "Exited";
701 else state_str += "Unknown";
702 }
703
704 state_str += (mode == INPUT_MODE_NORMAL)
705 ? " | r=Run, b=Add bp, p=Print, w=Watch, n=Step, s=Step In, o=Step Out, c=Cont, h=Help, q=Quit"
706 : (mode == INPUT_MODE_HELP ? " | Press any key to close help" : " | Enter=Confirm, Esc=Cancel");
707
708 for (int x = 0; x < width; ++x) {
709 tb_set_cell(x, height - 1, ' ', TB_BLACK, TB_WHITE);
710 }
711
712 draw_text(1, height - 1, TB_BLACK, TB_WHITE, state_str);
713}
714
715SBProcess launch_target(SBTarget& target, const std::string& target_path, const std::vector<std::string>& debuggee_args, const std::vector<std::string>& target_env, std::vector<std::string>& log_buffer) {
716 if (target.GetNumBreakpoints() == 0) {
717 SBBreakpoint bp = target.BreakpointCreateByName("main");
718 if (bp.IsValid() && bp.GetNumLocations() > 0) {
719 log_msg(log_buffer, "No breakpoints. Added breakpoint at 'main'");
720 } else {
721 log_msg(log_buffer, "No breakpoints. Failed to add breakpoint at 'main'");
722 }
723 }
724 log_msg(log_buffer, "Launching...");
725
726 std::vector<const char*> launch_argv;
727 launch_argv.push_back(target_path.c_str());
728 for (const auto& arg : debuggee_args) {
729 launch_argv.push_back(arg.c_str());
730 }
731 launch_argv.push_back(nullptr);
732
733 std::vector<const char*> launch_env;
734 for (const auto& env : target_env) {
735 launch_env.push_back(env.c_str());
736 }
737 launch_env.push_back(nullptr);
738
739 SBLaunchInfo launch_info(launch_argv.data());
740 launch_info.SetEnvironmentEntries(launch_env.data(), true);
741 launch_info.SetWorkingDirectory(".");
742
743 SBError error;
744 SBProcess process = target.Launch(launch_info, error);
745
746 if (!process.IsValid() || error.Fail()) {
747 std::string err_msg = "Launch failed";
748 if (error.GetCString()) {
749 err_msg += ": ";
750 err_msg += error.GetCString();
751 }
752 log_msg(log_buffer, err_msg);
753 } else {
754 log_msg(log_buffer, "Launched");
755 }
756 return process;
757}
758
759int main(int argc, char** argv) {
760 std::vector<std::string> target_env;
761 std::vector<std::string> startup_breakpoints;
762 std::vector<std::string> debuggee_args;
763 std::string target_path;
764 bool auto_run = false;
765
766 for (int i = 1; i < argc; ++i) {
767 std::string arg = argv[i];
768 if (arg == "-e" && i + 1 < argc) {
769 target_env.push_back(argv[++i]);
770 } else if (arg == "-b" && i + 1 < argc) {
771 startup_breakpoints.push_back(argv[++i]);
772 } else if (arg == "-run") {
773 auto_run = true;
774 } else if (arg == "-h" || arg == "--help") {
775 std::cout << "Usage: " << argv[0] << " [options] <target_executable> [-- arg1 arg2 ...]\n\n"
776 << "Options:\n"
777 << " -e KEY=VALUE Set environment variable\n"
778 << " -b BREAKPOINT Set startup breakpoint (name or file:line)\n"
779 << " -run Automatically run the target on startup\n"
780 << " -h, --help Show this help message\n";
781 return 0;
782 } else if (arg == "--") {
783 for (int j = i + 1; j < argc; ++j) {
784 debuggee_args.push_back(argv[j]);
785 }
786 break;
787 } else if (target_path.empty()) {
788 target_path = arg;
789 } else {
790 debuggee_args.push_back(arg);
791 }
792 }
793
794 if (target_path.empty()) {
795 std::cerr << "Usage: " << argv[0] << " [-e KEY=VALUE] [-b BREAKPOINT] [-run] ... <target_executable> [-- arg1 arg2 ...]\n";
796 return 1;
797 }
798
799 int log_fd = open("tdbg.log", O_WRONLY | O_CREAT | O_TRUNC, 0644);
800 if (log_fd != -1) {
801 dup2(log_fd, STDERR_FILENO);
802 close(log_fd);
803 }
804
805 LLDBGuard lldb_guard;
806 SBDebugger debugger = SBDebugger::Create();
807 debugger.SetAsync(false);
808
809 SBTarget target = debugger.CreateTarget(target_path.c_str());
810 if (!target.IsValid()) {
811 std::cerr << "Failed to create target for " << target_path << "\n";
812 return 1;
813 }
814
815 std::vector<std::string> log_buffer;
816 for (const auto& bp_spec : startup_breakpoints) {
817 SBBreakpoint bp = create_breakpoint(target, bp_spec);
818 if (bp.IsValid() && bp.GetNumLocations() > 0) {
819 log_msg(log_buffer, "Set startup breakpoint: " + bp_spec);
820 } else {
821 log_msg(log_buffer, "Failed to set startup breakpoint: " + bp_spec);
822 }
823 }
824
825 SBProcess process;
826 if (auto_run) {
827 process = launch_target(target, target_path, debuggee_args, target_env, log_buffer);
828 }
829
830 SBThread thread;
831 TermboxGuard tb_guard;
832
833 bool running = true;
834 InputMode mode = INPUT_MODE_NORMAL;
835 std::string input_buffer;
836 std::string current_source_filename;
837 std::vector<std::string> watch_expressions;
838 int log_scroll_offset = 0;
839 int locals_scroll_offset = 0;
840 int watch_scroll_offset = 0;
841 int source_scroll_offset = 0;
842 uint64_t last_pc = 0;
843 SourceCache source_cache;
844 log_buffer.push_back("Debugger started. Press 'b' to add breakpoint, 'r' to run.");
845
846 tb_set_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE);
847
848 while (running) {
849 tb_clear();
850
851 int width = tb_width();
852 int height = tb_height();
853 int main_window_height = height - layout_config.log_height - layout_config.status_height;
854 int split_x = width - layout_config.sidebar_width;
855 int locals_window_height = main_window_height - layout_config.watch_height;
856
857 SBFrame frame;
858 if (process.IsValid() && process.GetState() != eStateExited) {
859 thread = process.GetSelectedThread();
860 if (thread.IsValid()) {
861 frame = thread.GetSelectedFrame();
862 if (frame.IsValid()) {
863 uint64_t current_pc = frame.GetPC();
864 if (current_pc != last_pc) {
865 last_pc = current_pc;
866 SBLineEntry le = frame.GetLineEntry();
867 if (le.IsValid()) {
868 std::string fullpath;
869 if (le.GetFileSpec().GetDirectory()) {
870 fullpath = std::string(le.GetFileSpec().GetDirectory()) + "/" + le.GetFileSpec().GetFilename();
871 current_source_filename = le.GetFileSpec().GetFilename();
872 } else {
873 fullpath = le.GetFileSpec().GetFilename();
874 current_source_filename = fullpath;
875 }
876 const std::vector<std::string>& lines = source_cache.get_lines(fullpath);
877 int total_lines = (int)lines.size();
878 int ch = main_window_height - 2;
879 source_scroll_offset = std::max(0, (int)le.GetLine() - ch / 2 - 1);
880 if (source_scroll_offset + ch > total_lines) {
881 source_scroll_offset = std::max(0, total_lines - ch);
882 }
883 }
884 }
885 }
886 }
887 }
888
889 draw_source_view(frame, 0, 0, split_x, main_window_height, source_cache, source_scroll_offset);
890 draw_variables_view(frame, split_x, 0, layout_config.sidebar_width, locals_window_height, locals_scroll_offset);
891 draw_watch_view(frame, split_x, locals_window_height, layout_config.sidebar_width, layout_config.watch_height, watch_expressions, watch_scroll_offset);
892 draw_log_view(0, main_window_height, split_x, layout_config.log_height, log_buffer, mode, input_buffer, log_scroll_offset);
893 draw_breakpoints_view(target, split_x, main_window_height, layout_config.sidebar_width, layout_config.log_height);
894 draw_status_bar(process, mode, width, height);
895
896 if (mode == INPUT_MODE_HELP) {
897 draw_help_view(width, height);
898 }
899
900 tb_present();
901
902 struct tb_event ev;
903 if (tb_poll_event(&ev) == 0) {
904 if (ev.type == TB_EVENT_KEY) {
905 if (mode == INPUT_MODE_NORMAL) {
906 if (ev.ch == 'q') {
907 running = false;
908 } else if (ev.ch == 'r') {
909 if (!process.IsValid() || process.GetState() == eStateExited) {
910 process = launch_target(target, target_path, debuggee_args, target_env, log_buffer);
911 } else {
912 log_msg(log_buffer, "Already running");
913 }
914 } else if (ev.ch == 'b') {
915 mode = INPUT_MODE_BREAKPOINT;
916 if (!current_source_filename.empty()) {
917 input_buffer = current_source_filename + ":";
918 } else {
919 input_buffer.clear();
920 }
921 } else if (ev.ch == 'p') {
922 mode = INPUT_MODE_VARIABLE;
923 input_buffer.clear();
924 } else if (ev.ch == 'w') {
925 mode = INPUT_MODE_WATCH;
926 input_buffer.clear();
927 } else if (ev.ch == 'h') {
928 mode = INPUT_MODE_HELP;
929 } else {
930 if (process.IsValid() && process.GetState() == eStateStopped) {
931 switch (ev.ch) {
932 case 'n': if (thread.IsValid()) thread.StepOver(); break;
933 case 's': if (thread.IsValid()) thread.StepInto(); break;
934 case 'o': if (thread.IsValid()) thread.StepOut(); break;
935 case 'c': process.Continue(); break;
936 }
937 }
938
939 if (ev.key == TB_KEY_ARROW_LEFT && (ev.mod & TB_MOD_CTRL)) {
940 layout_config.sidebar_width = std::min(width - 20, layout_config.sidebar_width + 2);
941 } else if (ev.key == TB_KEY_ARROW_RIGHT && (ev.mod & TB_MOD_CTRL)) {
942 layout_config.sidebar_width = std::max(20, layout_config.sidebar_width - 2);
943 } else if (ev.key == TB_KEY_ARROW_UP && (ev.mod & TB_MOD_CTRL)) {
944 layout_config.log_height = std::min(height - 10, layout_config.log_height + 1);
945 } else if (ev.key == TB_KEY_ARROW_DOWN && (ev.mod & TB_MOD_CTRL)) {
946 layout_config.log_height = std::max(5, layout_config.log_height - 1);
947 }
948 }
949 } else if (mode == INPUT_MODE_BREAKPOINT || mode == INPUT_MODE_VARIABLE || mode == INPUT_MODE_WATCH) {
950 if (ev.key == TB_KEY_ESC) {
951 mode = INPUT_MODE_NORMAL;
952 input_buffer.clear();
953 } else if (ev.key == TB_KEY_ENTER) {
954 if (!input_buffer.empty()) {
955 if (mode == INPUT_MODE_BREAKPOINT) {
956 SBBreakpoint bp = create_breakpoint(target, input_buffer);
957 if (bp.IsValid() && bp.GetNumLocations() > 0) {
958 log_msg(log_buffer, "Breakpoint added: " + input_buffer);
959 } else {
960 log_msg(log_buffer, "Failed/Invalid breakpoint: " + input_buffer);
961 }
962 } else if (mode == INPUT_MODE_VARIABLE) {
963 if (!frame.IsValid()) {
964 log_msg(log_buffer, "Error: No stack frame available to evaluate '" + input_buffer + "'");
965 } else {
966 SBValue val = frame.EvaluateExpression(input_buffer.c_str());
967 if (val.IsValid() && !val.GetError().Fail()) {
968 format_variable_log(val, log_buffer, 0, input_buffer);
969 } else {
970 std::string err = "Error evaluating '" + input_buffer + "'";
971 if (val.GetError().GetCString()) {
972 err += ": ";
973 err += val.GetError().GetCString();
974 }
975 log_msg(log_buffer, err);
976 }
977 }
978 } else if (mode == INPUT_MODE_WATCH) {
979 watch_expressions.push_back(input_buffer);
980 log_msg(log_buffer, "Added to watch: " + input_buffer);
981 }
982 }
983 mode = INPUT_MODE_NORMAL;
984 input_buffer.clear();
985 } else if (ev.key == TB_KEY_BACKSPACE || ev.key == TB_KEY_BACKSPACE2) {
986 if (!input_buffer.empty()) input_buffer.pop_back();
987 } else if (ev.ch != 0) {
988 input_buffer += (char)ev.ch;
989 }
990 } else if (mode == INPUT_MODE_HELP) {
991 mode = INPUT_MODE_NORMAL;
992 }
993 } else if (ev.type == TB_EVENT_MOUSE) {
994 int main_window_height = tb_height() - layout_config.log_height - layout_config.status_height;
995
996 // Log window scrolling
997 int log_start_y = main_window_height;
998 int log_end_y = tb_height() - layout_config.status_height;
999 if (ev.x < split_x && ev.y >= log_start_y && ev.y < log_end_y) {
1000 if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
1001 int max_scroll = std::max(0, (int)log_buffer.size() - (layout_config.log_height - 2));
1002 if (log_scroll_offset < max_scroll) {
1003 log_scroll_offset++;
1004 }
1005 } else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
1006 if (log_scroll_offset > 0) {
1007 log_scroll_offset--;
1008 }
1009 }
1010 }
1011
1012 // Source window scrolling
1013 if (ev.x < split_x && ev.y < main_window_height) {
1014 SBLineEntry le = frame.GetLineEntry();
1015 if (le.IsValid()) {
1016 std::string fullpath;
1017 if (le.GetFileSpec().GetDirectory()) {
1018 fullpath = std::string(le.GetFileSpec().GetDirectory()) + "/" + le.GetFileSpec().GetFilename();
1019 } else {
1020 fullpath = le.GetFileSpec().GetFilename();
1021 }
1022 const std::vector<std::string>& lines = source_cache.get_lines(fullpath);
1023 int total_lines = (int)lines.size();
1024 int ch = main_window_height - 2;
1025 int max_scroll = std::max(0, total_lines - ch);
1026
1027 if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
1028 if (source_scroll_offset > 0) {
1029 source_scroll_offset--;
1030 }
1031 } else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
1032 if (source_scroll_offset < max_scroll) {
1033 source_scroll_offset++;
1034 }
1035 }
1036 }
1037 }
1038
1039 // Locals window scrolling
1040 int split_x = tb_width() - layout_config.sidebar_width;
1041 int locals_window_height = main_window_height - layout_config.watch_height;
1042 if (ev.x >= split_x && ev.y < locals_window_height) {
1043 std::vector<VarLine> lines;
1044 if (frame.IsValid()) {
1045 SBValueList vars = frame.GetVariables(true, true, false, true);
1046 for (uint32_t i = 0; i < vars.GetSize(); ++i) {
1047 collect_variables_recursive(vars.GetValueAtIndex(i), 0, lines, layout_config.sidebar_width - 2);
1048 }
1049 }
1050 int max_scroll = std::max(0, (int)lines.size() - (locals_window_height - 2));
1051
1052 if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
1053 if (locals_scroll_offset > 0) {
1054 locals_scroll_offset--;
1055 }
1056 } else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
1057 if (locals_scroll_offset < max_scroll) {
1058 locals_scroll_offset++;
1059 }
1060 }
1061 }
1062
1063 // Watch window scrolling
1064 if (ev.x >= split_x && ev.y >= locals_window_height && ev.y < main_window_height) {
1065 std::vector<VarLine> lines;
1066 for (const auto& expr : watch_expressions) {
1067 SBValue val = frame.EvaluateExpression(expr.c_str());
1068 if (val.IsValid() && !val.GetError().Fail()) {
1069 collect_variables_recursive(val, 0, lines, layout_config.sidebar_width - 2, expr);
1070 } else {
1071 VarLine vl;
1072 vl.text = expr + " = (error)";
1073 lines.push_back(vl);
1074 }
1075 }
1076 int max_scroll = std::max(0, (int)lines.size() - (layout_config.watch_height - 2));
1077
1078 if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
1079 if (watch_scroll_offset > 0) {
1080 watch_scroll_offset--;
1081 }
1082 } else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
1083 if (watch_scroll_offset < max_scroll) {
1084 watch_scroll_offset++;
1085 }
1086 }
1087 }
1088 }
1089 }
1090 }
1091
1092 return 0;
1093}