diff --git a/uppsrc/ide/Debuggers/Gdb_MI2.cpp b/uppsrc/ide/Debuggers/Gdb_MI2.cpp index a987394ea..0029f8986 100644 --- a/uppsrc/ide/Debuggers/Gdb_MI2.cpp +++ b/uppsrc/ide/Debuggers/Gdb_MI2.cpp @@ -1,1788 +1,1788 @@ -#include "Debuggers.h" -#include - -#include "PrettyPrinters.brc" - -#define LDUMP(x) DDUMP(x) -#define LLOG(x) DLOG(x) - -// we need those because we changed ArrayCtrl formats (including var types) -static VectorMap DataMap2(const ArrayCtrl& data) -{ - VectorMap m; - for(int i = 0; i < data.GetCount(); i++) - m.Add(data.Get(i, 0), data.Get(i, 2)); - return m; -} - -static void MarkChanged2(const VectorMap& m, ArrayCtrl& data) -{ - for(int i = 0; i < data.GetCount(); i++) - { - int q = m.Find(data.Get(i, 0)); - if(q >= 0 && m[q] != data.Get(i, 2)) - data.SetDisplay(i, 2, Single()); - else - data.SetDisplay(i, 2, StdDisplay()); - } -} - -void WatchEdit::HighlightLine(int line, Vector& h, int pos) -{ - Color cEven = Blend(SColorInfo, White, 220); - Color cOdd = Blend(SColorInfo, White, 128); - for(int i = 0; i < h.GetCount(); i++) - h[i].paper = (line % 2 ? cOdd : cEven); -} - -///////////////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC IDE INTERFACE -///////////////////////////////////////////////////////////////////////////////////////////////////////////// - -void Gdb_MI2::DebugBar(Bar& bar) -{ - bar.Add("Stop debugging", THISBACK(Stop)).Key(K_SHIFT_F5); - bar.Separator(); -// see note on Run() function -- crashes X on my machine, so removed by now -// bar.Add(!stopped, "Asynchronous break", THISBACK(AsyncBrk)); - bool b = !IdeIsDebugLock(); - bar.Add(b, "Step into", DbgImg::StepInto(), THISBACK1(Step, disas.HasFocus() ? "exec-step-instruction" : "exec-step")).Key(K_F11); - bar.Add(b, "Step over", DbgImg::StepOver(), THISBACK1(Step, disas.HasFocus() ? "exec-next-instruction" : "exec-next")).Key(K_F10); - bar.Add(b, "Step out", DbgImg::StepOut(), THISBACK1(Step, "exec-finish")).Key(K_SHIFT_F11); - bar.Add(b, "Run to", DbgImg::RunTo(), THISBACK(doRunTo)).Key(K_CTRL_F10); - bar.Add(b, "Run", DbgImg::Run(), THISBACK(Run)).Key(K_F5); - bar.Separator(); - bar.Add(b, "Quick watch", THISBACK(QuickWatch)).Key(K_CTRL_Q); - bar.MenuSeparator(); - bar.Add(b, "Copy backtrace", THISBACK(CopyStack)); - bar.Add(b, "Copy dissassembly", THISBACK(CopyDisas)); -} - -static int CharFilterReSlash(int c) -{ - return c == '\\' ? '/' : c; -} - -bool Gdb_MI2::SetBreakpoint(const String& filename, int line, const String& bp) -{ - String file = Filter(host->GetHostPath(NormalizePath(filename)), CharFilterReSlash); - - // gets all breakpoints - MIValue bps = GetBreakpoints(); - - // line should start from 1... - line++; - - // check wether we've got already a breakpoint here - // and remove it - MIValue brk = bps.FindBreakpoint(file, line); - if(!brk.IsEmpty()) - if(!MICmd(Format("break-delete %s", brk["number"].Get()))) - { - Exclamation(t_("Couldn't remove breakpoint")); - return false; - } - - if(bp.IsEmpty()) - return true; - else if(bp[0] == 0xe) - return MICmd(Format("break-insert %s:%d", file, line)); - else - return MICmd(Format("break-insert -c \"%s\" %s:%d", bp, file, line)); -} - -bool Gdb_MI2::RunTo() -{ - String bi; - bool df = disas.HasFocus(); - bool res; - // sets a temporary breakpoint on cursor location - // it'll be cleared automatically on first stop - if(df) - { - if(!disas.GetCursor()) - return false; - res = TryBreak(disas.GetCursor(), true); - } - else - res = TryBreak(IdeGetFileName(), IdeGetFileLine() + 1, true); - - if(!res) - { - Exclamation(t_("No code at chosen location !")); - return false; - } - - Run(); - if(df) - disas.SetFocus(); - return true; -} - -void Gdb_MI2::Run() -{ - MIValue val; - if(firstRun) - // GDB up to 7.1 has a bug that maps -exec-run ro run, not to run& - // making so async mode useless; we use the console run& command instead -// 2012-07-08 update : interrupting GDB in async mode without having -// non-stop mode enabled crashes X...don't know if it's a GDB bug or Theide one. -// anyways, by now we give up with async mode and remove 'Asynchronous break' function - val = MICmd("exec-run"); - -// val = MICmd("interpreter-exec console run&"); - - else - val = MICmd("exec-continue --all"); - int i = 50; - while(!started && --i) - { - GuiSleep(20); - Ctrl::ProcessEvents(); - ReadGdb(false); - } - if(!started) - { - Exclamation(t_("Failed to start application")); - return; - } - Lock(); - while(dbg && !stopped) - { - GuiSleep(20); - Ctrl::ProcessEvents(); - ReadGdb(false); - } - Unlock(); - if(stopped) - CheckStopReason(); - - started = stopped = false; - firstRun = false; - IdeActivateBottom(); -} - -void Gdb_MI2::AsyncBrk() -{ - // send an interrupt command to all running threads - StopAllThreads(); - - // gdb usually returns to command prompt instantly, BEFORE - // giving out stop reason, which we need. So, we wait some - // milliseconds and re-read (non blocking) GDB output to get it -/* - for(int i = 0; i < 20 && !stopped; i++) - { - Sleep(100); - ReadGdb(false); - } -*/ - - // if target is correctly stopped, 'stopped' flag should be already set - // so we don't care here. If not set, target can't be stopped. -} - -void Gdb_MI2::Stop() -{ - stopped = true; - if(dbg && dbg->IsRunning()) - dbg->Kill(); -} - -bool Gdb_MI2::IsFinished() -{ - return !dbg->IsRunning() && !IdeIsDebugLock(); -} - -bool Gdb_MI2::Tip(const String& exp, CodeEditor::MouseTip& mt) -{ - // quick exit - if(exp.IsEmpty()) - return false; - - // if local vars are empty, update them - UpdateLocalVars(); - - // display tip just for local variables... it should be more than enough - int i = localVarExpressions.Find(exp); - if(i < 0) - return false; - - mt.display = &StdDisplay(); - mt.value = exp + "=" + localVarValues[i]; - mt.sz = mt.display->GetStdSize(String(mt.value) + "X"); - return true; -} - -///////////////////////////////////////////////////////////////////////////////////////////////////////////// -// CONSTRUCTOR / DESTRUCTOR -///////////////////////////////////////////////////////////////////////////////////////////////////////////// - -Gdb_MI2::Gdb_MI2() -{ - CtrlLayout(regs); - regs.Height(regs.GetLayoutSize().cy); - AddReg(RPREFIX "ax", ®s.rax); - AddReg(RPREFIX "bx", ®s.rbx); - AddReg(RPREFIX "cx", ®s.rcx); - AddReg(RPREFIX "dx", ®s.rdx); - AddReg(RPREFIX "si", ®s.rsi); - AddReg(RPREFIX "di", ®s.rdi); - AddReg(RPREFIX "bp", ®s.rbp); - AddReg(RPREFIX "sp", ®s.rsp); -#ifdef CPU_64 - AddReg("r8", ®s.r8); - AddReg("r9", ®s.r9); - AddReg("r10", ®s.r10); - AddReg("r11", ®s.r11); - AddReg("r12", ®s.r12); - AddReg("r13", ®s.r13); - AddReg("r14", ®s.r14); - AddReg("r15", ®s.r15); -#endif - regs.Color(SColorLtFace); - regs.AddFrame(TopSeparatorFrame()); - regs.AddFrame(RightSeparatorFrame()); - - autos.NoHeader(); - autos.AddColumn("", 1); - autos.AddColumn("", 10); - autos.AddColumn("", 3); - autos.WhenLeftDouble = THISBACK1(onExploreExpr, &autos); - autos.EvenRowColor(); - autos.OddRowColor(); - - locals.NoHeader(); - locals.AddColumn("", 1); - locals.AddColumn("", 10); - locals.AddColumn("", 3); - locals.WhenLeftDouble = THISBACK1(onExploreExpr, &locals); - locals.EvenRowColor(); - locals.OddRowColor(); - - watches.NoHeader(); - watches.AddColumn("", 1).Edit(watchedit); - watches.AddColumn("", 10); - watches.AddColumn("", 3); - watches.Inserting().Removing(); - watches.WhenLeftDouble = THISBACK1(onExploreExpr, &watches); - watches.EvenRowColor(); - watches.OddRowColor(); - - int c = EditField::GetStdHeight(); - explorer.AddColumn("", 1); - explorer.AddColumn("", 6) /*.SetDisplay(Single()) */; - explorerPane.Add(explorerBackBtn.LeftPos(0, c).TopPos(0, c)); - explorerPane.Add(explorerForwardBtn.LeftPos(c + 2, c).TopPos(0, c)); - explorerPane.Add(explorerExprEdit.HSizePos(2 * c + 4).TopPos(0, c)); - explorerPane.Add(explorer.HSizePos().VSizePos(EditField::GetStdHeight(), 0)); - explorer.NoHeader(); - explorer.WhenLeftDouble = THISBACK(onExplorerChild); - explorer.WhenBar = THISBACK(ExplorerMenu); - - explorerBackBtn.SetImage(DbgImg::ExplorerBack()); - explorerBackBtn <<= THISBACK(onExplorerBack); - explorerForwardBtn.SetImage(DbgImg::ExplorerFw()); - explorerForwardBtn <<= THISBACK(onExplorerForward); - explorerBackBtn.Disable(); - explorerForwardBtn.Disable(); - explorerHistoryPos = -1; - - Add(tab.SizePos()); - tab.Add(autos.SizePos(), "Autos"); - tab.Add(locals.SizePos(), t_("Locals")); - tab.Add(watches.SizePos(), t_("Watches")); - tab.Add(explorerPane.SizePos(), t_("Explorer")); - - Add(threadSelector.LeftPos(FindTabsRight() + 10, StdFont().GetWidth('X') * 10).TopPos(2, EditField::GetStdHeight())); - Add(frame.HSizePos(threadSelector.GetRect().right + 10, 0).TopPos(2, EditField::GetStdHeight())); - frame.Ctrl::Add(dlock.SizePos()); - dlock = " Running.."; - dlock.SetFrame(BlackFrame()); - dlock.SetInk(Red); - dlock.NoTransparent(); - dlock.Hide(); - - CtrlLayout(quickwatch, "Quick watch"); - quickwatch.close.Cancel() <<= quickwatch.Breaker(IDCANCEL); - quickwatch.evaluate.Ok() <<= quickwatch.Acceptor(IDOK); - quickwatch.WhenClose = quickwatch.Breaker(IDCANCEL); - quickwatch.value.SetReadOnly(); - quickwatch.value.SetFont(Courier(12)); - quickwatch.value.SetColor(TextCtrl::PAPER_READONLY, White); - quickwatch.Sizeable().Zoomable(); - quickwatch.NoCenter(); - quickwatch.SetRect(0, 150, 300, 400); - quickwatch.Icon(DbgImg::QuickWatch()); - - Transparent(); - - started = false; - stopped = false; -} - -Gdb_MI2::~Gdb_MI2() -{ - IdeRemoveBottom(*this); - IdeRemoveRight(disas); -} - -///////////////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE FUNCTIONS -///////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// find free space at right of tabs (we should probably add something to TabCtrl for that..) -int Gdb_MI2::FindTabsRight(void) -{ - if(!tab.GetCount()) - return 0; - - int lastTab = tab.GetCount() - 1; - int i1 = 0, i2 = 10000; - - // bisect up it finds a point on last tab... - int iTab = -1; - int i = 0; - while(iTab != lastTab) - { - i = (i1 + i2) / 2; - iTab = tab.GetTab(Point(i, 0)); - if(iTab < 0) - i2 = i; - else - i1 = i; - } - - // now scan for righ tab edge - i1 = i; i2 = i + 10000; - while(abs(i1 - i2) > 2) - { - i = (i1 + i2) / 2; - iTab = tab.GetTab(Point(i, 0)); - if(iTab == -1) - i2 = i; - else - i1 = i; - } - return i; -} - -// lock/unlock debugger controls -void Gdb_MI2::Lock() -{ - IdeDebugLock(); - watches.Disable(); - locals.Disable(); - frame.Disable(); - threadSelector.Disable(); - dlock.Show(); -} - -void Gdb_MI2::Unlock() -{ - if(IdeDebugUnLock()) - { - watches.Enable(); - locals.Enable(); - frame.Enable(); - threadSelector.Enable(); - dlock.Hide(); - } -} - -// read debugger output analyzing command responses and async output -// things are quite tricky because debugger output seems to be -// slow and we have almost no terminator to stop on -- (gdb) is not -// so reliable as it can happen (strangely) in middle of nothing -MIValue Gdb_MI2::ParseGdb(String const &output, bool wait) -{ - MIValue res; - - LDUMP(output); - - // parse result data - StringStream ss(output); - int iSubstr; - while(!ss.IsEof()) - { - String s = TrimBoth(ss.GetLine()); - - // check 'running' and 'stopped' async output - if(s.StartsWith("*running")) - { - started = true; - stopReason.Clear(); - continue; - } -// 2012-07-08 -- In some cases, GDB output get mixed with app one -// so we shall look for 'stop' record in all string, -// not just at beginning as it was before -// not a wanderful way, as debugger could be tricked by some text... -/* - else if(s.StartsWith("*stopped")) - { - stopped = true; - s = '{' + s.Mid(9) + '}'; - stopReason = MIValue(s); - continue; - } -*/ - else if( (iSubstr = s.Find("*stopped,reason=")) >= 0) - { - stopped = true; - s = '{' + s.Mid(iSubstr + 9) + '}'; - stopReason = MIValue(s); - continue; - } - - // skip asynchronous responses - // in future, we could be gather/use them - if(s[0] == '*'|| s[0] == '=') - continue; - - // here handling of command responses - // we're not interested either, as we use MI interface - if(s[0] == '~') - continue; - - // here handling of target output - // well, for now discard this one too, but it should go on console output - if(s[0] == '~') - continue; - - // here handling of gdb log/debug message - // not interesting here - if(s[0] == '&') - continue; - - // now we SHALL have something starting with any of - // // "^done", "^running", "^connected", "^error" or "^exit" records - if(s.StartsWith("^done") || s.StartsWith("^running")) - { - // here we've got succesful command output in list form, if any - // shall skip the comma; following can be a serie of pairs, - // or directly an array of maps in form of : - // [{key="value",key="value",...},{key="value"...}...] - - int i = 5; // just skip shortest, ^done - while(s[i] && s[i] != ',') - i++; - if(!s[i]) - continue; - i++; - if(!s[i]) - continue; - res = MIValue(s.Mid(i)); - continue; - } - else if(s.StartsWith("^error")) - { - // first array element is reserved for command result status - s = s.Right(12); // '^error,msg=\"' - s = s.Left(s.GetCount() - 1); - res.SetError(s); - } - else - continue; - } - return res; -} - -MIValue Gdb_MI2::ReadGdb(bool wait) -{ - String output; - MIValue res; - - if(wait) - { - // this path is for locking read -- we NEED cmd output - int retries = 4; - while(dbg) - { - String s; - dbg->Read(s); - - // we really NEED an answer - if(s.IsEmpty() && output.IsEmpty()) - continue; - - // on first empty string, we check if we really - // got a valid answer - if(s.IsEmpty()) - { - res = ParseGdb(output); - - // if no valid answer (or, well, an empty answer..) - // we retry up to 4 times - if(res.IsEmpty() && --retries) - { - Sleep(20); - continue; - } - return res; - } - output += s; - } - } - else - { - // this path is for NON locking read -- we just cleanup - // streaming output, mostly - while(dbg) - { - String s; - dbg->Read(s); - if(s.IsEmpty()) - break; - output += s; - } - } - if(output.IsEmpty()) - return res; - return ParseGdb(output); -} - -// new-way commands using GDB MI interface -// on input : MI interface command line -// on output : an MIValue containing GDB output -// STREAM OUTPUT -// ~ command response -// @ target output -// & gdb log/debug messages -// -// RESULT RECORDS -// "^done" [ "," results ] -// "^running" same as "^done" -// "^connected" gdb has connected to a remote target. -// "^error" "," c-string The operation failed. The c-string contains the corresponding error message. -// "^exit" gdb has terminate -// -// ASYNCHRONOUS RECORDS -// *running,thread-id="thread" -// *stopped,reason="reason",thread-id="id",stopped-threads="stopped",core="core" -// =thread-group-added,id="id" -// =thread-group-removed,id="id" -// =thread-group-started,id="id",pid="pid" -// =thread-group-exited,id="id"[,exit-code="code"] -// =thread-created,id="id",group-id="gid" -// =thread-exited,id="id",group-id="gid" -// =thread-selected,id="id" -// =library-loaded,... -// =library-unloaded,... -// =breakpoint-created,bkpt={...} -// =breakpoint-modified,bkpt={...} -// =breakpoint-deleted,bkpt={...} -// -// FRAME INFO INSIDE RESPONSES -// level The level of the stack frame. The innermost frame has the level of zero. This field is always present. -// func The name of the function corresponding to the frame. This field may be absent if gdb is unable to determine the function name. -// addr The code address for the frame. This field is always present. -// file The name of the source files that correspond to the frame's code address. This field may be absent. -// line The source line corresponding to the frames' code address. This field may be absent. -// from The name of the binary file (either executable or shared library) the corresponds to the frame's code address. This field may be absent. - -// THREAD INFO INSIDE RESPONSES -// id The numeric id assigned to the thread by gdb. This field is always present. -// target-id Target-specific string identifying the thread. This field is always present. -// details Additional information about the thread provided by the target. It is supposed to be human-readable and not interpreted by the frontend. This field is optional. -// state Either `stopped' or `running', depending on whether the thread is presently running. This field is always present. -// core The value of this field is an integer number of the processor core the thread was last seen on. This field is optional. -// -// REMARKS : by now, we just handle synchronous output and check asynchronous one just to detect -// debugger run/stop status -- all remaining asynchrnonous output is discarded -MIValue Gdb_MI2::MICmd(const char *cmdLine) -{ - LDUMP(cmdLine); - // sends command to debugger and get result data - - // should handle dbg unexpected termination ? - if(!dbg || !dbg->IsRunning() /* || IdeIsDebugLock() */) - return MIValue(); - - // consume previous output from gdb... don't know why sometimes - // is there and gives problems to MI interface. We shall maybe - // parse and store it somewhere - ReadGdb(false); - dbg->Write(String("-") + cmdLine + "\n"); - - return ReadGdb(); -} - -// format breakpoint line from ide file and line -String Gdb_MI2::BreakPos(String const &file, int line) -{ - return String().Cat() << Filter(host->GetHostPath(NormalizePath(file)), CharFilterReSlash) << ":" << line; -} - -// set breakpoint -MIValue Gdb_MI2::InsertBreakpoint(const char *file, int line) -{ - return MICmd("break-insert " + BreakPos(file, line)); -} - -// get breakpoints info -MIValue Gdb_MI2::GetBreakpoints(void) -{ - // issue a break-list command - return MICmd("break-list")["BreakpointTable"]; -} - -MIValue Gdb_MI2::GetBreakpoint(int id) -{ - return MIValue(); -} - -MIValue Gdb_MI2::GetBreakPoint(const char *file, int line) -{ - return MIValue(); -} - -bool Gdb_MI2::TryBreak(adr_t addr, bool temp) -{ - String bp; - if(temp) - bp = "-t "; - bp += Format("*0x%X", (int64)addr); - - MIValue res = MICmd(String("break-insert ") + bp); - return !res.IsError(); -} - -bool Gdb_MI2::TryBreak(String const &file, int line, bool temp) -{ - String bp; - if(temp) - bp = "-t "; - bp += BreakPos(file, line); - MIValue res = MICmd(String("break-insert ") + bp); - return !res.IsError(); -} - -void Gdb_MI2::SyncDisas(MIValue &fInfo, bool fr) -{ - if(!disas.IsVisible()) - return; - - // get current frame's address - adr_t adr = stou(~fInfo["addr"].Get().Mid(2), NULL, 16); - if(!disas.InRange(adr)) - { - MIValue code; - - // if frame is inside a source file, disassemble current function - if(fInfo.Find("file") >= 0 && fInfo.Find("line") >= 0) - { - String file = fInfo["file"]; - String line = fInfo["line"]; - code = MICmd(Format("data-disassemble -f %s -l %s -n -1 -- 0", file, line))["asm_insns"]; - } - else - // otherwise disassemble some -100 ... +100 bytes around address - code = MICmd(Format("data-disassemble -s %x -e %x -- 0", (void *)(adr - 100), (void *)(adr + 100)))["asm_insns"]; - - disas.Clear(); - for(int iLine = 0; iLine < code.GetCount(); iLine++) - { - MIValue &line = code[iLine]; - adr_t address = stou(~line["address"].Get().Mid(2), NULL, 16); - String inst = line["inst"]; - int spc = inst.Find(' '); - String opCode, operand; - if(spc >= 0) - { - opCode = inst.Left(spc); - operand = TrimBoth(inst.Mid(spc)); - } - else - opCode = inst; - disas.Add(address, opCode, operand); - } - } - - // setup disassembler cursor - disas.SetCursor(adr); - disas.SetIp(adr, fr ? DbgImg::FrameLinePtr() : DbgImg::IpLinePtr()); - - // update registers - MIValue rNames = MICmd("data-list-register-names")["register-names"]; - MIValue rValues = MICmd("data-list-register-values x")["register-values"]; - Index iNames; - for(int i = 0; i < rNames.GetCount(); i++) - iNames.Add(rNames[i]); - for(int iReg = 0; iReg < regname.GetCount(); iReg++) - { - int i = iNames.Find(regname[iReg]); - String rValue = "****************"; - if(i >= 0) - rValue = "0000000000000000" + rValues[i]["value"].Get().Mid(2); -#ifdef CPU_64 - rValue = rValue.Right(16); -#else - rValue = rValue.Right(8); -#endif - if(reglbl[iReg]->GetText() != rValue) - { - reglbl[iReg]->SetLabel(rValue); - reglbl[iReg]->SetInk(LtRed); - } - else - reglbl[iReg]->SetInk(Black); - } - -} - -// sync ide display with breakpoint position -void Gdb_MI2::SyncIde(bool fr) -{ - // setup threads droplist - threadSelector.Clear(); - MIValue tInfo = MICmd("thread-info"); - int curThread = atoi(tInfo["current-thread-id"].Get()); - MIValue &threads = tInfo["threads"]; - for(int iThread = 0; iThread < threads.GetCount(); iThread++) - { - int id = atoi(threads[iThread]["id"].Get()); - if(id == curThread) - { - threadSelector.Add(id, Format("#%03x(*)", id)); - break; - } - } - threadSelector <<= curThread; - - // get current frame info and level - MIValue fInfo = MICmd("stack-info-frame")["frame"]; - int level = atoi(fInfo["level"].Get()); - - // if we got file and line info, we can sync the source editor position - autoLine = ""; - if(fInfo.Find("file") >= 0 && fInfo.Find("line") >= 0) - { - String file = GetLocalPath(fInfo["file"]); - int line = atoi(fInfo["line"].Get()); - if(FileExists(file)) - { - 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); - autoLine = IdeGetLine(line - 2) + ' ' + IdeGetLine(line - 1) + ' ' + IdeGetLine(line); - } - } - - // get the arguments for current frame - MIValue fArgs = MICmd(Format("stack-list-arguments 1 %d %d", level, level))["stack-args"][0]["args"]; - - // setup frame droplist - frame.Clear(); - frame.Add(0, FormatFrame(fInfo, fArgs)); - frame <<= 0; - - SyncData(); - SyncDisas(fInfo, fr); -} - -// logs frame data on console -void Gdb_MI2::LogFrame(String const &msg, MIValue &frame) -{ - String file = frame("file", ""); - String line = frame("line", ""); - String function = frame("function", ""); - String addr = frame("addr", ""); - - PutConsole(Format(msg + " at %s, function '%s', file '%s', line %s", addr, function, file, line)); -} - -// stop all running threads and re-select previous current thread -void Gdb_MI2::StopAllThreads(void) -{ - // get thread info for all threads - MIValue tInfo = MICmd("thread-info"); - MIValue &threads = tInfo["threads"]; - bool someRunning = false; - for(int iThread = 0; iThread < threads.GetCount(); iThread++) - { - if(threads[iThread]["state"].Get() != "stopped") - { - someRunning = true; - break; - } - } - // don't issue any stop command if no threads running - // (brings problems....) - if(!someRunning) - return; - - // stores current thread id - String current = tInfo("current-thread-id", ""); - - // stops all threads - MICmd("exec-interrupt --all"); - - // just to be sure, reads out GDB output - ReadGdb(false); - - // reselect current thread as it was before stopping all others - if(current != "") - MICmd("thread-select " + current); -} - -// check for stop reason -void Gdb_MI2::CheckStopReason(void) -{ - // we need to store stop reason BEFORE interrupting all other - // threads, otherwise it'll be lost - MIValue stReason = stopReason; - - // get the reason string - String reason; - if(stReason.IsEmpty()) - reason = "unknown reason"; - else - reason = stReason["reason"]; - - // as we are in non-stop mode, to allow async break to work - // we shall stop ALL running threads here, otherwise we'll have - // problems when single stepping a gui MT app - // single step will be done so for a single thread, while other - // are idle. Maybe we could make this behaviour optional - StopAllThreads(); - - if(reason == "exited-normally") - { - Stop(); - PutConsole("Program exited normally."); - } - else if(reason == "exited") - { - Stop(); - PutConsole("Program exited with code "); - } - else if(reason == "breakpoint-hit") - { - LogFrame("Hit breakpoint", stReason["frame"]); - SyncIde(); - } - else if(reason == "end-stepping-range") - { - LogFrame("End stepping range", stReason["frame"]); - SyncIde(); - } - else if(reason == "unknown reason") - { - PutConsole("Stopped by unknown reason"); - SyncIde(); - } - else - { - // weird stop reasons (i.e., signals, segfaults... may not have a frame - // data inside - if(stReason.Find("frame") < 0) - PutConsole(Format("Stopped, reason '%s'", reason)); - else - LogFrame(Format("Stopped, reason '%s'", reason), stReason["frame"]); - SyncIde(); - } -} - -void Gdb_MI2::Step(const char *cmd) -{ - bool b = disas.HasFocus(); - - MIValue res = MICmd(cmd); - - int i = 50; - while(!started && --i) - { - GuiSleep(20); - Ctrl::ProcessEvents(); - ReadGdb(false); - } - if(!started) - { - Stop(); - Exclamation(t_("Step failed - terminating debugger")); - return; - } - - Lock(); - ReadGdb(false); - while(dbg && !stopped) - { - GuiSleep(20); - Ctrl::ProcessEvents(); - ReadGdb(false); - } - Unlock(); - - firstRun = false; - if(stopped) - CheckStopReason(); - started = stopped = false; - IdeActivateBottom(); - - // reset focus to disassembly pane if was there before - if(b) - disas.SetFocus(); -} - -// setup ide cursor based on disassembler one -void Gdb_MI2::DisasCursor() -{ - if(!disas.HasFocus()) - return; - - // temporary disable disas lost-focus handler - // otherwise cursor will not be correctly set by following code - disas.WhenFocus.Clear(); - - // to get info of corresponding file/line of an address, the only way - // we found is to insert a breakpoint, note the position and remove it - MIValue b = MICmd(Format("break-insert *0x%X", (int64)disas.GetCursor()))["bkpt"]; - if(b.Find("file") >= 0 && b.Find("line") >= 0) - IdeSetDebugPos(b["file"], atoi(b["line"].Get()) - 1, DbgImg::DisasPtr(), 1); - if(b.Find("number") >= 0) - MICmd(Format("break-delete %s", b["number"].Get())); - disas.SetFocus(); - - // re-enable disas lost focus handler - disas.WhenFocus = THISBACK(DisasFocus); -} - -// reset ide default cursor image when disassembler loose focus -void Gdb_MI2::DisasFocus() -{ - if(!disas.HasFocus()) - IdeSetDebugPos(IdeGetFileName(), IdeGetFileLine(), Null, 1); -} - -// create a string representation of frame given its info and args -String Gdb_MI2::FormatFrame(MIValue &fInfo, MIValue &fArgs) -{ - int idx = atoi(fInfo("level", "-1")); - if(idx < 0) - return t_("invalid frame info"); - if(!fArgs.IsArray()) - return t_("invalid frame args"); - String func = fInfo("func", ""); - String file = fInfo("file", ""); - String line = fInfo("line", ""); - int nArgs = fArgs.GetCount(); - String argLine; - for(int iArg = 0; iArg < nArgs; iArg++) - { - argLine += fArgs[iArg]["name"].Get(); - if(fArgs[iArg].Find("value") >= 0) - argLine << "=" << fArgs[iArg]["value"]; - argLine << ','; - } - if(!argLine.IsEmpty()) - argLine = "(" + argLine.Left(argLine.GetCount() - 1) + ")"; - return Format("%02d-%s%s at %s:%s", idx, func, argLine, file, line); -} - -// re-fills frame's droplist when dropping it -void Gdb_MI2::DropFrames() -{ - int q = ~frame; - frame.Clear(); - - // get a list of frames - MIValue frameList = MICmd("stack-list-frames")["stack"]; - if(frameList.IsError() || !frameList.IsArray()) - { - Exclamation("Couldn't get stack frame list"); - return; - } - - // get the arguments for all frames, values just for simple types - MIValue frameArgs = MICmd("stack-list-arguments 1")["stack-args"]; - if(frameArgs.IsError() || !frameArgs.IsArray()) - { - Exclamation("Couldn't get stack arguments list"); - return; - } - - // fill the droplist - for(int iFrame = 0; iFrame < frameArgs.GetCount(); iFrame++) - { - MIValue &fInfo = frameList[iFrame]; - MIValue &fArgs = frameArgs[iFrame]["args"]; - frame.Add(iFrame, FormatFrame(fInfo, fArgs)); - } - frame <<= q; -} - -// shows selected stack frame in editor -void Gdb_MI2::ShowFrame() -{ - int i = (int)~frame; - if(!MICmd(Format("stack-select-frame %d", i))) - { - Exclamation(Format(t_("Couldn't select frame #%d"), i)); - return; - } - SyncIde(i); -} - -// re-fills thread selector droplist on drop -void Gdb_MI2::dropThreads() -{ - int q = ~threadSelector; - threadSelector.Clear(); - - // get a list of all available threads - MIValue tInfo = MICmd("thread-info"); - MIValue &threads = tInfo["threads"]; - if(!tInfo.IsTuple() || !threads.IsArray()) - { - Exclamation(t_("couldn't get thread info")); - return; - } - int currentId = atoi(tInfo["current-thread-id"].Get()); - for(int iThread = 0; iThread < threads.GetCount(); iThread++) - { - MIValue &t = threads[iThread]; - int id = atoi(t["id"].Get()); - String threadStr = Format("#%03x%s", id, (id == currentId ? "(*)" : "")); - threadSelector.Add(id, threadStr); - } - threadSelector <<= q; -} - -// selects current thread -void Gdb_MI2::showThread(void) -{ - int i = (int)~threadSelector; - MICmd(Format("thread-select %d", i)); - - SyncIde(); -} - -// update variables on demand (locals, watches....) -void Gdb_MI2::UpdateVars(void) -{ - MIValue iUpdated = MICmd("var-update 2 *"); - if(!iUpdated.IsTuple() || iUpdated.Find("changelist") < 0) - return; - MIValue &updated = iUpdated["changelist"]; - for(int iUpd = 0; iUpd < updated.GetCount(); iUpd++) - { - if(!updated[iUpd].IsTuple() || updated[iUpd].Find("name") < 0 || updated[iUpd].Find("value") < 0) - return; - String varName = updated[iUpd]["name"]; - String value = updated[iUpd]["value"]; - int iVar; - - // local variables - if( (iVar = localVarNames.Find(varName)) >= 0) - localVarValues[iVar] = value; - - // watches - if( (iVar = watchesNames.Find(varName)) >= 0) - watchesValues[iVar] = value; - - // autos - if( (iVar = autosNames.Find(varName)) >= 0) - autosValues[iVar] = value; - } -} - -struct CapitalLess -{ - bool operator()(String const &a, String const &b) const { return ToUpper(a) < ToUpper(b); } -}; - -// update local variables on demand -void Gdb_MI2::UpdateLocalVars(void) -{ - // we try to speed-up at most reading of local vars; this - // because we need to keep them updated at each step and - // re-reading as a whole is too time expensive. - // So, at first we build a list of local variable NAMES only - MIValue iLoc = MICmd("stack-list-variables 0"); - if(!iLoc || iLoc.IsEmpty()) - return; - MIValue &loc = iLoc["variables"]; - if(!loc.IsArray()) - return; - IndexlocIdx; - for(int iLoc = 0; iLoc < loc.GetCount(); iLoc++) - locIdx.Add(loc[iLoc]["name"]); - - // then we 'purge' stored local variables that are not present - // anymore... that can happen, for example, exiting from a loop - for(int iVar = localVarNames.GetCount() - 1; iVar >= 0; iVar--) - { - if(locIdx.Find(localVarExpressions[iVar]) < 0) - { - String varName = localVarNames.Pop(); - MICmd("var-delete \"" + varName + "\""); - localVarExpressions.Pop(); - localVarValues.Pop(); - localVarTypes.Pop(); - } - } - - // then we shall add missing variables - for(int iLoc = 0; iLoc < locIdx.GetCount(); iLoc++) - { - if(localVarExpressions.Find(locIdx[iLoc]) < 0) - { - MIValue var = MICmd(String("var-create - @ \"") + locIdx[iLoc] + "\""); - - // sometimes it has problem creating vars... maybe because they're - // still not active; we just skip them - if(var.IsError() || var.IsEmpty()) - continue; - - localVarNames.Add(var["name"]); - localVarExpressions.Add(locIdx[iLoc]); - localVarTypes.Add(var["type"]); - localVarValues.Add(var["value"]); - } - } - - // unsorted local vars are ugly and difficult to look at... - // so we sort them. As we've 4 structures, it's not so simple - Vector keys; - keys <<= localVarExpressions.GetKeys(); - IndexSort(keys, localVarTypes, CapitalLess()); - keys <<= localVarExpressions.GetKeys(); - IndexSort(keys, localVarValues, CapitalLess()); - Vector names; - names <<= localVarNames.GetKeys(); - keys <<= localVarExpressions.GetKeys(); - IndexSort(keys, names, CapitalLess()); - localVarNames = names; - localVarExpressions = keys; - - // and finally, we do an update to refresh changed variables - UpdateVars(); -} - -// update stored watches values on demand -void Gdb_MI2::UpdateWatches(void) -{ - // gets variable names from control - Index exprs; - for(int i = 0; i < watches.GetCount(); i++) - exprs.Add(watches.Get(i, 0)); - - // purge stored watches not available anymore - for(int iVar = watchesNames.GetCount() - 1; iVar >= 0; iVar--) - { - if(exprs.Find(watchesExpressions[iVar]) < 0) - { - String varName = watchesNames.Pop(); - MICmd("var-delete \"" + varName + "\""); - watchesExpressions.Pop(); - watchesValues.Pop(); - watchesTypes.Pop(); - } - } - - // then we shall add missing variables - for(int i = 0; i < exprs.GetCount(); i++) - { - if(watchesExpressions.Find(exprs[i]) < 0) - { - // the '@' means we create a dynamic variable - MIValue var = MICmd(String("var-create - @ \"") + exprs[i] + "\""); - - // sometimes it has problem creating vars... maybe because they're - // still not active; we just skip them - if(!var || var.IsEmpty() || !var.IsTuple()) - continue; - watchesNames.Add(var["name"]); - watchesExpressions.Add(exprs[i]); - watchesTypes.Add(var["type"]); - watchesValues.Add(var["value"]); - } - } - - // and finally, we do an update to refresh changed variables - UpdateVars(); -} - -// update stored auto values on demand -void Gdb_MI2::UpdateAutos(void) -{ - // gets variable names from control - Index exprs; - for(int i = 0; i < autos.GetCount(); i++) - exprs.Add(autos.Get(i, 0)); - - // purge stored autos not available anymore - for(int iVar = autosNames.GetCount() - 1; iVar >= 0; iVar--) - { - if(exprs.Find(autosExpressions[iVar]) < 0) - { - String varName = autosNames.Pop(); - MICmd("var-delete \"" + varName + "\""); - autosExpressions.Pop(); - autosValues.Pop(); - autosTypes.Pop(); - } - } - - // then we shall add missing variables - for(int i = 0; i < exprs.GetCount(); i++) - { - if(autosExpressions.Find(exprs[i]) < 0) - { - // the '@' means we create a dynamic variable - MIValue var = MICmd(String("var-create - @ \"") + exprs[i] + "\""); - - // sometimes it has problem creating vars... maybe because they're - // still not active; we just skip them - if(!var || var.IsEmpty() || !var.IsTuple()) - continue; - autosNames.Add(var["name"]); - autosExpressions.Add(exprs[i]); - autosTypes.Add(var["type"]); - autosValues.Add(var["value"]); - } - } - - // and finally, we do an update to refresh changed variables - UpdateVars(); -} - -void Gdb_MI2::SyncLocals() -{ - // update local vars cache, if needed - UpdateLocalVars(); - - // reload ide control - VectorMap prev = DataMap2(locals); - locals.Clear(); - for(int i = 0; i < localVarNames.GetCount(); i++) - locals.Add(localVarExpressions[i], localVarValues[i], localVarTypes[i]); - MarkChanged2(prev, locals); -} - -// sync watches treectrl -void Gdb_MI2::SyncWatches() -{ - // update local watches cache, if needed - UpdateWatches(); - - // re-fill the control - VectorMap prev = DataMap2(watches); - for(int i = 0; i < watches.GetCount(); i++) - { - String expr = watches.Get(i, 0); - int idx = watchesExpressions.Find(expr); - if(idx >= 0) - { - watches.Set(i, 2, watchesTypes[idx]); - watches.Set(i, 1, watchesValues[idx]); - } - else - { - watches.Set(i, 2, ""); - watches.Set(i, 1, t_("")); - } - } - MarkChanged2(prev, watches); -} - -// sync auto vars treectrl -void Gdb_MI2::SyncAutos() -{ - VectorMap prev = DataMap2(autos); - autos.Clear(); - - // read expressions around cursor line - CParser p(autoLine); - while(!p.IsEof()) - { - if(p.IsId()) - { - String exp = p.ReadId(); - for(;;) - { - if(p.Char('.') && p.IsId()) - exp << '.'; - else - if(p.Char2('-', '>') && p.IsId()) - exp << "->"; - else - break; - exp << p.ReadId(); - } - if(autos.Find(exp) < 0) - autos.Add(exp, ""); - } - p.SkipTerm(); - } - autos.Sort(); - - // update autos cache, if needed - UpdateAutos(); - - for(int i = autos.GetCount() -1; i >= 0; i--) - { - String expr = autos.Get(i, 0); - int idx = autosExpressions.Find(expr); - if(idx >= 0) - { - autos.Set(i, 2, autosTypes[idx]); - autos.Set(i, 1, autosValues[idx]); - } - else - autos.Remove(i); - } - - MarkChanged2(prev, autos); -} - -// sync data tabs, depending on which tab is shown -void Gdb_MI2::SyncData() -{ - switch(tab.Get()) { - case 0: - SyncAutos(); - break; - - case 1: - SyncLocals(); - break; - - case 2: - SyncWatches(); - break; - } -} - -// watches arrayctrl key handling -bool Gdb_MI2::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; - } - if(key == K_ENTER && explorerExprEdit.HasFocus()) - { - onExploreExpr(); - return true; - } - return Ctrl::Key(key, count); -} - -// sends pretty-printing scripts -void Gdb_MI2::SendPrettyPrinters(void) -{ - String fName = GetTempFileName(); - fName = fName.Left(fName.GetCount() - 3) + "py"; - SaveFile(fName, (const char *)PrettyPrinters); - MICmd("interpreter-exec console \"source " + fName + "\""); - FileDelete(fName); -} - -// format watch line -String Gdb_MI2::FormatWatchLine(String exp, String const &val, int level) -{ - if(exp.GetCount() < 20) - exp = exp + String(' ', 20); - else - exp = exp.Left(17) + "..."; - exp = exp.Left(20) + " = " + val; - return String(' ', level * 4) + exp; -} - -// deep watch current quickwatch variable -void Gdb_MI2::WatchDeep0(String parentExp, String const &var, int level, int &maxRemaining) -{ - // avoid endless recursion for circularly linked vars - if(--maxRemaining <= 0) - return; - - MIValue childInfo = MICmd("var-list-children 1 \"" + var + "\" 0 100"); - if(!childInfo || !childInfo.IsTuple()) - return; - int nChilds = min(atoi(childInfo("numchild", "-1")), 100); - if(nChilds <= 0) - return; - - MIValue &childs = childInfo["children"]; - for(int i = 0; i < childs.GetCount() && maxRemaining > 0; i++) - { - MIValue child = childs[i]; - String exp = child["exp"]; - - // handle pseudo children... -/* - while(exp == "public" || exp == "private" || exp == "protected") - { - child = MICmd(String("var-list-children 1 \"") + child["name"] + "\"")["children"][0]; - exp = child["exp"]; - } -*/ - if(isdigit(exp[0])) - { - exp = '[' + exp + ']'; - if(parentExp.Mid(parentExp.GetCount() - 1, 1) == ".") - parentExp = parentExp.Left(parentExp.GetCount() - 1); - } - if(exp[0] != '.' && exp[0] != '[') - exp = '.' + exp; - - String type = child("type", ""); - if(!type.IsEmpty()) - type = "(" + type + ")"; - String value = child["value"]; - - // try to format nicely results... - quickwatch.value <<= (String)~quickwatch.value + "\n" + FormatWatchLine(parentExp + exp, type + value, level); - - // recursive deep watch - WatchDeep0(exp, child["name"], level + 1, maxRemaining); - } -} - -void Gdb_MI2::WatchDeep(String parentExp, String const &name) -{ - // this is to avoid circular endless recursion - // we limit the total watched (sub)variables to this count - int maxRemaining = 300; - - WatchDeep0(parentExp, name, 1, maxRemaining); -} - -// opens quick watch dialog -void Gdb_MI2::QuickWatch() -{ - // try to figure out if we've got the cursor in some interesting - // place... if it is, grab the expression from there - // otherwise let it unchanged - Ctrl *c = GetFocusCtrl(); - if(typeid(*c) == typeid(AssistEditor)) - { - AssistEditor *a = dynamic_cast(c); - String s = a->ReadIdBack(a->GetCursor()); - quickwatch.expression <<= s; - } - else if(c == &autos) - { - int i = autos.GetCursor(); - if(i >= 0) - quickwatch.expression <<= autos.Get(i, 0); - } - else if(c == &locals) - { - int i = locals.GetCursor(); - if(i >= 0) - quickwatch.expression <<= locals.Get(i, 0); - } - else if(c == &watches) - { - int i = watches.GetCursor(); - if(i >= 0) - quickwatch.expression <<= watches.Get(i, 0); - } - else if(c == &explorer || c == &explorerExprEdit) - { - quickwatch.expression <<= ~explorerExprEdit; - } - - for(;;) - { - String exp = ~quickwatch.expression; - if(!exp.IsEmpty()) - { - MIValue v = MICmd("var-create - @ " + exp); - if(!v.IsError()) - { - String type = v("type", ""); - if(!type.IsEmpty()) - type = "(" + type + ")"; - String value = v["value"]; - quickwatch.value <<= FormatWatchLine(exp, type + value, 0); - quickwatch.expression.AddHistory(); - String name = v["name"]; - WatchDeep(exp, name); - MICmd("var-delete " + name); - } - else - quickwatch.value <<= t_(""); - } - else - quickwatch.value.Clear(); - int q = quickwatch.Run(); - if(q == IDCANCEL) - break; - } - quickwatch.Close(); -} - -// copy stack frame list to clipboard -void Gdb_MI2::CopyStack() -{ - DropFrames(); - String s; - for(int i = 0; i < frame.GetCount(); i++) - s << frame.GetValue(i) << "\n"; - WriteClipboardText(s); -} - -// copy disassembly listing to clipboard -void Gdb_MI2::CopyDisas() -{ - disas.WriteClipboard(); -} - -///////////////////////////////////// EXPLORER ////////////////////////////////////////////////////// - -void Gdb_MI2::doExplore(String const &expr, String var, bool isChild, bool appendHistory) -{ - // set the expression inside expression editor - explorerExprEdit = expr; - - // store current expr as "parent expr" to be used - // when displaying childs... GDB is not reliable on this - explorerParentExpr = expr; - - // try to find a suitable already created var inside history - // if not, and if not a child, we shall create it - if(var.IsEmpty()) - { - // empty var is allowed ONLY for non-childs exploring - if(isChild) - return; - - int iVar = explorerHistoryExpressions.Find(expr); - if(iVar < 0) - { - MIValue v = MICmd("var-create - @ \"" + expr + "\""); - if(v.IsEmpty() || v.IsError()) - { - explorer.Clear(); - explorerChildVars.Clear(); - explorer.Add(expr, ""); - return; - } - var = v["name"]; - } - else - var = explorerHistoryVars[iVar]; - } - - // update the history : trim it from past current position - // and append it at end - if(appendHistory) - { - if(explorerHistoryPos >= 0) - { - // frees all non-child variables following current position - for(int i = explorerHistoryPos + 1; i < explorerHistoryVars.GetCount(); i++) - { - if(!explorerHistoryChilds[i] && explorerHistoryVars[i] != var) - { - MICmd("var-delete \"" + explorerHistoryVars[i] + "\""); - } - } - explorerHistoryPos++; - explorerHistoryExpressions.Trim(explorerHistoryPos); - explorerHistoryVars.Trim(explorerHistoryPos); - explorerHistoryChilds.Trim(explorerHistoryPos); - } - else - explorerHistoryPos = 0; - explorerHistoryExpressions.Add(expr); - explorerHistoryVars.Add(var); - explorerHistoryChilds.Add(isChild); - } - - // here we've finally got variable to explore, just read it - // and set into first row of explorer ArrayCtrl - String value = MICmd("var-evaluate-expression \"" + var + "\"")["value"]; - String type = MICmd("var-info-type \"" + var + "\"")["type"]; - explorer.Clear(); - explorerChildVars.Clear(); - explorer.Add("=", "(" + type + ")" + value, var); - - // now we shall add variable children... we limit them to a number of 100 - // which should be anough. Alternative would be to display them in ranges, - // but this over-complicates the application. - MIValue childInfo = MICmd("var-list-children 1 \"" + var + "\" 0 100"); - int nChilds = min(atoi(childInfo["numchild"].Get()), 100); - if(nChilds) - { - MIValue &childs = childInfo["children"]; - for(int i = 0; i < childs.GetCount(); i++) - { - MIValue child = childs[i]; - String exp = child["exp"]; - - // handle pseudo children... -/* - while(exp == "public" || exp == "private" || exp == "protected") - { - child = MICmd(String("var-list-children 1 \"") + child["name"] + "\"")["children"][0]; - exp = child["exp"]; - } -*/ - if(isdigit(exp[0])) - exp = '[' + exp + ']'; - String type = child("type", ""); - if(!type.IsEmpty()) - type = "(" + type + ")"; - String value = child["value"]; - explorer.Add(exp, type + value); - explorerChildVars.Add(child["name"]); - } - } - - explorerBackBtn.Enable(explorerHistoryPos > 0); - explorerForwardBtn.Enable(explorerHistoryPos < explorerHistoryVars.GetCount() - 1); -} - -// -void Gdb_MI2::onExploreExpr(ArrayCtrl *what) -{ - String expr; - if(!what) - { - // if expression don't come from another ArrayCtrl - // we use the expression editbox - expr = ~explorerExprEdit; - } - else - { - // otherwise, we use the expression from sending ArrayCtrl - int line = what->GetCursor(); - if(line >= 0) - expr = what->Get(line, 0); - } - // nothing to do on empty expression - if(expr == "") - return; - doExplore(expr, "", false, true); - - // activate explorer tab - tab.Set(3); -} - -void Gdb_MI2::onExplorerChild() -{ - // click on first line (value line) does nothing - int line = explorer.GetCursor(); - if(line < 1) - return; - if(--line < explorerChildVars.GetCount()) - { - String var = explorerChildVars[line]; - String varExp = var; - String expr = MICmd("var-info-expression \"" + var + "\"")["exp"]; - if(expr[0] != '[' && expr[0] != '.') - if(isdigit(expr[0])) - expr = '[' + expr + ']'; - else - expr = '.' + expr; - expr = explorerParentExpr + expr; - doExplore(expr, var, true, true); - } -} - -void Gdb_MI2::onExplorerBack() -{ - if(explorerHistoryPos < 1) - return; - explorerHistoryPos--; - String expr = explorerHistoryExpressions[explorerHistoryPos]; - String var = explorerHistoryVars[explorerHistoryPos]; - bool isChild = explorerHistoryChilds[explorerHistoryPos]; - doExplore(expr, var, isChild, false); -} - -void Gdb_MI2::onExplorerForward() -{ - if(explorerHistoryPos >= explorerHistoryVars.GetCount() - 1) - return; - explorerHistoryPos++; - String expr = explorerHistoryExpressions[explorerHistoryPos]; - String var = explorerHistoryVars[explorerHistoryPos]; - bool isChild = explorerHistoryChilds[explorerHistoryPos]; - doExplore(expr, var, isChild, false); -} - -void Gdb_MI2::ExplorerMenu(Bar& bar) -{ -} -////////////////////////////////////////////////////////////////////////////////////////////////////// - -// create GDB process and initializes it -bool Gdb_MI2::Create(One _host, const String& exefile, const String& cmdline) -{ - host = _host; - dbg = host->StartProcess("gdb " + GetHostPath(exefile) + " --interpreter=mi -q"); - if(!dbg) { - Exclamation(t_("Error invoking gdb !")); - return false; - } - IdeSetBottom(*this); - IdeSetRight(disas); - - disas.AddFrame(regs); - disas.WhenCursor = THISBACK(DisasCursor); - disas.WhenFocus = THISBACK(DisasFocus); - - frame.WhenDrop = THISBACK(DropFrames); - frame <<= THISBACK(ShowFrame); - - threadSelector.WhenDrop = THISBACK(dropThreads); - threadSelector <<= THISBACK(showThread); - - watches.WhenAcceptEdit = THISBACK(SyncData); - tab <<= THISBACK(SyncData); - - // this one will allow asynchronous break of running app -// 2012-07-08 -- DISABLED because of GDB bugs... -// MICmd("gdb-set target-async 1"); - MICmd("gdb-set pagination off"); - -// Don't enable this one -- brings every sort of bugs with -// It was useful to issue Asynchronous break, but too many bugs -// to be useable -// MICmd("gdb-set non-stop on"); - -// MICmd("gdb-set interactive-mode off"); - - MICmd("gdb-set disassembly-flavor intel"); - MICmd("gdb-set exec-done-display off"); - MICmd("gdb-set annotate 1"); - MICmd("gdb-set height 0"); - MICmd("gdb-set width 0"); - MICmd("gdb-set confirm off"); - MICmd("gdb-set print asm-demangle"); - - // avoids debugger crash if caught inside ungrabbing function - // (don't solves all cases, but helps...) - MICmd("gdb-set unwindonsignal on"); - - if(!IsNull(cmdline)) - MICmd("gdb-set args " + cmdline); - - // enable pretty printing - SendPrettyPrinters(); - MICmd("enable-pretty-printing"); - - firstRun = true; - - return true; -} - -One Gdb_MI2Create(One host, const String& exefile, const String& cmdline) -{ - Gdb_MI2 *dbg = new Gdb_MI2; - if(!dbg->Create(host, exefile, cmdline)) - { - delete dbg; - return NULL; - } - return dbg; -} +#include "Debuggers.h" +#include + +#include "PrettyPrinters.brc" + +#define LDUMP(x) // DDUMP(x) +#define LLOG(x) // DLOG(x) + +// we need those because we changed ArrayCtrl formats (including var types) +static VectorMap DataMap2(const ArrayCtrl& data) +{ + VectorMap m; + for(int i = 0; i < data.GetCount(); i++) + m.Add(data.Get(i, 0), data.Get(i, 2)); + return m; +} + +static void MarkChanged2(const VectorMap& m, ArrayCtrl& data) +{ + for(int i = 0; i < data.GetCount(); i++) + { + int q = m.Find(data.Get(i, 0)); + if(q >= 0 && m[q] != data.Get(i, 2)) + data.SetDisplay(i, 2, Single()); + else + data.SetDisplay(i, 2, StdDisplay()); + } +} + +void WatchEdit::HighlightLine(int line, Vector& h, int pos) +{ + Color cEven = Blend(SColorInfo, White, 220); + Color cOdd = Blend(SColorInfo, White, 128); + for(int i = 0; i < h.GetCount(); i++) + h[i].paper = (line % 2 ? cOdd : cEven); +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC IDE INTERFACE +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void Gdb_MI2::DebugBar(Bar& bar) +{ + bar.Add("Stop debugging", THISBACK(Stop)).Key(K_SHIFT_F5); + bar.Separator(); +// see note on Run() function -- crashes X on my machine, so removed by now +// bar.Add(!stopped, "Asynchronous break", THISBACK(AsyncBrk)); + bool b = !IdeIsDebugLock(); + bar.Add(b, "Step into", DbgImg::StepInto(), THISBACK1(Step, disas.HasFocus() ? "exec-step-instruction" : "exec-step")).Key(K_F11); + bar.Add(b, "Step over", DbgImg::StepOver(), THISBACK1(Step, disas.HasFocus() ? "exec-next-instruction" : "exec-next")).Key(K_F10); + bar.Add(b, "Step out", DbgImg::StepOut(), THISBACK1(Step, "exec-finish")).Key(K_SHIFT_F11); + bar.Add(b, "Run to", DbgImg::RunTo(), THISBACK(doRunTo)).Key(K_CTRL_F10); + bar.Add(b, "Run", DbgImg::Run(), THISBACK(Run)).Key(K_F5); + bar.Separator(); + bar.Add(b, "Quick watch", THISBACK(QuickWatch)).Key(K_CTRL_Q); + bar.MenuSeparator(); + bar.Add(b, "Copy backtrace", THISBACK(CopyStack)); + bar.Add(b, "Copy dissassembly", THISBACK(CopyDisas)); +} + +static int CharFilterReSlash(int c) +{ + return c == '\\' ? '/' : c; +} + +bool Gdb_MI2::SetBreakpoint(const String& filename, int line, const String& bp) +{ + String file = Filter(host->GetHostPath(NormalizePath(filename)), CharFilterReSlash); + + // gets all breakpoints + MIValue bps = GetBreakpoints(); + + // line should start from 1... + line++; + + // check wether we've got already a breakpoint here + // and remove it + MIValue brk = bps.FindBreakpoint(file, line); + if(!brk.IsEmpty()) + if(!MICmd(Format("break-delete %s", brk["number"].Get()))) + { + Exclamation(t_("Couldn't remove breakpoint")); + return false; + } + + if(bp.IsEmpty()) + return true; + else if(bp[0] == 0xe) + return MICmd(Format("break-insert %s:%d", file, line)); + else + return MICmd(Format("break-insert -c \"%s\" %s:%d", bp, file, line)); +} + +bool Gdb_MI2::RunTo() +{ + String bi; + bool df = disas.HasFocus(); + bool res; + // sets a temporary breakpoint on cursor location + // it'll be cleared automatically on first stop + if(df) + { + if(!disas.GetCursor()) + return false; + res = TryBreak(disas.GetCursor(), true); + } + else + res = TryBreak(IdeGetFileName(), IdeGetFileLine() + 1, true); + + if(!res) + { + Exclamation(t_("No code at chosen location !")); + return false; + } + + Run(); + if(df) + disas.SetFocus(); + return true; +} + +void Gdb_MI2::Run() +{ + MIValue val; + if(firstRun) + // GDB up to 7.1 has a bug that maps -exec-run ro run, not to run& + // making so async mode useless; we use the console run& command instead +// 2012-07-08 update : interrupting GDB in async mode without having +// non-stop mode enabled crashes X...don't know if it's a GDB bug or Theide one. +// anyways, by now we give up with async mode and remove 'Asynchronous break' function + val = MICmd("exec-run"); + +// val = MICmd("interpreter-exec console run&"); + + else + val = MICmd("exec-continue --all"); + int i = 50; + while(!started && --i) + { + GuiSleep(20); + Ctrl::ProcessEvents(); + ReadGdb(false); + } + if(!started) + { + Exclamation(t_("Failed to start application")); + return; + } + Lock(); + while(dbg && !stopped) + { + GuiSleep(20); + Ctrl::ProcessEvents(); + ReadGdb(false); + } + Unlock(); + if(stopped) + CheckStopReason(); + + started = stopped = false; + firstRun = false; + IdeActivateBottom(); +} + +void Gdb_MI2::AsyncBrk() +{ + // send an interrupt command to all running threads + StopAllThreads(); + + // gdb usually returns to command prompt instantly, BEFORE + // giving out stop reason, which we need. So, we wait some + // milliseconds and re-read (non blocking) GDB output to get it +/* + for(int i = 0; i < 20 && !stopped; i++) + { + Sleep(100); + ReadGdb(false); + } +*/ + + // if target is correctly stopped, 'stopped' flag should be already set + // so we don't care here. If not set, target can't be stopped. +} + +void Gdb_MI2::Stop() +{ + stopped = true; + if(dbg && dbg->IsRunning()) + dbg->Kill(); +} + +bool Gdb_MI2::IsFinished() +{ + return !dbg->IsRunning() && !IdeIsDebugLock(); +} + +bool Gdb_MI2::Tip(const String& exp, CodeEditor::MouseTip& mt) +{ + // quick exit + if(exp.IsEmpty()) + return false; + + // if local vars are empty, update them + UpdateLocalVars(); + + // display tip just for local variables... it should be more than enough + int i = localVarExpressions.Find(exp); + if(i < 0) + return false; + + mt.display = &StdDisplay(); + mt.value = exp + "=" + localVarValues[i]; + mt.sz = mt.display->GetStdSize(String(mt.value) + "X"); + return true; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// CONSTRUCTOR / DESTRUCTOR +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + +Gdb_MI2::Gdb_MI2() +{ + CtrlLayout(regs); + regs.Height(regs.GetLayoutSize().cy); + AddReg(RPREFIX "ax", ®s.rax); + AddReg(RPREFIX "bx", ®s.rbx); + AddReg(RPREFIX "cx", ®s.rcx); + AddReg(RPREFIX "dx", ®s.rdx); + AddReg(RPREFIX "si", ®s.rsi); + AddReg(RPREFIX "di", ®s.rdi); + AddReg(RPREFIX "bp", ®s.rbp); + AddReg(RPREFIX "sp", ®s.rsp); +#ifdef CPU_64 + AddReg("r8", ®s.r8); + AddReg("r9", ®s.r9); + AddReg("r10", ®s.r10); + AddReg("r11", ®s.r11); + AddReg("r12", ®s.r12); + AddReg("r13", ®s.r13); + AddReg("r14", ®s.r14); + AddReg("r15", ®s.r15); +#endif + regs.Color(SColorLtFace); + regs.AddFrame(TopSeparatorFrame()); + regs.AddFrame(RightSeparatorFrame()); + + autos.NoHeader(); + autos.AddColumn("", 1); + autos.AddColumn("", 10); + autos.AddColumn("", 3); + autos.WhenLeftDouble = THISBACK1(onExploreExpr, &autos); + autos.EvenRowColor(); + autos.OddRowColor(); + + locals.NoHeader(); + locals.AddColumn("", 1); + locals.AddColumn("", 10); + locals.AddColumn("", 3); + locals.WhenLeftDouble = THISBACK1(onExploreExpr, &locals); + locals.EvenRowColor(); + locals.OddRowColor(); + + watches.NoHeader(); + watches.AddColumn("", 1).Edit(watchedit); + watches.AddColumn("", 10); + watches.AddColumn("", 3); + watches.Inserting().Removing(); + watches.WhenLeftDouble = THISBACK1(onExploreExpr, &watches); + watches.EvenRowColor(); + watches.OddRowColor(); + + int c = EditField::GetStdHeight(); + explorer.AddColumn("", 1); + explorer.AddColumn("", 6) /*.SetDisplay(Single()) */; + explorerPane.Add(explorerBackBtn.LeftPos(0, c).TopPos(0, c)); + explorerPane.Add(explorerForwardBtn.LeftPos(c + 2, c).TopPos(0, c)); + explorerPane.Add(explorerExprEdit.HSizePos(2 * c + 4).TopPos(0, c)); + explorerPane.Add(explorer.HSizePos().VSizePos(EditField::GetStdHeight(), 0)); + explorer.NoHeader(); + explorer.WhenLeftDouble = THISBACK(onExplorerChild); + explorer.WhenBar = THISBACK(ExplorerMenu); + + explorerBackBtn.SetImage(DbgImg::ExplorerBack()); + explorerBackBtn <<= THISBACK(onExplorerBack); + explorerForwardBtn.SetImage(DbgImg::ExplorerFw()); + explorerForwardBtn <<= THISBACK(onExplorerForward); + explorerBackBtn.Disable(); + explorerForwardBtn.Disable(); + explorerHistoryPos = -1; + + Add(tab.SizePos()); + tab.Add(autos.SizePos(), "Autos"); + tab.Add(locals.SizePos(), t_("Locals")); + tab.Add(watches.SizePos(), t_("Watches")); + tab.Add(explorerPane.SizePos(), t_("Explorer")); + + Add(threadSelector.LeftPos(FindTabsRight() + 10, StdFont().GetWidth('X') * 10).TopPos(2, EditField::GetStdHeight())); + Add(frame.HSizePos(threadSelector.GetRect().right + 10, 0).TopPos(2, EditField::GetStdHeight())); + frame.Ctrl::Add(dlock.SizePos()); + dlock = " Running.."; + dlock.SetFrame(BlackFrame()); + dlock.SetInk(Red); + dlock.NoTransparent(); + dlock.Hide(); + + CtrlLayout(quickwatch, "Quick watch"); + quickwatch.close.Cancel() <<= quickwatch.Breaker(IDCANCEL); + quickwatch.evaluate.Ok() <<= quickwatch.Acceptor(IDOK); + quickwatch.WhenClose = quickwatch.Breaker(IDCANCEL); + quickwatch.value.SetReadOnly(); + quickwatch.value.SetFont(Courier(12)); + quickwatch.value.SetColor(TextCtrl::PAPER_READONLY, White); + quickwatch.Sizeable().Zoomable(); + quickwatch.NoCenter(); + quickwatch.SetRect(0, 150, 300, 400); + quickwatch.Icon(DbgImg::QuickWatch()); + + Transparent(); + + started = false; + stopped = false; +} + +Gdb_MI2::~Gdb_MI2() +{ + IdeRemoveBottom(*this); + IdeRemoveRight(disas); +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE FUNCTIONS +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// find free space at right of tabs (we should probably add something to TabCtrl for that..) +int Gdb_MI2::FindTabsRight(void) +{ + if(!tab.GetCount()) + return 0; + + int lastTab = tab.GetCount() - 1; + int i1 = 0, i2 = 10000; + + // bisect up it finds a point on last tab... + int iTab = -1; + int i = 0; + while(iTab != lastTab) + { + i = (i1 + i2) / 2; + iTab = tab.GetTab(Point(i, 0)); + if(iTab < 0) + i2 = i; + else + i1 = i; + } + + // now scan for righ tab edge + i1 = i; i2 = i + 10000; + while(abs(i1 - i2) > 2) + { + i = (i1 + i2) / 2; + iTab = tab.GetTab(Point(i, 0)); + if(iTab == -1) + i2 = i; + else + i1 = i; + } + return i; +} + +// lock/unlock debugger controls +void Gdb_MI2::Lock() +{ + IdeDebugLock(); + watches.Disable(); + locals.Disable(); + frame.Disable(); + threadSelector.Disable(); + dlock.Show(); +} + +void Gdb_MI2::Unlock() +{ + if(IdeDebugUnLock()) + { + watches.Enable(); + locals.Enable(); + frame.Enable(); + threadSelector.Enable(); + dlock.Hide(); + } +} + +// read debugger output analyzing command responses and async output +// things are quite tricky because debugger output seems to be +// slow and we have almost no terminator to stop on -- (gdb) is not +// so reliable as it can happen (strangely) in middle of nothing +MIValue Gdb_MI2::ParseGdb(String const &output, bool wait) +{ + MIValue res; + + LDUMP(output); + + // parse result data + StringStream ss(output); + int iSubstr; + while(!ss.IsEof()) + { + String s = TrimBoth(ss.GetLine()); + + // check 'running' and 'stopped' async output + if(s.StartsWith("*running")) + { + started = true; + stopReason.Clear(); + continue; + } +// 2012-07-08 -- In some cases, GDB output get mixed with app one +// so we shall look for 'stop' record in all string, +// not just at beginning as it was before +// not a wanderful way, as debugger could be tricked by some text... +/* + else if(s.StartsWith("*stopped")) + { + stopped = true; + s = '{' + s.Mid(9) + '}'; + stopReason = MIValue(s); + continue; + } +*/ + else if( (iSubstr = s.Find("*stopped,reason=")) >= 0) + { + stopped = true; + s = '{' + s.Mid(iSubstr + 9) + '}'; + stopReason = MIValue(s); + continue; + } + + // skip asynchronous responses + // in future, we could be gather/use them + if(s[0] == '*'|| s[0] == '=') + continue; + + // here handling of command responses + // we're not interested either, as we use MI interface + if(s[0] == '~') + continue; + + // here handling of target output + // well, for now discard this one too, but it should go on console output + if(s[0] == '~') + continue; + + // here handling of gdb log/debug message + // not interesting here + if(s[0] == '&') + continue; + + // now we SHALL have something starting with any of + // // "^done", "^running", "^connected", "^error" or "^exit" records + if(s.StartsWith("^done") || s.StartsWith("^running")) + { + // here we've got succesful command output in list form, if any + // shall skip the comma; following can be a serie of pairs, + // or directly an array of maps in form of : + // [{key="value",key="value",...},{key="value"...}...] + + int i = 5; // just skip shortest, ^done + while(s[i] && s[i] != ',') + i++; + if(!s[i]) + continue; + i++; + if(!s[i]) + continue; + res = MIValue(s.Mid(i)); + continue; + } + else if(s.StartsWith("^error")) + { + // first array element is reserved for command result status + s = s.Right(12); // '^error,msg=\"' + s = s.Left(s.GetCount() - 1); + res.SetError(s); + } + else + continue; + } + return res; +} + +MIValue Gdb_MI2::ReadGdb(bool wait) +{ + String output; + MIValue res; + + if(wait) + { + // this path is for locking read -- we NEED cmd output + int retries = 4; + while(dbg) + { + String s; + dbg->Read(s); + + // we really NEED an answer + if(s.IsEmpty() && output.IsEmpty()) + continue; + + // on first empty string, we check if we really + // got a valid answer + if(s.IsEmpty()) + { + res = ParseGdb(output); + + // if no valid answer (or, well, an empty answer..) + // we retry up to 4 times + if(res.IsEmpty() && --retries) + { + Sleep(20); + continue; + } + return res; + } + output += s; + } + } + else + { + // this path is for NON locking read -- we just cleanup + // streaming output, mostly + while(dbg) + { + String s; + dbg->Read(s); + if(s.IsEmpty()) + break; + output += s; + } + } + if(output.IsEmpty()) + return res; + return ParseGdb(output); +} + +// new-way commands using GDB MI interface +// on input : MI interface command line +// on output : an MIValue containing GDB output +// STREAM OUTPUT +// ~ command response +// @ target output +// & gdb log/debug messages +// +// RESULT RECORDS +// "^done" [ "," results ] +// "^running" same as "^done" +// "^connected" gdb has connected to a remote target. +// "^error" "," c-string The operation failed. The c-string contains the corresponding error message. +// "^exit" gdb has terminate +// +// ASYNCHRONOUS RECORDS +// *running,thread-id="thread" +// *stopped,reason="reason",thread-id="id",stopped-threads="stopped",core="core" +// =thread-group-added,id="id" +// =thread-group-removed,id="id" +// =thread-group-started,id="id",pid="pid" +// =thread-group-exited,id="id"[,exit-code="code"] +// =thread-created,id="id",group-id="gid" +// =thread-exited,id="id",group-id="gid" +// =thread-selected,id="id" +// =library-loaded,... +// =library-unloaded,... +// =breakpoint-created,bkpt={...} +// =breakpoint-modified,bkpt={...} +// =breakpoint-deleted,bkpt={...} +// +// FRAME INFO INSIDE RESPONSES +// level The level of the stack frame. The innermost frame has the level of zero. This field is always present. +// func The name of the function corresponding to the frame. This field may be absent if gdb is unable to determine the function name. +// addr The code address for the frame. This field is always present. +// file The name of the source files that correspond to the frame's code address. This field may be absent. +// line The source line corresponding to the frames' code address. This field may be absent. +// from The name of the binary file (either executable or shared library) the corresponds to the frame's code address. This field may be absent. + +// THREAD INFO INSIDE RESPONSES +// id The numeric id assigned to the thread by gdb. This field is always present. +// target-id Target-specific string identifying the thread. This field is always present. +// details Additional information about the thread provided by the target. It is supposed to be human-readable and not interpreted by the frontend. This field is optional. +// state Either `stopped' or `running', depending on whether the thread is presently running. This field is always present. +// core The value of this field is an integer number of the processor core the thread was last seen on. This field is optional. +// +// REMARKS : by now, we just handle synchronous output and check asynchronous one just to detect +// debugger run/stop status -- all remaining asynchrnonous output is discarded +MIValue Gdb_MI2::MICmd(const char *cmdLine) +{ + LDUMP(cmdLine); + // sends command to debugger and get result data + + // should handle dbg unexpected termination ? + if(!dbg || !dbg->IsRunning() /* || IdeIsDebugLock() */) + return MIValue(); + + // consume previous output from gdb... don't know why sometimes + // is there and gives problems to MI interface. We shall maybe + // parse and store it somewhere + ReadGdb(false); + dbg->Write(String("-") + cmdLine + "\n"); + + return ReadGdb(); +} + +// format breakpoint line from ide file and line +String Gdb_MI2::BreakPos(String const &file, int line) +{ + return String().Cat() << Filter(host->GetHostPath(NormalizePath(file)), CharFilterReSlash) << ":" << line; +} + +// set breakpoint +MIValue Gdb_MI2::InsertBreakpoint(const char *file, int line) +{ + return MICmd("break-insert " + BreakPos(file, line)); +} + +// get breakpoints info +MIValue Gdb_MI2::GetBreakpoints(void) +{ + // issue a break-list command + return MICmd("break-list")["BreakpointTable"]; +} + +MIValue Gdb_MI2::GetBreakpoint(int id) +{ + return MIValue(); +} + +MIValue Gdb_MI2::GetBreakPoint(const char *file, int line) +{ + return MIValue(); +} + +bool Gdb_MI2::TryBreak(adr_t addr, bool temp) +{ + String bp; + if(temp) + bp = "-t "; + bp += Format("*0x%X", (int64)addr); + + MIValue res = MICmd(String("break-insert ") + bp); + return !res.IsError(); +} + +bool Gdb_MI2::TryBreak(String const &file, int line, bool temp) +{ + String bp; + if(temp) + bp = "-t "; + bp += BreakPos(file, line); + MIValue res = MICmd(String("break-insert ") + bp); + return !res.IsError(); +} + +void Gdb_MI2::SyncDisas(MIValue &fInfo, bool fr) +{ + if(!disas.IsVisible()) + return; + + // get current frame's address + adr_t adr = stou(~fInfo["addr"].Get().Mid(2), NULL, 16); + if(!disas.InRange(adr)) + { + MIValue code; + + // if frame is inside a source file, disassemble current function + if(fInfo.Find("file") >= 0 && fInfo.Find("line") >= 0) + { + String file = fInfo["file"]; + String line = fInfo["line"]; + code = MICmd(Format("data-disassemble -f %s -l %s -n -1 -- 0", file, line))["asm_insns"]; + } + else + // otherwise disassemble some -100 ... +100 bytes around address + code = MICmd(Format("data-disassemble -s %x -e %x -- 0", (void *)(adr - 100), (void *)(adr + 100)))["asm_insns"]; + + disas.Clear(); + for(int iLine = 0; iLine < code.GetCount(); iLine++) + { + MIValue &line = code[iLine]; + adr_t address = stou(~line["address"].Get().Mid(2), NULL, 16); + String inst = line["inst"]; + int spc = inst.Find(' '); + String opCode, operand; + if(spc >= 0) + { + opCode = inst.Left(spc); + operand = TrimBoth(inst.Mid(spc)); + } + else + opCode = inst; + disas.Add(address, opCode, operand); + } + } + + // setup disassembler cursor + disas.SetCursor(adr); + disas.SetIp(adr, fr ? DbgImg::FrameLinePtr() : DbgImg::IpLinePtr()); + + // update registers + MIValue rNames = MICmd("data-list-register-names")["register-names"]; + MIValue rValues = MICmd("data-list-register-values x")["register-values"]; + Index iNames; + for(int i = 0; i < rNames.GetCount(); i++) + iNames.Add(rNames[i]); + for(int iReg = 0; iReg < regname.GetCount(); iReg++) + { + int i = iNames.Find(regname[iReg]); + String rValue = "****************"; + if(i >= 0) + rValue = "0000000000000000" + rValues[i]["value"].Get().Mid(2); +#ifdef CPU_64 + rValue = rValue.Right(16); +#else + rValue = rValue.Right(8); +#endif + if(reglbl[iReg]->GetText() != rValue) + { + reglbl[iReg]->SetLabel(rValue); + reglbl[iReg]->SetInk(LtRed); + } + else + reglbl[iReg]->SetInk(Black); + } + +} + +// sync ide display with breakpoint position +void Gdb_MI2::SyncIde(bool fr) +{ + // setup threads droplist + threadSelector.Clear(); + MIValue tInfo = MICmd("thread-info"); + int curThread = atoi(tInfo["current-thread-id"].Get()); + MIValue &threads = tInfo["threads"]; + for(int iThread = 0; iThread < threads.GetCount(); iThread++) + { + int id = atoi(threads[iThread]["id"].Get()); + if(id == curThread) + { + threadSelector.Add(id, Format("#%03x(*)", id)); + break; + } + } + threadSelector <<= curThread; + + // get current frame info and level + MIValue fInfo = MICmd("stack-info-frame")["frame"]; + int level = atoi(fInfo["level"].Get()); + + // if we got file and line info, we can sync the source editor position + autoLine = ""; + if(fInfo.Find("file") >= 0 && fInfo.Find("line") >= 0) + { + String file = GetLocalPath(fInfo["file"]); + int line = atoi(fInfo["line"].Get()); + if(FileExists(file)) + { + 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); + autoLine = IdeGetLine(line - 2) + ' ' + IdeGetLine(line - 1) + ' ' + IdeGetLine(line); + } + } + + // get the arguments for current frame + MIValue fArgs = MICmd(Format("stack-list-arguments 1 %d %d", level, level))["stack-args"][0]["args"]; + + // setup frame droplist + frame.Clear(); + frame.Add(0, FormatFrame(fInfo, fArgs)); + frame <<= 0; + + SyncData(); + SyncDisas(fInfo, fr); +} + +// logs frame data on console +void Gdb_MI2::LogFrame(String const &msg, MIValue &frame) +{ + String file = frame("file", ""); + String line = frame("line", ""); + String function = frame("function", ""); + String addr = frame("addr", ""); + + PutConsole(Format(msg + " at %s, function '%s', file '%s', line %s", addr, function, file, line)); +} + +// stop all running threads and re-select previous current thread +void Gdb_MI2::StopAllThreads(void) +{ + // get thread info for all threads + MIValue tInfo = MICmd("thread-info"); + MIValue &threads = tInfo["threads"]; + bool someRunning = false; + for(int iThread = 0; iThread < threads.GetCount(); iThread++) + { + if(threads[iThread]["state"].Get() != "stopped") + { + someRunning = true; + break; + } + } + // don't issue any stop command if no threads running + // (brings problems....) + if(!someRunning) + return; + + // stores current thread id + String current = tInfo("current-thread-id", ""); + + // stops all threads + MICmd("exec-interrupt --all"); + + // just to be sure, reads out GDB output + ReadGdb(false); + + // reselect current thread as it was before stopping all others + if(current != "") + MICmd("thread-select " + current); +} + +// check for stop reason +void Gdb_MI2::CheckStopReason(void) +{ + // we need to store stop reason BEFORE interrupting all other + // threads, otherwise it'll be lost + MIValue stReason = stopReason; + + // get the reason string + String reason; + if(stReason.IsEmpty()) + reason = "unknown reason"; + else + reason = stReason["reason"]; + + // as we are in non-stop mode, to allow async break to work + // we shall stop ALL running threads here, otherwise we'll have + // problems when single stepping a gui MT app + // single step will be done so for a single thread, while other + // are idle. Maybe we could make this behaviour optional + StopAllThreads(); + + if(reason == "exited-normally") + { + Stop(); + PutConsole("Program exited normally."); + } + else if(reason == "exited") + { + Stop(); + PutConsole("Program exited with code "); + } + else if(reason == "breakpoint-hit") + { + LogFrame("Hit breakpoint", stReason["frame"]); + SyncIde(); + } + else if(reason == "end-stepping-range") + { + LogFrame("End stepping range", stReason["frame"]); + SyncIde(); + } + else if(reason == "unknown reason") + { + PutConsole("Stopped by unknown reason"); + SyncIde(); + } + else + { + // weird stop reasons (i.e., signals, segfaults... may not have a frame + // data inside + if(stReason.Find("frame") < 0) + PutConsole(Format("Stopped, reason '%s'", reason)); + else + LogFrame(Format("Stopped, reason '%s'", reason), stReason["frame"]); + SyncIde(); + } +} + +void Gdb_MI2::Step(const char *cmd) +{ + bool b = disas.HasFocus(); + + MIValue res = MICmd(cmd); + + int i = 50; + while(!started && --i) + { + GuiSleep(20); + Ctrl::ProcessEvents(); + ReadGdb(false); + } + if(!started) + { + Stop(); + Exclamation(t_("Step failed - terminating debugger")); + return; + } + + Lock(); + ReadGdb(false); + while(dbg && !stopped) + { + GuiSleep(20); + Ctrl::ProcessEvents(); + ReadGdb(false); + } + Unlock(); + + firstRun = false; + if(stopped) + CheckStopReason(); + started = stopped = false; + IdeActivateBottom(); + + // reset focus to disassembly pane if was there before + if(b) + disas.SetFocus(); +} + +// setup ide cursor based on disassembler one +void Gdb_MI2::DisasCursor() +{ + if(!disas.HasFocus()) + return; + + // temporary disable disas lost-focus handler + // otherwise cursor will not be correctly set by following code + disas.WhenFocus.Clear(); + + // to get info of corresponding file/line of an address, the only way + // we found is to insert a breakpoint, note the position and remove it + MIValue b = MICmd(Format("break-insert *0x%X", (int64)disas.GetCursor()))["bkpt"]; + if(b.Find("file") >= 0 && b.Find("line") >= 0) + IdeSetDebugPos(b["file"], atoi(b["line"].Get()) - 1, DbgImg::DisasPtr(), 1); + if(b.Find("number") >= 0) + MICmd(Format("break-delete %s", b["number"].Get())); + disas.SetFocus(); + + // re-enable disas lost focus handler + disas.WhenFocus = THISBACK(DisasFocus); +} + +// reset ide default cursor image when disassembler loose focus +void Gdb_MI2::DisasFocus() +{ + if(!disas.HasFocus()) + IdeSetDebugPos(IdeGetFileName(), IdeGetFileLine(), Null, 1); +} + +// create a string representation of frame given its info and args +String Gdb_MI2::FormatFrame(MIValue &fInfo, MIValue &fArgs) +{ + int idx = atoi(fInfo("level", "-1")); + if(idx < 0) + return t_("invalid frame info"); + if(!fArgs.IsArray()) + return t_("invalid frame args"); + String func = fInfo("func", ""); + String file = fInfo("file", ""); + String line = fInfo("line", ""); + int nArgs = fArgs.GetCount(); + String argLine; + for(int iArg = 0; iArg < nArgs; iArg++) + { + argLine += fArgs[iArg]["name"].Get(); + if(fArgs[iArg].Find("value") >= 0) + argLine << "=" << fArgs[iArg]["value"]; + argLine << ','; + } + if(!argLine.IsEmpty()) + argLine = "(" + argLine.Left(argLine.GetCount() - 1) + ")"; + return Format("%02d-%s%s at %s:%s", idx, func, argLine, file, line); +} + +// re-fills frame's droplist when dropping it +void Gdb_MI2::DropFrames() +{ + int q = ~frame; + frame.Clear(); + + // get a list of frames + MIValue frameList = MICmd("stack-list-frames")["stack"]; + if(frameList.IsError() || !frameList.IsArray()) + { + Exclamation("Couldn't get stack frame list"); + return; + } + + // get the arguments for all frames, values just for simple types + MIValue frameArgs = MICmd("stack-list-arguments 1")["stack-args"]; + if(frameArgs.IsError() || !frameArgs.IsArray()) + { + Exclamation("Couldn't get stack arguments list"); + return; + } + + // fill the droplist + for(int iFrame = 0; iFrame < frameArgs.GetCount(); iFrame++) + { + MIValue &fInfo = frameList[iFrame]; + MIValue &fArgs = frameArgs[iFrame]["args"]; + frame.Add(iFrame, FormatFrame(fInfo, fArgs)); + } + frame <<= q; +} + +// shows selected stack frame in editor +void Gdb_MI2::ShowFrame() +{ + int i = (int)~frame; + if(!MICmd(Format("stack-select-frame %d", i))) + { + Exclamation(Format(t_("Couldn't select frame #%d"), i)); + return; + } + SyncIde(i); +} + +// re-fills thread selector droplist on drop +void Gdb_MI2::dropThreads() +{ + int q = ~threadSelector; + threadSelector.Clear(); + + // get a list of all available threads + MIValue tInfo = MICmd("thread-info"); + MIValue &threads = tInfo["threads"]; + if(!tInfo.IsTuple() || !threads.IsArray()) + { + Exclamation(t_("couldn't get thread info")); + return; + } + int currentId = atoi(tInfo["current-thread-id"].Get()); + for(int iThread = 0; iThread < threads.GetCount(); iThread++) + { + MIValue &t = threads[iThread]; + int id = atoi(t["id"].Get()); + String threadStr = Format("#%03x%s", id, (id == currentId ? "(*)" : "")); + threadSelector.Add(id, threadStr); + } + threadSelector <<= q; +} + +// selects current thread +void Gdb_MI2::showThread(void) +{ + int i = (int)~threadSelector; + MICmd(Format("thread-select %d", i)); + + SyncIde(); +} + +// update variables on demand (locals, watches....) +void Gdb_MI2::UpdateVars(void) +{ + MIValue iUpdated = MICmd("var-update 2 *"); + if(!iUpdated.IsTuple() || iUpdated.Find("changelist") < 0) + return; + MIValue &updated = iUpdated["changelist"]; + for(int iUpd = 0; iUpd < updated.GetCount(); iUpd++) + { + if(!updated[iUpd].IsTuple() || updated[iUpd].Find("name") < 0 || updated[iUpd].Find("value") < 0) + return; + String varName = updated[iUpd]["name"]; + String value = updated[iUpd]["value"]; + int iVar; + + // local variables + if( (iVar = localVarNames.Find(varName)) >= 0) + localVarValues[iVar] = value; + + // watches + if( (iVar = watchesNames.Find(varName)) >= 0) + watchesValues[iVar] = value; + + // autos + if( (iVar = autosNames.Find(varName)) >= 0) + autosValues[iVar] = value; + } +} + +struct CapitalLess +{ + bool operator()(String const &a, String const &b) const { return ToUpper(a) < ToUpper(b); } +}; + +// update local variables on demand +void Gdb_MI2::UpdateLocalVars(void) +{ + // we try to speed-up at most reading of local vars; this + // because we need to keep them updated at each step and + // re-reading as a whole is too time expensive. + // So, at first we build a list of local variable NAMES only + MIValue iLoc = MICmd("stack-list-variables 0"); + if(!iLoc || iLoc.IsEmpty()) + return; + MIValue &loc = iLoc["variables"]; + if(!loc.IsArray()) + return; + IndexlocIdx; + for(int iLoc = 0; iLoc < loc.GetCount(); iLoc++) + locIdx.Add(loc[iLoc]["name"]); + + // then we 'purge' stored local variables that are not present + // anymore... that can happen, for example, exiting from a loop + for(int iVar = localVarNames.GetCount() - 1; iVar >= 0; iVar--) + { + if(locIdx.Find(localVarExpressions[iVar]) < 0) + { + String varName = localVarNames.Pop(); + MICmd("var-delete \"" + varName + "\""); + localVarExpressions.Pop(); + localVarValues.Pop(); + localVarTypes.Pop(); + } + } + + // then we shall add missing variables + for(int iLoc = 0; iLoc < locIdx.GetCount(); iLoc++) + { + if(localVarExpressions.Find(locIdx[iLoc]) < 0) + { + MIValue var = MICmd(String("var-create - @ \"") + locIdx[iLoc] + "\""); + + // sometimes it has problem creating vars... maybe because they're + // still not active; we just skip them + if(var.IsError() || var.IsEmpty()) + continue; + + localVarNames.Add(var["name"]); + localVarExpressions.Add(locIdx[iLoc]); + localVarTypes.Add(var["type"]); + localVarValues.Add(var["value"]); + } + } + + // unsorted local vars are ugly and difficult to look at... + // so we sort them. As we've 4 structures, it's not so simple + Vector keys; + keys <<= localVarExpressions.GetKeys(); + IndexSort(keys, localVarTypes, CapitalLess()); + keys <<= localVarExpressions.GetKeys(); + IndexSort(keys, localVarValues, CapitalLess()); + Vector names; + names <<= localVarNames.GetKeys(); + keys <<= localVarExpressions.GetKeys(); + IndexSort(keys, names, CapitalLess()); + localVarNames = names; + localVarExpressions = keys; + + // and finally, we do an update to refresh changed variables + UpdateVars(); +} + +// update stored watches values on demand +void Gdb_MI2::UpdateWatches(void) +{ + // gets variable names from control + Index exprs; + for(int i = 0; i < watches.GetCount(); i++) + exprs.Add(watches.Get(i, 0)); + + // purge stored watches not available anymore + for(int iVar = watchesNames.GetCount() - 1; iVar >= 0; iVar--) + { + if(exprs.Find(watchesExpressions[iVar]) < 0) + { + String varName = watchesNames.Pop(); + MICmd("var-delete \"" + varName + "\""); + watchesExpressions.Pop(); + watchesValues.Pop(); + watchesTypes.Pop(); + } + } + + // then we shall add missing variables + for(int i = 0; i < exprs.GetCount(); i++) + { + if(watchesExpressions.Find(exprs[i]) < 0) + { + // the '@' means we create a dynamic variable + MIValue var = MICmd(String("var-create - @ \"") + exprs[i] + "\""); + + // sometimes it has problem creating vars... maybe because they're + // still not active; we just skip them + if(!var || var.IsEmpty() || !var.IsTuple()) + continue; + watchesNames.Add(var["name"]); + watchesExpressions.Add(exprs[i]); + watchesTypes.Add(var["type"]); + watchesValues.Add(var["value"]); + } + } + + // and finally, we do an update to refresh changed variables + UpdateVars(); +} + +// update stored auto values on demand +void Gdb_MI2::UpdateAutos(void) +{ + // gets variable names from control + Index exprs; + for(int i = 0; i < autos.GetCount(); i++) + exprs.Add(autos.Get(i, 0)); + + // purge stored autos not available anymore + for(int iVar = autosNames.GetCount() - 1; iVar >= 0; iVar--) + { + if(exprs.Find(autosExpressions[iVar]) < 0) + { + String varName = autosNames.Pop(); + MICmd("var-delete \"" + varName + "\""); + autosExpressions.Pop(); + autosValues.Pop(); + autosTypes.Pop(); + } + } + + // then we shall add missing variables + for(int i = 0; i < exprs.GetCount(); i++) + { + if(autosExpressions.Find(exprs[i]) < 0) + { + // the '@' means we create a dynamic variable + MIValue var = MICmd(String("var-create - @ \"") + exprs[i] + "\""); + + // sometimes it has problem creating vars... maybe because they're + // still not active; we just skip them + if(!var || var.IsEmpty() || !var.IsTuple()) + continue; + autosNames.Add(var["name"]); + autosExpressions.Add(exprs[i]); + autosTypes.Add(var["type"]); + autosValues.Add(var["value"]); + } + } + + // and finally, we do an update to refresh changed variables + UpdateVars(); +} + +void Gdb_MI2::SyncLocals() +{ + // update local vars cache, if needed + UpdateLocalVars(); + + // reload ide control + VectorMap prev = DataMap2(locals); + locals.Clear(); + for(int i = 0; i < localVarNames.GetCount(); i++) + locals.Add(localVarExpressions[i], localVarValues[i], localVarTypes[i]); + MarkChanged2(prev, locals); +} + +// sync watches treectrl +void Gdb_MI2::SyncWatches() +{ + // update local watches cache, if needed + UpdateWatches(); + + // re-fill the control + VectorMap prev = DataMap2(watches); + for(int i = 0; i < watches.GetCount(); i++) + { + String expr = watches.Get(i, 0); + int idx = watchesExpressions.Find(expr); + if(idx >= 0) + { + watches.Set(i, 2, watchesTypes[idx]); + watches.Set(i, 1, watchesValues[idx]); + } + else + { + watches.Set(i, 2, ""); + watches.Set(i, 1, t_("")); + } + } + MarkChanged2(prev, watches); +} + +// sync auto vars treectrl +void Gdb_MI2::SyncAutos() +{ + VectorMap prev = DataMap2(autos); + autos.Clear(); + + // read expressions around cursor line + CParser p(autoLine); + while(!p.IsEof()) + { + if(p.IsId()) + { + String exp = p.ReadId(); + for(;;) + { + if(p.Char('.') && p.IsId()) + exp << '.'; + else + if(p.Char2('-', '>') && p.IsId()) + exp << "->"; + else + break; + exp << p.ReadId(); + } + if(autos.Find(exp) < 0) + autos.Add(exp, ""); + } + p.SkipTerm(); + } + autos.Sort(); + + // update autos cache, if needed + UpdateAutos(); + + for(int i = autos.GetCount() -1; i >= 0; i--) + { + String expr = autos.Get(i, 0); + int idx = autosExpressions.Find(expr); + if(idx >= 0) + { + autos.Set(i, 2, autosTypes[idx]); + autos.Set(i, 1, autosValues[idx]); + } + else + autos.Remove(i); + } + + MarkChanged2(prev, autos); +} + +// sync data tabs, depending on which tab is shown +void Gdb_MI2::SyncData() +{ + switch(tab.Get()) { + case 0: + SyncAutos(); + break; + + case 1: + SyncLocals(); + break; + + case 2: + SyncWatches(); + break; + } +} + +// watches arrayctrl key handling +bool Gdb_MI2::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; + } + if(key == K_ENTER && explorerExprEdit.HasFocus()) + { + onExploreExpr(); + return true; + } + return Ctrl::Key(key, count); +} + +// sends pretty-printing scripts +void Gdb_MI2::SendPrettyPrinters(void) +{ + String fName = GetTempFileName(); + fName = fName.Left(fName.GetCount() - 3) + "py"; + SaveFile(fName, (const char *)PrettyPrinters); + MICmd("interpreter-exec console \"source " + fName + "\""); + FileDelete(fName); +} + +// format watch line +String Gdb_MI2::FormatWatchLine(String exp, String const &val, int level) +{ + if(exp.GetCount() < 20) + exp = exp + String(' ', 20); + else + exp = exp.Left(17) + "..."; + exp = exp.Left(20) + " = " + val; + return String(' ', level * 4) + exp; +} + +// deep watch current quickwatch variable +void Gdb_MI2::WatchDeep0(String parentExp, String const &var, int level, int &maxRemaining) +{ + // avoid endless recursion for circularly linked vars + if(--maxRemaining <= 0) + return; + + MIValue childInfo = MICmd("var-list-children 1 \"" + var + "\" 0 100"); + if(!childInfo || !childInfo.IsTuple()) + return; + int nChilds = min(atoi(childInfo("numchild", "-1")), 100); + if(nChilds <= 0) + return; + + MIValue &childs = childInfo["children"]; + for(int i = 0; i < childs.GetCount() && maxRemaining > 0; i++) + { + MIValue child = childs[i]; + String exp = child["exp"]; + + // handle pseudo children... +/* + while(exp == "public" || exp == "private" || exp == "protected") + { + child = MICmd(String("var-list-children 1 \"") + child["name"] + "\"")["children"][0]; + exp = child["exp"]; + } +*/ + if(isdigit(exp[0])) + { + exp = '[' + exp + ']'; + if(parentExp.Mid(parentExp.GetCount() - 1, 1) == ".") + parentExp = parentExp.Left(parentExp.GetCount() - 1); + } + if(exp[0] != '.' && exp[0] != '[') + exp = '.' + exp; + + String type = child("type", ""); + if(!type.IsEmpty()) + type = "(" + type + ")"; + String value = child["value"]; + + // try to format nicely results... + quickwatch.value <<= (String)~quickwatch.value + "\n" + FormatWatchLine(parentExp + exp, type + value, level); + + // recursive deep watch + WatchDeep0(exp, child["name"], level + 1, maxRemaining); + } +} + +void Gdb_MI2::WatchDeep(String parentExp, String const &name) +{ + // this is to avoid circular endless recursion + // we limit the total watched (sub)variables to this count + int maxRemaining = 300; + + WatchDeep0(parentExp, name, 1, maxRemaining); +} + +// opens quick watch dialog +void Gdb_MI2::QuickWatch() +{ + // try to figure out if we've got the cursor in some interesting + // place... if it is, grab the expression from there + // otherwise let it unchanged + Ctrl *c = GetFocusCtrl(); + if(typeid(*c) == typeid(AssistEditor)) + { + AssistEditor *a = dynamic_cast(c); + String s = a->ReadIdBack(a->GetCursor()); + quickwatch.expression <<= s; + } + else if(c == &autos) + { + int i = autos.GetCursor(); + if(i >= 0) + quickwatch.expression <<= autos.Get(i, 0); + } + else if(c == &locals) + { + int i = locals.GetCursor(); + if(i >= 0) + quickwatch.expression <<= locals.Get(i, 0); + } + else if(c == &watches) + { + int i = watches.GetCursor(); + if(i >= 0) + quickwatch.expression <<= watches.Get(i, 0); + } + else if(c == &explorer || c == &explorerExprEdit) + { + quickwatch.expression <<= ~explorerExprEdit; + } + + for(;;) + { + String exp = ~quickwatch.expression; + if(!exp.IsEmpty()) + { + MIValue v = MICmd("var-create - @ " + exp); + if(!v.IsError()) + { + String type = v("type", ""); + if(!type.IsEmpty()) + type = "(" + type + ")"; + String value = v["value"]; + quickwatch.value <<= FormatWatchLine(exp, type + value, 0); + quickwatch.expression.AddHistory(); + String name = v["name"]; + WatchDeep(exp, name); + MICmd("var-delete " + name); + } + else + quickwatch.value <<= t_(""); + } + else + quickwatch.value.Clear(); + int q = quickwatch.Run(); + if(q == IDCANCEL) + break; + } + quickwatch.Close(); +} + +// copy stack frame list to clipboard +void Gdb_MI2::CopyStack() +{ + DropFrames(); + String s; + for(int i = 0; i < frame.GetCount(); i++) + s << frame.GetValue(i) << "\n"; + WriteClipboardText(s); +} + +// copy disassembly listing to clipboard +void Gdb_MI2::CopyDisas() +{ + disas.WriteClipboard(); +} + +///////////////////////////////////// EXPLORER ////////////////////////////////////////////////////// + +void Gdb_MI2::doExplore(String const &expr, String var, bool isChild, bool appendHistory) +{ + // set the expression inside expression editor + explorerExprEdit = expr; + + // store current expr as "parent expr" to be used + // when displaying childs... GDB is not reliable on this + explorerParentExpr = expr; + + // try to find a suitable already created var inside history + // if not, and if not a child, we shall create it + if(var.IsEmpty()) + { + // empty var is allowed ONLY for non-childs exploring + if(isChild) + return; + + int iVar = explorerHistoryExpressions.Find(expr); + if(iVar < 0) + { + MIValue v = MICmd("var-create - @ \"" + expr + "\""); + if(v.IsEmpty() || v.IsError()) + { + explorer.Clear(); + explorerChildVars.Clear(); + explorer.Add(expr, ""); + return; + } + var = v["name"]; + } + else + var = explorerHistoryVars[iVar]; + } + + // update the history : trim it from past current position + // and append it at end + if(appendHistory) + { + if(explorerHistoryPos >= 0) + { + // frees all non-child variables following current position + for(int i = explorerHistoryPos + 1; i < explorerHistoryVars.GetCount(); i++) + { + if(!explorerHistoryChilds[i] && explorerHistoryVars[i] != var) + { + MICmd("var-delete \"" + explorerHistoryVars[i] + "\""); + } + } + explorerHistoryPos++; + explorerHistoryExpressions.Trim(explorerHistoryPos); + explorerHistoryVars.Trim(explorerHistoryPos); + explorerHistoryChilds.Trim(explorerHistoryPos); + } + else + explorerHistoryPos = 0; + explorerHistoryExpressions.Add(expr); + explorerHistoryVars.Add(var); + explorerHistoryChilds.Add(isChild); + } + + // here we've finally got variable to explore, just read it + // and set into first row of explorer ArrayCtrl + String value = MICmd("var-evaluate-expression \"" + var + "\"")["value"]; + String type = MICmd("var-info-type \"" + var + "\"")["type"]; + explorer.Clear(); + explorerChildVars.Clear(); + explorer.Add("=", "(" + type + ")" + value, var); + + // now we shall add variable children... we limit them to a number of 100 + // which should be anough. Alternative would be to display them in ranges, + // but this over-complicates the application. + MIValue childInfo = MICmd("var-list-children 1 \"" + var + "\" 0 100"); + int nChilds = min(atoi(childInfo["numchild"].Get()), 100); + if(nChilds) + { + MIValue &childs = childInfo["children"]; + for(int i = 0; i < childs.GetCount(); i++) + { + MIValue child = childs[i]; + String exp = child["exp"]; + + // handle pseudo children... +/* + while(exp == "public" || exp == "private" || exp == "protected") + { + child = MICmd(String("var-list-children 1 \"") + child["name"] + "\"")["children"][0]; + exp = child["exp"]; + } +*/ + if(isdigit(exp[0])) + exp = '[' + exp + ']'; + String type = child("type", ""); + if(!type.IsEmpty()) + type = "(" + type + ")"; + String value = child["value"]; + explorer.Add(exp, type + value); + explorerChildVars.Add(child["name"]); + } + } + + explorerBackBtn.Enable(explorerHistoryPos > 0); + explorerForwardBtn.Enable(explorerHistoryPos < explorerHistoryVars.GetCount() - 1); +} + +// +void Gdb_MI2::onExploreExpr(ArrayCtrl *what) +{ + String expr; + if(!what) + { + // if expression don't come from another ArrayCtrl + // we use the expression editbox + expr = ~explorerExprEdit; + } + else + { + // otherwise, we use the expression from sending ArrayCtrl + int line = what->GetCursor(); + if(line >= 0) + expr = what->Get(line, 0); + } + // nothing to do on empty expression + if(expr == "") + return; + doExplore(expr, "", false, true); + + // activate explorer tab + tab.Set(3); +} + +void Gdb_MI2::onExplorerChild() +{ + // click on first line (value line) does nothing + int line = explorer.GetCursor(); + if(line < 1) + return; + if(--line < explorerChildVars.GetCount()) + { + String var = explorerChildVars[line]; + String varExp = var; + String expr = MICmd("var-info-expression \"" + var + "\"")["exp"]; + if(expr[0] != '[' && expr[0] != '.') + if(isdigit(expr[0])) + expr = '[' + expr + ']'; + else + expr = '.' + expr; + expr = explorerParentExpr + expr; + doExplore(expr, var, true, true); + } +} + +void Gdb_MI2::onExplorerBack() +{ + if(explorerHistoryPos < 1) + return; + explorerHistoryPos--; + String expr = explorerHistoryExpressions[explorerHistoryPos]; + String var = explorerHistoryVars[explorerHistoryPos]; + bool isChild = explorerHistoryChilds[explorerHistoryPos]; + doExplore(expr, var, isChild, false); +} + +void Gdb_MI2::onExplorerForward() +{ + if(explorerHistoryPos >= explorerHistoryVars.GetCount() - 1) + return; + explorerHistoryPos++; + String expr = explorerHistoryExpressions[explorerHistoryPos]; + String var = explorerHistoryVars[explorerHistoryPos]; + bool isChild = explorerHistoryChilds[explorerHistoryPos]; + doExplore(expr, var, isChild, false); +} + +void Gdb_MI2::ExplorerMenu(Bar& bar) +{ +} +////////////////////////////////////////////////////////////////////////////////////////////////////// + +// create GDB process and initializes it +bool Gdb_MI2::Create(One _host, const String& exefile, const String& cmdline) +{ + host = _host; + dbg = host->StartProcess("gdb " + GetHostPath(exefile) + " --interpreter=mi -q"); + if(!dbg) { + Exclamation(t_("Error invoking gdb !")); + return false; + } + IdeSetBottom(*this); + IdeSetRight(disas); + + disas.AddFrame(regs); + disas.WhenCursor = THISBACK(DisasCursor); + disas.WhenFocus = THISBACK(DisasFocus); + + frame.WhenDrop = THISBACK(DropFrames); + frame <<= THISBACK(ShowFrame); + + threadSelector.WhenDrop = THISBACK(dropThreads); + threadSelector <<= THISBACK(showThread); + + watches.WhenAcceptEdit = THISBACK(SyncData); + tab <<= THISBACK(SyncData); + + // this one will allow asynchronous break of running app +// 2012-07-08 -- DISABLED because of GDB bugs... +// MICmd("gdb-set target-async 1"); + MICmd("gdb-set pagination off"); + +// Don't enable this one -- brings every sort of bugs with +// It was useful to issue Asynchronous break, but too many bugs +// to be useable +// MICmd("gdb-set non-stop on"); + +// MICmd("gdb-set interactive-mode off"); + + MICmd("gdb-set disassembly-flavor intel"); + MICmd("gdb-set exec-done-display off"); + MICmd("gdb-set annotate 1"); + MICmd("gdb-set height 0"); + MICmd("gdb-set width 0"); + MICmd("gdb-set confirm off"); + MICmd("gdb-set print asm-demangle"); + + // avoids debugger crash if caught inside ungrabbing function + // (don't solves all cases, but helps...) + MICmd("gdb-set unwindonsignal on"); + + if(!IsNull(cmdline)) + MICmd("gdb-set args " + cmdline); + + // enable pretty printing + SendPrettyPrinters(); + MICmd("enable-pretty-printing"); + + firstRun = true; + + return true; +} + +One Gdb_MI2Create(One host, const String& exefile, const String& cmdline) +{ + Gdb_MI2 *dbg = new Gdb_MI2; + if(!dbg->Create(host, exefile, cmdline)) + { + delete dbg; + return NULL; + } + return dbg; +}