From 212894be3d0e28fa301017e20810b0071e89f1e8 Mon Sep 17 00:00:00 2001 From: micio Date: Thu, 2 Feb 2012 12:09:26 +0000 Subject: [PATCH] Ide/Debuggers/Gdb_MI2 : added watches support git-svn-id: svn://ultimatepp.org/upp/trunk@4512 f0d560ea-af0d-0410-9eb7-867de7ffcac7 --- uppsrc/ide/Debuggers/Gdb_MI2.cpp | 2030 +++++++++++++++--------------- uppsrc/ide/Debuggers/Gdb_MI2.h | 348 ++--- 2 files changed, 1188 insertions(+), 1190 deletions(-) diff --git a/uppsrc/ide/Debuggers/Gdb_MI2.cpp b/uppsrc/ide/Debuggers/Gdb_MI2.cpp index a68773ac9..84a159bb0 100644 --- a/uppsrc/ide/Debuggers/Gdb_MI2.cpp +++ b/uppsrc/ide/Debuggers/Gdb_MI2.cpp @@ -1,1022 +1,1008 @@ -#include "Debuggers.h" - -#include "PrettyPrinters.brc" - -///////////////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC IDE INTERFACE -///////////////////////////////////////////////////////////////////////////////////////////////////////////// - -void Gdb_MI2::DebugBar(Bar& bar) -{ - bar.Add("Stop debugging", THISBACK(Stop)).Key(K_SHIFT_F5); - bar.Separator(); - 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(); - - // check wether we've got already a breakpoint here - // and remove it - MIValue brk = bps.FindBreakpoint(file, line); - if(!brk.IsEmpty()) - ASSERT(!MICmd("break-delete " + brk["number"]).IsError()); - - if(bp.IsEmpty()) - return true; - else if(bp[0] == 0xe) - return !MICmd("break-insert " + Format("%s:%d", file, line)).IsError(); - else - return !MICmd("break-insert " + Format("-c \"%s\" %s:%d", bp, file, line)).IsError(); -} - -bool Gdb_MI2::RunTo() -{ - String bi; - bool df = disas.HasFocus(); - if(df) - { - if(!disas.GetCursor()) - return false; - bi = Sprintf("*0x%X", disas.GetCursor()); - } - else - bi = BreakPos(IdeGetFileName(), IdeGetFileLine()); - - // sets a temporary breakpoint on cursor location - // it'll be cleared automatically on first stop - if(!TryBreak(IdeGetFileName(), IdeGetFileLine(), true)) - { - Exclamation(t_("No code at chosen location !")); - return false; - } - - Run(); - if(df) - disas.SetFocus(); - return true; -} - -void Gdb_MI2::Run() -{ - MIValue val; - if(firstRun) - val = MICmd("exec-run"); - else - val = MICmd("exec-continue"); - - 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::Stop() -{ - started = stopped = false; - if(dbg && dbg->IsRunning()) - dbg->Kill(); -} - -bool Gdb_MI2::IsFinished() -{ - return !dbg->IsRunning() && !IdeIsDebugLock(); -} - -bool Gdb_MI2::Tip(const String& exp, CodeEditor::MouseTip& mt) -{ - return false; -} - -///////////////////////////////////////////////////////////////////////////////////////////////////////////// -// CONSTRUCTOR / DESTRUCTOR -///////////////////////////////////////////////////////////////////////////////////////////////////////////// - -Gdb_MI2::Gdb_MI2() -{ - CtrlLayout(regs); - regs.Height(regs.GetLayoutSize().cy); - AddReg("eax", ®s.eax); - AddReg("ebx", ®s.ebx); - AddReg("ecx", ®s.ecx); - AddReg("edx", ®s.edx); - AddReg("esi", ®s.esi); - AddReg("edi", ®s.edi); - AddReg("ebp", ®s.ebp); - AddReg("esp", ®s.esp); - regs.Color(SColorLtFace); - regs.AddFrame(TopSeparatorFrame()); - regs.AddFrame(RightSeparatorFrame()); - - locals.NoHeader(); - locals.AddColumn("", 1); - locals.AddColumn("", 6); - watches.NoHeader(); - watches.AddColumn("", 1).Edit(watchedit); - watches.AddColumn("", 6); - watches.Inserting().Removing(); - autos.NoHeader(); - autos.AddColumn("", 1); - autos.AddColumn("", 6); - Add(tab.SizePos()); - tab.Add(watches.SizePos(), "Watches"); - tab.Add(locals.SizePos(), "Locals"); - tab.Add(autos.SizePos(), "Autos"); - Add(frame.HSizePos(200, 0).TopPos(2, EditField::GetStdHeight())); - frame.Ctrl::Add(dlock.SizePos()); - dlock = " Running.."; - dlock.SetFrame(BlackFrame()); - dlock.SetInk(Red); - dlock.NoTransparent(); - dlock.Hide(); - - CtrlLayoutOKCancel(quickwatch, "Quick watch"); - quickwatch.WhenClose = quickwatch.Breaker(IDCANCEL); - quickwatch.value.SetReadOnly(); - quickwatch.value.SetFont(Courier(12)); - 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 -///////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// lock/unlock debugger controls -void Gdb_MI2::Lock() -{ - IdeDebugLock(); - watches.Disable(); - locals.Disable(); - frame.Disable(); - dlock.Show(); -} - -void Gdb_MI2::Unlock() -{ - if(IdeDebugUnLock()) - { - watches.Enable(); - locals.Enable(); - frame.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; - - // parse result data - StringStream ss(output); - while(!ss.IsEof()) - { - String s = TrimBoth(ss.GetLine()); - - // check 'running' and 'stopped' async output - if(s.StartsWith("*running")) - { - started = true; - stopReason.Clear(); - continue; - } - else if(s.StartsWith("*stopped")) - { - stopped = true; - s = '{' + s.Mid(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) -{ - // 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 + 1; -} - -// 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(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::SetDisas(const String& text) -{ - disas.Clear(); - StringStream ss(text); - while(!ss.IsEof()) { - String ln = ss.GetLine(); - CParser p(ln); - if(p.Char2('0', 'x')) { - dword adr = p.IsNumber(16) ? p.ReadNumber(16) : 0; - String code; - String args; - int level = 0; - while(!p.IsEof()) { - if(p.Char(':') && level == 0) - break; - else - if(p.Char('<')) - level++; - else - if(p.Char('>')) - level--; - else - p.GetChar(); - } - p.Spaces(); - if(p.IsId()) { - code = p.ReadId(); - for(;;) { - if(p.Spaces()) - args.Cat(' '); - if(p.IsEof()) - break; - if(p.Char2('0', 'x')) { - dword adr = 0; - if(p.IsNumber(16)) - adr = p.ReadNumber(16); - String fname; - bool usefname = false; - if(p.Char('<')) { - const char *b = p.GetPtr(); - int level = 1; - usefname = true; - while(!p.IsEof()) { - if(p.Char('>') && --level == 0) { - fname = String(b, p.GetPtr() - 1); - break; - } - if(p.Char('<')) - level++; - if(p.Char('+')) - usefname = false; - else { - p.GetChar(); - p.Spaces(); - } - } - } - args << (usefname ? fname : Sprintf("0x%X", adr)); - disas.AddT(adr); - } - else - if(p.Id("DWORD")) - args.Cat("dword "); - else - if(p.Id("WORD")) - args.Cat("word "); - else - if(p.Id("BYTE")) - args.Cat("byte "); - else - if(p.Id("PTR")) - args.Cat("ptr "); - else - args.Cat(p.GetChar()); - } - } - disas.Add(adr, code, args); - } - } -} -*/ - -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); - } - } -} - -// sync ide display with breakpoint position -void Gdb_MI2::SyncIde(bool fr) -{ - // 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 - 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); - } - } - - // i we got an address, we can sync the assembly position - if(fInfo.Find("addr") >= 0) - { - } - - // get the arguments for current frame - MIValue fArgs = MICmd(Format("stack-list-arguments 1 %d %d", level, level))["stack-args"][0]["args"]; - - // setup droplist - frame.Clear(); - frame.Add(0, FormatFrame(fInfo, fArgs)); - frame <<= 0; - - SyncLocals(); - 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)); -} - -// check for stop reason -void Gdb_MI2::CheckStopReason(void) -{ - ASSERT(!stopReason.IsEmpty()); - String reason = stopReason["reason"]; - 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", stopReason["frame"]); - SyncIde(); - } - else - { - LogFrame(Format("Stopped, reason '%s'", reason), stopReason["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) - { - Exclamation(t_("Failed to start application")); - return; - } - - Lock(); - ReadGdb(false); - while(dbg && !stopped) - { - GuiSleep(20); - Ctrl::ProcessEvents(); - ReadGdb(false); - } - Unlock(); - if(b) - disas.SetFocus(); - - firstRun = false; - if(stopped) - CheckStopReason(); - started = stopped = false; - IdeActivateBottom(); -} - -// setup ide cursor based on disassembler one -void Gdb_MI2::DisasCursor() -{ -/* - if(!disas.HasFocus()) - return; - int line; - String file; - adr_t addr; - if(ParsePos(FastCmd(Sprintf("info line *0x%X", disas.GetCursor())), file, line, addr)) - IdeSetDebugPos(file, line - 1, DbgImg::DisasPtr(), 1); - disas.SetFocus(); -*/ -} - -// reset ide default cursor image when disassembler loose focus -void Gdb_MI2::DisasFocus() -{ -// if(!disas.HasFocus()) -// IdeSetDebugPos(file, 0, 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"].Get()); - 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"]; - 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"]; - frameList.AssertArray(); - - // get the arguments for all frames, values just for simple types - MIValue frameArgs = MICmd("stack-list-arguments 1")["stack-args"]; - frameArgs.AssertArray(); - - // 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; - MICmd(Format("stack-select-frame %d", i)); - SyncIde(i); -} - -// 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 loc = MICmd("stack-list-variables 0")["variables"]; - 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(localVarNames[iVar]) < 0) - { - localVarNames.Pop(); - localVarExpressions.Pop(); - localVarValues.Pop(); - localVarTypes.Pop(); - } - } - - // then we shall add missing variables - for(int iLoc = 0; iLoc < locIdx.GetCount(); iLoc++) - { - if(localVarNames.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"]); - } - } - - // and finally, we do an update to refresh changed variables - MIValue updated = MICmd("var-update 2 *")["changelist"]; - for(int iUpd = 0; iUpd < updated.GetCount(); iUpd++) - { - String varName = updated[iUpd]["name"]; - int iVar; - if( (iVar = localVarNames.Find(varName)) < 0) - continue; - if( updated[iUpd].Find("value") < 0) - continue; - localVarValues[iVar] = updated[iUpd]["value"]; - } -} - - -void Gdb_MI2::SyncLocals() -{ - // update local vars cache, if needed - UpdateLocalVars(); - - // reload ide control - VectorMap prev = DataMap(locals); - locals.Clear(); - for(int i = 0; i < localVarNames.GetCount(); i++) - locals.Add(localVarExpressions[i], "(" + localVarTypes[i] + ")" + localVarValues[i]); - MarkChanged(prev, locals); -} - -// sync watches treectrl -void Gdb_MI2::SyncWatches() -{ -/* - VectorMap prev = DataMap(watches); - for(int i = 0; i < watches.GetCount(); i++) - watches.Set(i, 1, Print(watches.Get(i, 0))); - MarkChanged(prev, watches); -*/ -} - -// sync auto vars treectrl -void Gdb_MI2::SyncAutos() -{ -/* - VectorMap prev = DataMap(autos); - autos.Clear(); - 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) { - String val = Print(exp); - if(!IsNull(val) && val.Find('(') < 0) - autos.Add(exp, val); - } - } - p.SkipTerm(); - } - autos.Sort(); - MarkChanged(prev, autos); -*/ -} - -// sync data tabs, depending on which tab is shown -void Gdb_MI2::SyncData() -{ - switch(tab.Get()) { - case 0: - SyncWatches(); - break; - - case 1: - SyncLocals(); - break; - - case 2: - SyncAutos(); - 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; - } - 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); - DLOG(MICmd("interpreter-exec console \"source " + fName + "\"").Dump()); - FileDelete(fName); -} - -// opens quick watch dialog -void Gdb_MI2::QuickWatch() -{ -/* - for(;;) { - int q = quickwatch.Run(); - if(q == IDCANCEL) - break; - FastCmd("set print pretty on"); - String s = FastCmd("p " + (String)~quickwatch.expression); - const char *a = strchr(s, '='); - if(a) { - a++; - while(*a == ' ') - a++; - quickwatch.value <<= a; - quickwatch.expression.AddHistory(); - } - else - quickwatch.value <<= s; - FastCmd("set print pretty off"); - } - 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(); -} - -// 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); - - watches.WhenAcceptEdit = THISBACK(SyncData); - tab <<= THISBACK(SyncData); - - 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"); - - 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 "PrettyPrinters.brc" + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC IDE INTERFACE +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void Gdb_MI2::DebugBar(Bar& bar) +{ + bar.Add("Stop debugging", THISBACK(Stop)).Key(K_SHIFT_F5); + bar.Separator(); + 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(); + + // check wether we've got already a breakpoint here + // and remove it + MIValue brk = bps.FindBreakpoint(file, line); + if(!brk.IsEmpty()) + ASSERT(!MICmd("break-delete " + brk["number"]).IsError()); + + if(bp.IsEmpty()) + return true; + else if(bp[0] == 0xe) + return !MICmd("break-insert " + Format("%s:%d", file, line)).IsError(); + else + return !MICmd("break-insert " + Format("-c \"%s\" %s:%d", bp, file, line)).IsError(); +} + +bool Gdb_MI2::RunTo() +{ + String bi; + bool df = disas.HasFocus(); + if(df) + { + if(!disas.GetCursor()) + return false; + bi = Sprintf("*0x%X", disas.GetCursor()); + } + else + bi = BreakPos(IdeGetFileName(), IdeGetFileLine()); + + // sets a temporary breakpoint on cursor location + // it'll be cleared automatically on first stop + if(!TryBreak(IdeGetFileName(), IdeGetFileLine(), true)) + { + Exclamation(t_("No code at chosen location !")); + return false; + } + + Run(); + if(df) + disas.SetFocus(); + return true; +} + +void Gdb_MI2::Run() +{ + MIValue val; + if(firstRun) + val = MICmd("exec-run"); + else + val = MICmd("exec-continue"); + + 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::Stop() +{ + started = stopped = false; + if(dbg && dbg->IsRunning()) + dbg->Kill(); +} + +bool Gdb_MI2::IsFinished() +{ + return !dbg->IsRunning() && !IdeIsDebugLock(); +} + +bool Gdb_MI2::Tip(const String& exp, CodeEditor::MouseTip& mt) +{ + return false; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// CONSTRUCTOR / DESTRUCTOR +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + +Gdb_MI2::Gdb_MI2() +{ + CtrlLayout(regs); + regs.Height(regs.GetLayoutSize().cy); + AddReg("eax", ®s.eax); + AddReg("ebx", ®s.ebx); + AddReg("ecx", ®s.ecx); + AddReg("edx", ®s.edx); + AddReg("esi", ®s.esi); + AddReg("edi", ®s.edi); + AddReg("ebp", ®s.ebp); + AddReg("esp", ®s.esp); + regs.Color(SColorLtFace); + regs.AddFrame(TopSeparatorFrame()); + regs.AddFrame(RightSeparatorFrame()); + + locals.NoHeader(); + locals.AddColumn("", 1); + locals.AddColumn("", 6); + watches.NoHeader(); + watches.AddColumn("", 1).Edit(watchedit); + watches.AddColumn("", 6); + watches.Inserting().Removing(); + autos.NoHeader(); + autos.AddColumn("", 1); + autos.AddColumn("", 6); + Add(tab.SizePos()); + tab.Add(watches.SizePos(), "Watches"); + tab.Add(locals.SizePos(), "Locals"); + tab.Add(autos.SizePos(), "Autos"); + Add(frame.HSizePos(200, 0).TopPos(2, EditField::GetStdHeight())); + frame.Ctrl::Add(dlock.SizePos()); + dlock = " Running.."; + dlock.SetFrame(BlackFrame()); + dlock.SetInk(Red); + dlock.NoTransparent(); + dlock.Hide(); + + CtrlLayoutOKCancel(quickwatch, "Quick watch"); + quickwatch.WhenClose = quickwatch.Breaker(IDCANCEL); + quickwatch.value.SetReadOnly(); + quickwatch.value.SetFont(Courier(12)); + 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 +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// lock/unlock debugger controls +void Gdb_MI2::Lock() +{ + IdeDebugLock(); + watches.Disable(); + locals.Disable(); + frame.Disable(); + dlock.Show(); +} + +void Gdb_MI2::Unlock() +{ + if(IdeDebugUnLock()) + { + watches.Enable(); + locals.Enable(); + frame.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; + + // parse result data + StringStream ss(output); + while(!ss.IsEof()) + { + String s = TrimBoth(ss.GetLine()); + + // check 'running' and 'stopped' async output + if(s.StartsWith("*running")) + { + started = true; + stopReason.Clear(); + continue; + } + else if(s.StartsWith("*stopped")) + { + stopped = true; + s = '{' + s.Mid(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) +{ + // 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 + 1; +} + +// 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(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); + } + } +} + +// sync ide display with breakpoint position +void Gdb_MI2::SyncIde(bool fr) +{ + // 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 + 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); + } + } + + // i we got an address, we can sync the assembly position + if(fInfo.Find("addr") >= 0) + { + } + + // get the arguments for current frame + MIValue fArgs = MICmd(Format("stack-list-arguments 1 %d %d", level, level))["stack-args"][0]["args"]; + + // setup 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)); +} + +// check for stop reason +void Gdb_MI2::CheckStopReason(void) +{ + ASSERT(!stopReason.IsEmpty()); + String reason = stopReason["reason"]; + 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", stopReason["frame"]); + SyncIde(); + } + else + { + LogFrame(Format("Stopped, reason '%s'", reason), stopReason["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) + { + Exclamation(t_("Failed to start application")); + return; + } + + Lock(); + ReadGdb(false); + while(dbg && !stopped) + { + GuiSleep(20); + Ctrl::ProcessEvents(); + ReadGdb(false); + } + Unlock(); + if(b) + disas.SetFocus(); + + firstRun = false; + if(stopped) + CheckStopReason(); + started = stopped = false; + IdeActivateBottom(); +} + +// setup ide cursor based on disassembler one +void Gdb_MI2::DisasCursor() +{ +/* + if(!disas.HasFocus()) + return; + int line; + String file; + adr_t addr; + if(ParsePos(FastCmd(Sprintf("info line *0x%X", disas.GetCursor())), file, line, addr)) + IdeSetDebugPos(file, line - 1, DbgImg::DisasPtr(), 1); + disas.SetFocus(); +*/ +} + +// reset ide default cursor image when disassembler loose focus +void Gdb_MI2::DisasFocus() +{ +// if(!disas.HasFocus()) +// IdeSetDebugPos(file, 0, 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"].Get()); + 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"]; + 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"]; + frameList.AssertArray(); + + // get the arguments for all frames, values just for simple types + MIValue frameArgs = MICmd("stack-list-arguments 1")["stack-args"]; + frameArgs.AssertArray(); + + // 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; + MICmd(Format("stack-select-frame %d", i)); + SyncIde(i); +} + +// update variables on demand (locals, watches....) +void Gdb_MI2::UpdateVars(void) +{ + MIValue updated = MICmd("var-update 2 *")["changelist"]; + for(int iUpd = 0; iUpd < updated.GetCount(); iUpd++) + { + String varName = updated[iUpd]["name"]; + int iVar; + + // local variables + if( (iVar = localVarNames.Find(varName)) < 0) + continue; + if( updated[iUpd].Find("value") < 0) + continue; + localVarValues[iVar] = updated[iUpd]["value"]; + + // watches + if( (iVar = watchesNames.Find(varName)) < 0) + continue; + if( updated[iUpd].Find("value") < 0) + continue; + watchesValues[iVar] = updated[iUpd]["value"]; + + } +} + +// 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 loc = MICmd("stack-list-variables 0")["variables"]; + 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"]); + } + } + + // 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.IsError() || var.IsEmpty()) + 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(); +} + +void Gdb_MI2::SyncLocals() +{ + // update local vars cache, if needed + UpdateLocalVars(); + + // reload ide control + VectorMap prev = DataMap(locals); + locals.Clear(); + for(int i = 0; i < localVarNames.GetCount(); i++) + locals.Add(localVarExpressions[i], "(" + localVarTypes[i] + ")" + localVarValues[i]); + MarkChanged(prev, locals); +} + +// sync watches treectrl +void Gdb_MI2::SyncWatches() +{ + // update local watches cache, if needed + UpdateWatches(); + + // re-fill the control + VectorMap prev = DataMap(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, 1, "(" + watchesTypes[idx] + ")" + watchesValues[idx]); + else + watches.Set(i, 1, t_("")); + } + MarkChanged(prev, watches); +} + +// sync auto vars treectrl +void Gdb_MI2::SyncAutos() +{ +/* + VectorMap prev = DataMap(autos); + autos.Clear(); + 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) { + String val = Print(exp); + if(!IsNull(val) && val.Find('(') < 0) + autos.Add(exp, val); + } + } + p.SkipTerm(); + } + autos.Sort(); + MarkChanged(prev, autos); +*/ +} + +// sync data tabs, depending on which tab is shown +void Gdb_MI2::SyncData() +{ + switch(tab.Get()) { + case 0: + SyncWatches(); + break; + + case 1: + SyncLocals(); + break; + + case 2: + SyncAutos(); + 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; + } + 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); +} + +// opens quick watch dialog +void Gdb_MI2::QuickWatch() +{ +/* + for(;;) { + int q = quickwatch.Run(); + if(q == IDCANCEL) + break; + FastCmd("set print pretty on"); + String s = FastCmd("p " + (String)~quickwatch.expression); + const char *a = strchr(s, '='); + if(a) { + a++; + while(*a == ' ') + a++; + quickwatch.value <<= a; + quickwatch.expression.AddHistory(); + } + else + quickwatch.value <<= s; + FastCmd("set print pretty off"); + } + 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(); +} + +// 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); + + watches.WhenAcceptEdit = THISBACK(SyncData); + tab <<= THISBACK(SyncData); + + 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"); + + 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; +} diff --git a/uppsrc/ide/Debuggers/Gdb_MI2.h b/uppsrc/ide/Debuggers/Gdb_MI2.h index 15912730c..47052aa5d 100644 --- a/uppsrc/ide/Debuggers/Gdb_MI2.h +++ b/uppsrc/ide/Debuggers/Gdb_MI2.h @@ -1,168 +1,180 @@ -#ifndef _ide_Debuggers_Gdb_MI2_h_ -#define _ide_Debuggers_Gdb_MI2_h_ - -#include "MIValue.h" - -#define LAYOUTFILE -#include - -class Gdb_MI2 : public Debugger, public ParentCtrl -{ - private: - - One host; - One dbg; - - bool firstRun; - - // the disassembler window - DbgDisas disas; - - // the registers pane - FrameBottom > regs; - - // the quick watch dialog - WithGdb_MI2QuickwatchLayout quickwatch; - - EditString watchedit; - DropList frame; - TabCtrl tab; - ArrayCtrl locals; - ArrayCtrl watches; - ArrayCtrl autos; - Label dlock; - - Vector regname; - Vector