#include "Debuggers.h" #define METHOD_NAME UPP_METHOD_NAME("Gdb") void Gdb::DebugBar(Bar& bar) { using namespace PdbKeys; bar.Add("Stop debugging", DbgImg::StopDebug(), THISBACK(Stop)) .Key(K_SHIFT_F5); bar.Separator(); bool b = !IdeIsDebugLock(); bar.Add(b, AK_STEPINTO, DbgImg::StepInto(), THISBACK1(Step, disas.HasFocus() ? "stepi" : "step")); bar.Add(b, AK_STEPOVER, DbgImg::StepOver(), THISBACK1(Step, disas.HasFocus() ? "nexti" : "next")); bar.Add(b, AK_STEPOUT, DbgImg::StepOut(), THISBACK1(Step, "finish")); bar.Add(b, AK_RUNTO, DbgImg::RunTo(), THISBACK(DoRunTo)); bar.Add(b, AK_RUN, DbgImg::Run(), THISBACK(Run)); bar.Add(!b && pid, AK_BREAK, DbgImg::Stop(), THISBACK(BreakRunning)); bar.MenuSeparator(); bar.Add(b, AK_AUTOS, THISBACK1(SetTab, 0)); bar.Add(b, AK_LOCALS, THISBACK1(SetTab, 1)); bar.Add(b, AK_THISS, THISBACK1(SetTab, 2)); bar.Add(b, AK_WATCHES, THISBACK1(SetTab, 3)); bar.Add(b, AK_CLEARWATCHES, THISBACK(ClearWatches)); bar.Add(b, AK_ADDWATCH, THISBACK(QuickWatch)); bar.Add(b, AK_CPU, THISBACK1(SetTab, 4)); bar.MenuSeparator(); bar.Add(b, "Copy backtrace", THISBACK(CopyStack)); bar.Add(b, "Copy backtrace of all threads", THISBACK(CopyStackAll)); bar.Add(b, "Copy dissassembly", THISBACK(CopyDisas)); } String FormatFrame(const char *s) { if(*s++ != '#') return Null; while(IsDigit(*s)) s++; while(*s == ' ') s++; if(s[0] == '0' && ToUpper(s[1]) == 'X') { s += 2; while(IsXDigit(*s)) s++; while(*s == ' ') s++; if(s[0] != 'i' && s[1] != 'n') return Null; s += 2; while(*s == ' ') s++; } if(!IsAlpha(*s)) return Null; const char *w = strchr(s, '\r'); if(w) return String(s, w); w = strchr(s, '\n'); if(w) return String(s, w); return s; } void Gdb::CopyStack() { if(IdeIsDebugLock()) return; DropFrames(); String s; for(int i = 0; i < frame.GetCount(); i++) s << frame.GetValue(i) << "\n"; WriteClipboardText(s); } void Gdb::CopyStackAll() { String s = FastCmd("info threads"); StringStream ss(s); String r; while(!ss.IsEof()) { String s = ss.GetLine(); CParser p(s); try { bool active = p.Char('*'); if(p.IsNumber()) { int id = p.ReadInt(); r << "----------------------------------\r\n" << "Thread: " << id << "\r\n\r\n"; FastCmd(Sprintf("thread %d", id)); int i = 0; int q = ~frame; frame.Clear(); for(;;) { String s = FormatFrame(FastCmd("frame " + AsString(i++))); if(IsNull(s)) break; r << s << "\r\n"; } r << "\r\n"; } } catch(CParser::Error) {} } FastCmd("thread " + ~~threads); WriteClipboardText(r); } void Gdb::CopyDisas() { if(IdeIsDebugLock()) return; disas.WriteClipboard(); } int CharFilterReSlash(int c) { return c == '\\' ? '/' : c; } String Bpoint(Host& host, const String& file, int line) { return String().Cat() << Filter(host.GetHostPath(NormalizePath(file)), CharFilterReSlash) << ":" << line + 1; } bool Gdb::TryBreak(const char *text) { return FindTag(FastCmd(text), "Breakpoint"); } bool Gdb::SetBreakpoint(const String& filename, int line, const String& bp) { String bi = Bpoint(*host, filename, line); String command; if(bp.IsEmpty()) command = "clear " + bi; else if(bp[0]==0xe || bp == "1") command = "b " + bi; else command = "b " + bi + " if " + bp; return !FastCmd(command).IsEmpty(); } void Gdb::SetDisas(const String& text) { disas.Clear(); StringStream ss(text); while(!ss.IsEof()) { String ln = ss.GetLine(); const char *s = ln; while(*s && !IsDigit(*s)) s++; adr_t adr = 0; String code, args; if(s[0] == '0' && ToLower(s[1]) == 'x') adr = (adr_t)ScanInt64(s + 2, NULL, 16); int q = ln.Find(">:"); if(q >= 0) { s = ~ln + q + 2; while(IsSpace(*s)) s++; while(*s && !IsSpace(*s)) code.Cat(*s++); while(IsSpace(*s)) s++; args = s; q = args.Find("0x"); if(q >= 0) disas.AddT(ScanInt(~args + q + 2, NULL, 16)); disas.Add(adr, code, args); } } } void Gdb::SyncDisas(bool fr) { if(!disas.IsVisible()) return; if(!disas.InRange(addr)) SetDisas(FastCmd("disas")); disas.SetCursor(addr); disas.SetIp(addr, fr ? DbgImg::FrameLinePtr() : DbgImg::IpLinePtr()); } bool ParsePos(const String& s, String& fn, int& line, adr_t & adr) { const char *q = FindTag(s, "\x1a\x1a"); if(!q) return false; q += 2; Vector p = Split(q + 2, ':'); p.SetCount(5); fn = String(q, q + 2) + p[0]; line = atoi(p[1]); try { CParser pa(p[4]); pa.Char2('0', 'x'); if(pa.IsNumber(16)) adr = (adr_t)pa.ReadNumber64(16); } catch(CParser::Error) {} return true; } void Gdb::CheckEnd(const char *s) { if(!dbg) { Stop(); return; } if(FindTag(s, "Program exited normally.")) { Stop(); return; } const char *q = FindTag(s, "Program exited with code "); if(q) { PutConsole(q); Stop(); return; } } String Gdb::Cmdp(const char *cmdline, bool fr) { expression_cache.Clear(); IdeHidePtr(); String s = Cmd(cmdline); if(ParsePos(s, file, line, addr)) { IdeSetDebugPos(GetLocalPath(file), line - 1, fr ? DbgImg::FrameLinePtr() : DbgImg::IpLinePtr(), 0); IdeSetDebugPos(GetLocalPath(file), line - 1, disas.HasFocus() ? fr ? DbgImg::FrameLinePtr() : DbgImg::IpLinePtr() : Image(), 1); SyncDisas(fr); autoline.Clear(); for(int i = -4; i <= 4; i++) autoline << ' ' << IdeGetLine(line + i); } else { file = Null; try { CParser pa(s); pa.Char2('0', 'x'); if(pa.IsNumber(16)) addr = (adr_t)pa.ReadNumber64(16); } catch(CParser::Error) {} SyncDisas(fr); } frame.Clear(); frame.Add(0, FormatFrame(FastCmd("frame"))); frame <<= 0; threads.Clear(); s = FastCmd("info threads"); StringStream ss(s); int active_thread = -1; while(!ss.IsEof()) { String s = ss.GetLine(); CParser p(s); try { bool active = p.Char('*'); if(p.IsNumber()) { int id = p.ReadInt(); threads.Add(id, String().Cat() << "Thread " << id); if(active) active_thread = id; } threads.GoBegin(); } catch(CParser::Error) {} } if(active_thread >= 0) threads <<= active_thread; if(threads.GetCount() == 0) Stop(); Data(); return s; } String Gdb::DoRun() { if(firstrun) { firstrun = false; Cmd("start"); String s = Cmd("info inferior"); int q = s.FindAfter("process"); pid = atoi(~s + q); IdeSetBar(); } return Cmdp("continue"); } bool Gdb::RunTo() { if(IdeIsDebugLock()) return false; String bi; bool df = disas.HasFocus(); if(df) { if(!disas.GetCursor()) return false; bi = Sprintf("*0x%X", disas.GetCursor()); } else bi = Bpoint(*host, IdeGetFileName(), IdeGetFileLine()); if(!TryBreak("b " + bi)) { Exclamation("No code at chosen location!"); return false; } String e = DoRun(); FastCmd("clear " + bi); if(df) disas.SetFocus(); CheckEnd(e); IdeActivateBottom(); return true; } void Gdb::BreakRunning() { #ifdef PLATFORM_WIN32 HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if(!h) { auto error = t_("Failed to open debugge process."); Loge() << METHOD_NAME << error; ErrorOK(error); return; } DebugBreakProcess(h); CloseHandle(h); #endif #ifdef PLATFORM_POSIX if (kill(pid, SIGINT) == -1) { auto error = t_("Failed to send SIGINT signal to debugge process."); Loge() << METHOD_NAME << error; ErrorOK(error); return; } #endif } void Gdb::Run() { if(IdeIsDebugLock()) return; String s = DoRun(); CheckEnd(s); IdeActivateBottom(); } void Gdb::Step(const char *cmd) { if(IdeIsDebugLock()) return; bool b = disas.HasFocus(); String s = Cmdp(cmd); if(b) disas.SetFocus(); CheckEnd(s); IdeActivateBottom(); } void Gdb::DisasCursor() { if(!disas.HasFocus()) return; int line; String file; adr_t addr; if(ParsePos(FastCmd(Sprintf("info line *0x%X", disas.GetCursor())), file, line, addr)) IdeSetDebugPos(file, line - 1, DbgImg::DisasPtr(), 1); disas.SetFocus(); } void Gdb::DisasFocus() { // if(!disas.HasFocus()) // IdeSetDebugPos(file, 0, Null, 1); } void Gdb::DropFrames() { int i = 0; frame.Clear(); while(i <= max_stack_trace_size) { auto s = ObtainFrame(i); if(IsNull(s)) { break; } if (i == max_stack_trace_size) { auto msg = Sprintf( "More entries.. (Only first %d elements had been printed)", max_stack_trace_size); frame.Add(i, msg); break; } frame.Add(i++, s); } if (frame_idx == -1 && frame.GetCount() >= 0) { frame_idx = 0; } RestoreFramePos(); } String Gdb::ObtainFrame(int frame_idx) { return FormatFrame(FastCmd(Sprintf("frame %d", frame_idx))); } void Gdb::SwitchFrame() { auto i = static_cast(~frame); if (i == max_stack_trace_size) { RestoreFramePos(); return; } frame_idx = i; Cmdp(Sprintf("frame %d", i), i); } void Gdb::SwitchThread() { frame_idx = -1; int i = static_cast(~threads); Cmdp(Sprintf("thread %d", i), i); } bool Gdb::Key(dword key, int count) { if(key >= 32 && key < 65535 && tab.Get() == 2) { watches.DoInsertAfter(); Ctrl* f = GetFocusCtrl(); if(f && watches.HasChildDeep(f)) f->Key(key, count); return true; } return Ctrl::Key(key, count); } void Gdb::RestoreFramePos() { if (frame_idx >= 0 && frame_idx < frame.GetCount()) { frame <<= frame_idx; } } bool Gdb::Create(One&& _host, const String& exefile, const String& cmdline, bool console) { host = pick(_host); if (!CreateDbg(host, exefile, console)) { ErrorOK("Error while invoking gdb!"); return false; } IdeSetBottom(*this); IdeSetRight(disas); disas.WhenCursor = THISBACK(DisasCursor); disas.WhenFocus = THISBACK(DisasFocus); frame.WhenDrop = THISBACK(DropFrames); frame <<= THISBACK(SwitchFrame); threads <<= THISBACK(SwitchThread); watches.WhenAcceptEdit = THISBACK(Data); tab <<= THISBACK(Data); Cmd("set prompt " GDB_PROMPT); Cmd("set disassembly-flavor intel"); Cmd("set exec-done-display off"); Cmd("set annotate 1"); Cmd("set height 0"); Cmd("set width 0"); Cmd("set confirm off"); Cmd("set print asm-demangle"); Cmd("set print static-members off"); Cmd("set print vtbl off"); Cmd("set print repeat 0"); Cmd("set print null-stop"); #ifdef PLATFORM_WIN32 Cmd("set new-console on"); #endif if(!IsNull(cmdline)) Cmd("set args " + cmdline); firstrun = true; return true; } bool Gdb::CreateDbg(One& host, const String& exeFile, bool console) { const auto& hostTools = host->GetTools(); dbg = host->StartProcess(GdbCommand(console) + hostTools.NormalizeExecutablePath(exeFile)); return static_cast(dbg); } Gdb::~Gdb() { IdeRemoveBottom(*this); IdeRemoveRight(disas); KillDebugTTY(); } void Gdb::Periodic() { if(TTYQuit()) Stop(); } Gdb::Gdb() : frame_idx(-1) , max_stack_trace_size(200) { locals.NoHeader(); locals.AddColumn("", 1); locals.AddColumn("", 6); locals.EvenRowColor(); locals.WhenSel = THISBACK1(SetTree, &locals); watches.NoHeader(); watches.AddColumn("", 1).Edit(watchedit); watches.AddColumn("", 6); watches.Inserting().Removing(); watches.EvenRowColor(); watches.WhenSel = THISBACK1(SetTree, &watches); autos.NoHeader(); autos.AddColumn("", 1); autos.AddColumn("", 6); autos.EvenRowColor(); autos.WhenSel = THISBACK1(SetTree, &autos); self.NoHeader(); self.AddColumn("", 1); self.AddColumn("", 6); self.EvenRowColor(); self.WhenSel = THISBACK1(SetTree, &self); cpu.Columns(3); cpu.ItemHeight(Courier(Ctrl::HorzLayoutZoom(12)).GetCy()); pane.Add(tab.SizePos()); tab.Add(autos.SizePos(), "Autos"); tab.Add(locals.SizePos(), "Locals"); tab.Add(watches.SizePos(), "Watches"); tab.Add(self.SizePos(), "this"); tab.Add(cpu.SizePos(), "CPU"); pane.Add(threads.LeftPosZ(300, 100).TopPos(2)); pane.Add(frame.HSizePosZ(404, 0).TopPos(2)); split.Horz(pane, tree.SizePos()); split.SetPos(8000); Add(split); tree.WhenOpen = THISBACK(TreeExpand); frame.Ctrl::Add(dlock.SizePos()); dlock = " Running.."; dlock.SetFrame(BlackFrame()); dlock.SetInk(Red); dlock.NoTransparent(); dlock.Hide(); CtrlLayoutOKCancel(quickwatch, "Watch"); quickwatch.WhenClose = quickwatch.Breaker(IDCANCEL); quickwatch.value.SetReadOnly(); quickwatch.value.SetFont(CourierZ(11)); quickwatch.Sizeable().Zoomable(); quickwatch.SetRect(0, 150, 300, 400); Transparent(); periodic.Set(-50, THISBACK(Periodic)); } One GdbCreate(One&& host, const String& exefile, const String& cmdline, bool console) { One dbg = MakeOne(); if(!dbg->Create(pick(host), exefile, cmdline, console)) { return nullptr; } return dbg; }