From 638dabd428dfa061c39f5402ed9cbcd38c9e5cc9 Mon Sep 17 00:00:00 2001 From: micio Date: Tue, 25 Feb 2014 16:34:59 +0000 Subject: [PATCH] Ide/Debuggers/Gdb_MI2 : refactored and added optional multithread version git-svn-id: svn://ultimatepp.org/upp/trunk@6951 f0d560ea-af0d-0410-9eb7-867de7ffcac7 --- uppsrc/ide/Debuggers/Dbg.cpp | 4 + uppsrc/ide/Debuggers/Debuggers.upp | 15 +- uppsrc/ide/Debuggers/Gdb_MI2.cpp | 988 ++++++++++++----------- uppsrc/ide/Debuggers/Gdb_MI2.h | 101 ++- uppsrc/ide/Debuggers/Gdb_MI2Eval.cpp | 304 ------- uppsrc/ide/Debuggers/Gdb_MI2Explore.cpp | 207 +++-- uppsrc/ide/Debuggers/Gdb_MI2Gdb.cpp | 313 +++++++ uppsrc/ide/Debuggers/MIValue.cpp | 15 + uppsrc/ide/Debuggers/TypeSimplify.h | 12 +- uppsrc/ide/Debuggers/UppSimplifiers.icpp | 782 +++++++++--------- uppsrc/ide/Debuggers/VarItem.cpp | 307 +++++++ uppsrc/ide/Debuggers/VarItem.h | 118 +++ 12 files changed, 1909 insertions(+), 1257 deletions(-) delete mode 100644 uppsrc/ide/Debuggers/Gdb_MI2Eval.cpp create mode 100644 uppsrc/ide/Debuggers/Gdb_MI2Gdb.cpp create mode 100644 uppsrc/ide/Debuggers/VarItem.cpp create mode 100644 uppsrc/ide/Debuggers/VarItem.h diff --git a/uppsrc/ide/Debuggers/Dbg.cpp b/uppsrc/ide/Debuggers/Dbg.cpp index 455522425..2097f3523 100644 --- a/uppsrc/ide/Debuggers/Dbg.cpp +++ b/uppsrc/ide/Debuggers/Dbg.cpp @@ -51,6 +51,8 @@ void RedDisplay::Paint(Draw& w, const Rect& r, const Value& q, Color ink, Color VectorMap DataMap(const ArrayCtrl& data) { + GuiLock __; + VectorMap m; for(int i = 0; i < data.GetCount(); i++) m.Add(data.Get(i, 0), data.Get(i, 1)); @@ -59,6 +61,8 @@ VectorMap DataMap(const ArrayCtrl& data) void MarkChanged(const VectorMap& m, ArrayCtrl& data) { + GuiLock __; + for(int i = 0; i < data.GetCount(); i++) { int q = m.Find(data.Get(i, 0)); if(q >= 0 && m[q] != data.Get(i, 1)) diff --git a/uppsrc/ide/Debuggers/Debuggers.upp b/uppsrc/ide/Debuggers/Debuggers.upp index 00496292f..5abb4e448 100644 --- a/uppsrc/ide/Debuggers/Debuggers.upp +++ b/uppsrc/ide/Debuggers/Debuggers.upp @@ -9,20 +9,23 @@ file Debuggers.h, Debuggers.iml, Gdb.lay, - MIValue.h, - MIValue.cpp, - TypeSimplify.h, - TypeSimplify.cpp, - UppSimplifiers.icpp, Terminal.cpp, Disas.cpp, Dbg.cpp, Gdb.cpp, + GDB_MI2 readonly separator, + MIValue.h, + MIValue.cpp, + VarItem.h, + VarItem.cpp, + TypeSimplify.h, + TypeSimplify.cpp, Gdb_MI2.lay, Gdb_MI2.h, Gdb_MI2Explore.cpp, - Gdb_MI2Eval.cpp, + Gdb_MI2Gdb.cpp, Gdb_MI2.cpp, + UppSimplifiers.icpp, PDB readonly separator, Pdb.h, cvconst.h, diff --git a/uppsrc/ide/Debuggers/Gdb_MI2.cpp b/uppsrc/ide/Debuggers/Gdb_MI2.cpp index 2c047eff6..f01734f3f 100644 --- a/uppsrc/ide/Debuggers/Gdb_MI2.cpp +++ b/uppsrc/ide/Debuggers/Gdb_MI2.cpp @@ -18,6 +18,8 @@ void WatchEdit::HighlightLine(int line, Vector& h, int pos) // (avoid re-painting and resetting scroll if not needed) void Gdb_MI2::FillPane(ArrayCtrl &pane, Index const &nam, Vector const &val) { + GuiLock __; + int oldCount = pane.GetCount(); int newCount = nam.GetCount(); if(newCount < oldCount) @@ -56,9 +58,14 @@ void Gdb_MI2::DebugBar(Bar& bar) bar.Add(b, "Copy dissassembly", THISBACK(CopyDisas)); } +static int CharFilterReSlash2(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); + String file = Filter(host->GetHostPath(NormalizePath(filename)), CharFilterReSlash2); // gets all breakpoints MIValue bps = GetBreakpoints(); @@ -116,14 +123,9 @@ bool Gdb_MI2::RunTo() 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"); @@ -157,23 +159,8 @@ void Gdb_MI2::Run() } #ifdef PLATFORM_POSIX -// sends a ctrl-c to debugger, returns true on success, false otherwise -bool Gdb_MI2::InterruptDebugger(void) -{ - int killed = 0; - for(int iProc = 0; iProc < processes.GetCount(); iProc++) - if(kill(processes[iProc], SIGINT) == 0) - killed++; - return killed; -} -#endif - void Gdb_MI2::AsyncBrk() { -#ifdef PLATFORM_POSIX - // data must be refreshed - dataSynced = false; - // send interrupt command to debugger if(!InterruptDebugger()) return; @@ -187,12 +174,77 @@ void Gdb_MI2::AsyncBrk() if(stopped) break; } -#endif } +#endif + +#ifdef flagMT +// mutex-protected functions +bool Gdb_MI2::IsThreadRunning(void) +{ + bool b; + INTERLOCKED_(mutex) { + b = threadRunning; + } + return b; +} + +void Gdb_MI2::IncThreadRunning() +{ + INTERLOCKED_(mutex) { + threadRunning++; + } +} + +void Gdb_MI2::DecThreadRunning() +{ + INTERLOCKED_(mutex) { + threadRunning--; + } +} + +bool Gdb_MI2::IsStopThread(void) +{ + bool b; + INTERLOCKED_(mutex) { + b = stopThread; + } + return b; +} + +void Gdb_MI2::SetStopThread(bool b) +{ + INTERLOCKED_(mutex) { + stopThread = b; + } +} +#endif + +#ifdef flagMT +// quick exit from service thread when called and 'stopThread' is set +// throws a BreakExc exception +void Gdb_MI2::RaiseIfStop(void) +{ + INTERLOCKED_(mutex) { + if(!IsMainThread() && stopThread) + throw BreakExc(); + } +} +#else +void Gdb_MI2::RaiseIfStop(void) +{ +} +#endif + void Gdb_MI2::Stop() { stopped = true; +#ifdef flagMT + // stop debugger threads and wait for termination + SetStopThread(true); + while(IsThreadRunning()) + Sleep(20); +#endif if(dbg && dbg->IsRunning()) dbg->Kill(); } @@ -295,7 +347,11 @@ Gdb_MI2::Gdb_MI2() watches.WhenLeftDouble = THISBACK1(onExploreExpr, &watches); watches.EvenRowColor(); watches.OddRowColor(); - watches.WhenUpdateRow = THISBACK1(SyncWatches, MIValue()); +#ifdef flagMT + watches.WhenUpdateRow = THISBACK(SyncWatches); +#else + watches.WhenUpdateRow = THISBACK1(SyncWatches, Vector()); +#endif int c = EditField::GetStdHeight(); explorer.AddColumn("", 1); @@ -350,12 +406,16 @@ Gdb_MI2::Gdb_MI2() stopped = false; periodic.Set(-50, THISBACK(Periodic)); - dataSynced = false; + + localSynced = false; + thisSynced = false; + watchesSynced = false; + explorerSynced = false; #ifdef flagMT - // no running debug threads - runningThreads = 0; - stopThreads = false; + // no running debug thread + threadRunning = false; + stopThread = false; #endif } @@ -430,238 +490,10 @@ void Gdb_MI2::Unlock() } } -// 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; - } - - // catch process start/stop and store/remove pids - else if(s.StartsWith("=thread-group-started,id=")) - { - String id, pid; - int i = s.Find("id="); - if(i < 0) - continue; - i += 4; - while(s[i] && s[i] != '"') - id.Cat(s[i++]); - i = s.Find("pid="); - if(i < 0) - continue; - i += 5; - while(s[i] && s[i] != '"') - pid.Cat(s[i++]); - - processes.Add(id, atoi(pid)); - } - - else if(s.StartsWith("=thread-group-exited,id=")) - { - String id; - int i = s.Find("id="); - if(i < 0) - continue; - i += 4; - while(s[i] && s[i] != '"') - id.Cat(s[i++]); - i = processes.Find(id); - if(i >= 0) - processes.Remove(i); - } - - // 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, s; - MIValue res; - - if(wait) - { - // blocking path - // waits for 3 seconds max, then return empty value - // some commands (in particular if they return python exceptions) - // have a delay between returned exception text and command result - // so we shall wait up to the final (gdb) - int retries = 13 * 50; - while(dbg && retries--) - { - dbg->Read(s); - output += s; - if(TrimRight(s).EndsWith("(gdb)")) - break; - Sleep(20); - continue; - } - } - else if(dbg) - dbg->Read(output); - 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; + return String().Cat() << Filter(host->GetHostPath(NormalizePath(file)), CharFilterReSlash2) << ":" << line; } // set breakpoint @@ -785,7 +617,10 @@ void Gdb_MI2::SyncDisas(MIValue &fInfo, bool fr) void Gdb_MI2::SyncIde(bool fr) { // kill pending update callbacks +#ifndef flagMT timeCallback.Kill(); + exploreCallback.Kill(); +#endif // get current frame info and level MIValue fInfo = MICmd("stack-info-frame")["frame"]; @@ -826,6 +661,7 @@ void Gdb_MI2::SyncIde(bool fr) } } + // get the arguments for current frame MIValue fArgs = MICmd(Format("stack-list-arguments 1 %d %d", level, level))["stack-args"][0]["args"]; if(!fArgs.IsError() && !fArgs.IsEmpty()) @@ -839,8 +675,15 @@ void Gdb_MI2::SyncIde(bool fr) // sync disassembly SyncDisas(fInfo, fr); + // update vars only on idle times +#ifdef flagMT + SyncData(); + SyncExplorer(); +#else timeCallback.Set(500, THISBACK(SyncData)); + exploreCallback.Set(480, THISBACK1(SyncExplorer, Vector())); +#endif } // logs frame data on console @@ -854,47 +697,14 @@ void Gdb_MI2::LogFrame(String const &msg, MIValue &frame) 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) { // data must be synced on stop... - dataSynced = false; + localSynced = false; + thisSynced = false; + watchesSynced = false; + explorerSynced = false; // we need to store stop reason BEFORE interrupting all other // threads, otherwise it'll be lost @@ -907,15 +717,6 @@ void Gdb_MI2::CheckStopReason(void) 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(); @@ -1133,234 +934,491 @@ struct CapitalLess }; // update local variables on demand -void Gdb_MI2::SyncLocals(MIValue val) +#ifdef flagMT +void Gdb_MI2::SyncLocals() { - bool firstCall = false; - static VectorMap prev; - - // if not done, start first evaluation step - if(val.IsEmpty()) + IncThreadRunning(); + + try { - prev = DataMap(locals); + VectorMap prev = DataMap(locals); + + RaiseIfStop(); // get local vars names - MIValue locs = MICmd("stack-list-variables 1"); + MIValue locs = MICmd("stack-list-variables 0"); + if(!locs.IsTuple() || locs.Find("variables") < 0) + throw BreakExc(); + + // variables are returned as a tuple, named "variables" + // containing a array of variables + MIValue lLocs = locs["variables"]; + if(!lLocs.IsArray()) + throw BreakExc(); + + Vector localVars; + for(int iLoc = 0; iLoc < lLocs.GetCount(); iLoc++) + { + MIValue &lLoc = lLocs[iLoc]; + String name = lLoc["name"]; + + // skip 'this', we've a page for it + if(name == "this") + continue; + + localVars.Add(VarItem(this, name)); + } + + // create a VarItem for each variable and put evaluation results + // inside local arrays + localExpressions.Clear(); + localValues.Clear(); + + for(int iLoc = 0; iLoc < localVars.GetCount(); iLoc++) + { + VarItem &vItem = localVars[iLoc]; + localExpressions << vItem.evaluableExpression; + localValues << vItem.value; + } + + // update locals pane + FillPane(locals, localExpressions, localValues); + + // autos variables can come from members or locals... + SyncAutos(); + + // simplify batch + for(int iLoc = 0; iLoc < localVars.GetCount(); iLoc++) + { + while(localVars[iLoc].Simplify()) + RaiseIfStop(); + + VarItem &v = localVars[iLoc]; + localValues[iLoc] = v.value; + { + GuiLock __; + locals.Set(iLoc, 1, v.value); + } + } + + localSynced = true; + } + catch(...) + { + localSynced = false; + } + + DecThreadRunning(); +} +#else +void Gdb_MI2::SyncLocals(Vector localVars) +{ + static VectorMap prev; + if(localVars.IsEmpty()) + { + prev = DataMap(members);; + + // get local vars names + MIValue locs = MICmd("stack-list-variables 0"); if(!locs.IsTuple() || locs.Find("variables") < 0) return; - + // variables are returned as a tuple, named "variables" // containing a array of variables MIValue lLocs = locs["variables"]; if(!lLocs.IsArray()) return; - - // as values are in string format we must parse them - // so we simply build a big string with all variables - // and feed into MIValue parser - String s = "{"; + for(int iLoc = 0; iLoc < lLocs.GetCount(); iLoc++) { MIValue &lLoc = lLocs[iLoc]; String name = lLoc["name"]; - + // skip 'this', we've a page for it if(name == "this") continue; - s << name << "=" << lLoc["value"] << ","; - } - if(s.EndsWith(",")) - s = s.Left(s.GetCount()-1); - s << "}"; - val = s; - - val.PackNames(); - AddAttribs("", val); - val.FixArrays(); - - TypeSimplify(val, false); - - firstCall = true; - } - - bool more = TypeSimplify(val, true); - - // collect variables names and values - CollectVariables(val, localExpressions, localValues, localHints); - - // update locals pane - FillPane(locals, localExpressions, localValues); - if(more) - // more evaluations are needed, respawn the function with a delay - // big delay on first respawn to allow quick stepping without lag - timeCallback.Set(firstCall ? 2000 : 200, THISBACK1(SyncLocals, val)); - else - // when finished, mark changed values - MarkChanged(prev, locals); + localVars.Add(VarItem(this, name)); + } + + // create a VarItem for each variable and put evaluation results + // inside local arrays + localExpressions.Clear(); + localValues.Clear(); + + for(int iLoc = 0; iLoc < localVars.GetCount(); iLoc++) + { + VarItem &vItem = localVars[iLoc]; + localExpressions << vItem.evaluableExpression; + localValues << vItem.value; + } + + // update locals pane + FillPane(locals, localExpressions, localValues); + + // autos variables can come from members or locals... + SyncAutos(); + + timeCallback.Set(500, THISBACK1(SyncLocals, localVars)); + return; + } + + // simplify batch + for(int iLoc = 0; iLoc < localVars.GetCount(); iLoc++) + { + if(localVars[iLoc].Simplify()) + { + locals.Set(iLoc, 1, localVars[iLoc].value); + SyncAutos(); + timeCallback.Set(100, THISBACK1(SyncLocals, localVars)); + return; + } + } + + for(int iLoc = 0; iLoc < localVars.GetCount(); iLoc++) + locals.Set(iLoc, 1, localVars[iLoc].value); + + // when finished, mark changed values + MarkChanged(prev, locals); + localSynced = true; } +#endif // update 'this' inspector data -void Gdb_MI2::SyncThis(MIValue val) +#ifdef flagMT +void Gdb_MI2::SyncThis() { - bool firstCall = false; - static VectorMap prev; - - // if not done, start first evaluation step - if(val.IsEmpty()) + IncThreadRunning(); + + try { - val = Evaluate("*this", false); - prev = DataMap(members); + VectorMap prev = DataMap(members); + + RaiseIfStop(); - // this is the first evaluation step - // just simple types -- lag some seconds to full evaluation - // which is quite slow - firstCall = true; - } + // create a vari object and evaluate '*this' expression + VarItem vItem(this); + vItem.Evaluate("*this"); - // deep evaluation step - bool more = TypeSimplify(val, true); + RaiseIfStop(); + + // get children + Vector children = vItem.GetChildren(); + + RaiseIfStop(); + + thisExpressions.Clear(); + thisShortExpressions.Clear(); + thisValues.Clear(); + for(int iVar = 0; iVar < children.GetCount(); iVar++) + { + VarItem &v = children[iVar]; + thisExpressions << v.evaluableExpression; + String exp = v.shortExpression; + int dot = exp.Find('.'); + if(dot >= 0) + exp = exp.Mid(dot + 1); + thisShortExpressions << exp; + thisValues << v.value; + } - // collect variables names and values - CollectVariables(val, thisExpressions, thisValues, thisHints); + RaiseIfStop(); - // strip the (*this) from variables names - thisShortExpressions.Clear(); - for(int iExpr = 0; iExpr < thisExpressions.GetCount(); iExpr++) - thisShortExpressions.Add(thisExpressions[iExpr].Mid(8)); + // update 'this' pane + FillPane(members, thisShortExpressions, thisValues); - // update 'this' pane - FillPane(members, thisShortExpressions, thisValues); + // autos variables can come from members or locals... + SyncAutos(); - if(more) - // more evaluations are needed, respawn the function with a delay - // big delay on first respawn to allow quick stepping without lag - timeCallback.Set(firstCall ? 2000 : 200, THISBACK1(SyncThis, val)); - else + // simplify batch + for(int iVar = 0; iVar < children.GetCount(); iVar++) + { + RaiseIfStop(); + while(children[iVar].Simplify()) + RaiseIfStop(); + + VarItem &v = children[iVar]; + + thisValues[iVar] = v.value; + { + GuiLock __; + members.Set(iVar, 1, v.value); + } + SyncAutos(); + } + // when finished, mark changed values MarkChanged(prev, members); + thisSynced = true; + } + catch(...) + { + thisSynced = false; + } + + DecThreadRunning(); } +#else +void Gdb_MI2::SyncThis(Vector children) +{ + static VectorMap prev; + if(children.IsEmpty()) + { + prev = DataMap(members);; + + // create a vari object and evaluate '*this' expression + VarItem vItem(this, "*this"); + + // get children + children = vItem.GetChildren(); + + // fill 'this' memners expressions, short expressions and values + thisExpressions.Clear(); + thisShortExpressions.Clear(); + thisValues.Clear(); + for(int iVar = 0; iVar < children.GetCount(); iVar++) + { + VarItem &v = children[iVar]; + thisExpressions << v.evaluableExpression; + String exp = v.shortExpression; + int dot = exp.Find('.'); + if(dot >= 0) + exp = exp.Mid(dot + 1); + thisShortExpressions << exp; + thisValues << v.value; + } + + // update 'this' pane + FillPane(members, thisShortExpressions, thisValues); + + // autos variables can come from members or locals... + SyncAutos(); + + timeCallback.Set(500, THISBACK1(SyncThis, children)); + return; + } + + // simplify batch + for(int iVar = 0; iVar < children.GetCount(); iVar++) + { + if(children[iVar].Simplify()) + { + members.Set(iVar, 1, children[iVar].value); + SyncAutos(); + timeCallback.Set(100, THISBACK1(SyncThis, children)); + return; + } + } + + for(int iVar = 0; iVar < children.GetCount(); iVar++) + members.Set(iVar, 1, children[iVar].value); + + // when finished, mark changed values + MarkChanged(prev, members); + thisSynced = true; +} +#endif // sync auto vars treectrl void Gdb_MI2::SyncAutos() { - VectorMap prev = DataMap(autos); - autos.Clear(); - - // read expressions around cursor line - CParser p(autoLine); - while(!p.IsEof()) - { - if(p.IsId()) + INTERLOCKED { + GuiLock __; + + autos.Clear(); + + // read expressions around cursor line + CParser p(autoLine); + while(!p.IsEof()) { - String exp = p.ReadId(); - for(;;) + if(p.IsId()) { - if(p.Char('.') && p.IsId()) - exp << '.'; + String exp = p.ReadId(); + for(;;) + { + if(p.Char('.') && p.IsId()) + exp << '.'; + else + if(p.Char2('-', '>') && p.IsId()) + exp << "->"; + else + break; + exp << p.ReadId(); + } + int idx = localExpressions.Find(exp); + if(idx < 0) + { + idx = thisShortExpressions.Find(exp); + if(idx >= 0) + autos.Add(exp, thisValues[idx]); + } else - if(p.Char2('-', '>') && p.IsId()) - exp << "->"; - else - break; - exp << p.ReadId(); + autos.Add(exp, localValues[idx]); } - int idx = localExpressions.Find(exp); - if(idx >= 0) - autos.Add(exp, localValues[idx]); + p.SkipTerm(); } - p.SkipTerm(); + autos.Sort(); } - autos.Sort(); - MarkChanged(prev, autos); } // sync watches treectrl -void Gdb_MI2::SyncWatches(MIValue val) +#ifdef flagMT +void Gdb_MI2::SyncWatches() { - static VectorMap prev; - bool firstCall = false; - - // on first cycle, we fast evaluate all expressions - // without going deep, and put all results into an array - if(val.IsEmpty()) + // re-enter if called from main thread + if(IsMainThread()) { - prev = DataMap(watches); - - for(int i = 0; i < watches.GetCount(); i++) + debugThread.Start(THISBACK(SyncWatches)); + return; + } + + INTERLOCKED { + IncThreadRunning(); + try { - String expr = watches.Get(i,0); - MIValue res; - res.Set(""); - MIValue valExpr = MICmd("data-evaluate-expression " + expr); - - if(valExpr.IsTuple() && valExpr.Find("value") >= 0) + VectorMap prev = DataMap(watches); + + // get watches expressions and create VarItems for them + // and put results inside watches control + Vector watchesVars; + watchesExpressions.Clear(); + watchesValues.Clear(); + for(int iWatch = 0; iWatch < watches.GetCount(); iWatch++) { - MIValue const &tup = valExpr.Get("value"); - if(tup.IsString()) + String exp, val; { - String s = tup.ToString(); - res = s; + GuiLock __; + exp = watches.Get(iWatch, 0); + } + watchesExpressions << exp; + watchesVars.Add(VarItem(this, exp)); + val = watchesVars.Top().value; + watchesValues.Add(val); + { + GuiLock __; + watches.Set(iWatch, 1, val); } } - res.PackNames(); - AddAttribs("", res); - res.FixArrays(); - - val.Add(res); + + // simplify batch + for(int iWatch = 0; iWatch < watchesVars.GetCount(); iWatch++) + { + RaiseIfStop(); + while(watchesVars[iWatch].Simplify()) + RaiseIfStop(); + + VarItem &v = watchesVars[iWatch]; + + watchesValues[iWatch] = v.value; + { + GuiLock __; + watches.Set(iWatch, 1, v.value); + } + } + + // when finished, mark changed values + MarkChanged(prev, watches); + watchesSynced = true; } - firstCall = true; + catch(...) + { + watchesSynced = false; + } + DecThreadRunning(); } - - bool more = false; - - // simplify loop, returns when at least one simplify step happens - for(int iVal = 0; iVal < val.GetCount(); iVal++) - { - MIValue &v = val[iVal]; - more |= TypeSimplify(v, true); - if(more) - break; - } - // collects all results - watchesValues.Clear(); - for(int iVal = 0; iVal < val.GetCount(); iVal++) - watchesValues << CollectVariablesShort(val[iVal]); - - // fill expressions with ctrl expr contents - watchesExpressions.Clear(); - for(int iVal = 0; iVal < watchesValues.GetCount(); iVal++) - watchesExpressions << watches.Get(iVal, 0); - - // update locals pane - FillPane(watches, watchesExpressions, watchesValues); - - if(more) - // more evaluations are needed, respawn the function with a delay - // big delay on first respawn to allow quick stepping without lag - timeCallback.Set(firstCall ? 2000 : 200, THISBACK1(SyncWatches, val)); - else - // when finished, mark changed values - MarkChanged(prev, watches); } +#else +void Gdb_MI2::SyncWatches(Vector watchesVars) +{ + static VectorMap prev; + if(watchesVars.IsEmpty()) + { + prev = DataMap(watches);; + + // get watches expressions and create VarItems for them + // and put results inside watches control + watchesExpressions.Clear(); + watchesValues.Clear(); + for(int iWatch = 0; iWatch < watches.GetCount(); iWatch++) + { + String exp = watches.Get(iWatch, 0); + watchesExpressions << exp; + watchesVars.Add(VarItem(this, exp)); + String val = watchesVars.Top().value; + watchesValues.Add(val); + watches.Set(iWatch, 1, val); + } + + timeCallback.Set(500, THISBACK1(SyncWatches, watchesVars)); + return; + } + + // simplify batch + for(int iWatch = 0; iWatch < watchesVars.GetCount(); iWatch++) + { + if(watchesVars[iWatch].Simplify()) + { + watches.Set(iWatch, 1, watchesVars[iWatch].value); + timeCallback.Set(100, THISBACK1(SyncWatches, watchesVars)); + return; + } + } + + for(int iWatch = 0; iWatch < watchesVars.GetCount(); iWatch++) + watches.Set(iWatch, 1, watchesVars[iWatch].value); + + // when finished, mark changed values + MarkChanged(prev, watches); + watchesSynced = true; +} +#endif // sync data tabs, depending on which tab is shown void Gdb_MI2::SyncData() { - if(dataSynced) - return; +#ifdef flagMT // update stored 'this' member data // also used for tooltips and 'this' pane page - SyncThis(); + if(IsStopThread()) + return; + INTERLOCKED_(mutex) { + if(!thisSynced) + debugThread.Start(THISBACK(SyncThis)); + } // updated locals variables - SyncLocals(); + if(IsStopThread()) + return; + INTERLOCKED_(mutex) { + if(!localSynced) + debugThread.Start(THISBACK(SyncLocals)); + } - SyncAutos(); + //update watches + if(IsStopThread()) + return; + INTERLOCKED_(mutex) { + if(!watchesSynced) + debugThread.Start(THISBACK(SyncWatches)); + } - SyncWatches(); +#else + + // update stored 'this' member data + // also used for tooltips and 'this' pane page + if(!thisSynced) + SyncThis(); - dataSynced = true; + // updated locals variables + if(!localSynced) + SyncLocals(); + + // update watches + if(!watchesSynced) + SyncWatches(); + +#endif } // watches arrayctrl key handling @@ -1540,7 +1598,7 @@ void Gdb_MI2::CopyDisas() bool Gdb_MI2::Create(One _host, const String& exefile, const String& cmdline, bool console) { host = _host; - dbg = host->StartProcess(GdbCommand(console) + GetHostPath(exefile) + " --interpreter=mi -q"); + dbg = host->StartProcess(GdbCommand(console) + GetHostPath(exefile) + " --interpreter=mi2" /*" -q"*/); if(!dbg) { Exclamation(t_("Error invoking gdb !")); return false; @@ -1603,6 +1661,8 @@ bool Gdb_MI2::Create(One _host, const String& exefile, const String& cmdli // (don't solves all cases, but helps...) MICmd("gdb-set unwindonsignal on"); +// MICmd("gdb-set interactive on"); + if(!IsNull(cmdline)) MICmd("gdb-set args " + cmdline); diff --git a/uppsrc/ide/Debuggers/Gdb_MI2.h b/uppsrc/ide/Debuggers/Gdb_MI2.h index c4309958c..bc07b842a 100644 --- a/uppsrc/ide/Debuggers/Gdb_MI2.h +++ b/uppsrc/ide/Debuggers/Gdb_MI2.h @@ -2,6 +2,7 @@ #define _ide_Debuggers_Gdb_MI2_h_ #include "MIValue.h" +#include "VarItem.h" class WatchEdit : public LineEdit { @@ -11,6 +12,13 @@ class WatchEdit : public LineEdit #define LAYOUTFILE #include +// abort command exception - used to stop non-main threads +struct BreakExc : public Exc +{ + BreakExc() : Exc("break") {} + +}; + class Gdb_MI2 : public Debugger, public ParentCtrl { private: @@ -22,24 +30,39 @@ class Gdb_MI2 : public Debugger, public ParentCtrl // multithread support #ifdef flagMT // numbr of running debug threads - int runningThreads; + int threadRunning; // flag to signal threads to stop - bool stopThreads; + bool stopThread; // mutex and thead object Mutex mutex; Thread debugThread; + + // mutex-protected functions + bool IsThreadRunning(void); + void IncThreadRunning(); + void DecThreadRunning(); + + bool IsStopThread(void); + void SetStopThread(bool b); #endif - // debug break support -- ONLY POSIX, by now #ifdef PLATFORM_POSIX + // debug break support -- ONLY POSIX, by now bool InterruptDebugger(void); #endif + +#ifdef PLATFORM_POSIX + // current command break support -- ONLY POSIX, by now + // used to speed up operations in MT mode + bool InterruptCommand(void); +#endif // used to post and kill timed callbacks TimeCallback timeCallback; + TimeCallback exploreCallback; One host; One dbg; @@ -84,9 +107,11 @@ class Gdb_MI2 : public Debugger, public ParentCtrl void doExplore(String const &expr, bool appendHistory); + // explorer expressions and values + IndexexplorerExpressions; + VectorexplorerValues; Index explorerHistoryExpressions; int explorerHistoryPos; - Index explorerChildExpressions; Label dlock; @@ -167,18 +192,38 @@ class Gdb_MI2 : public Debugger, public ParentCtrl // sync auto vars treectrl void SyncAutos(); - + +#ifdef flagMT // sync local variables pane - void SyncLocals(MIValue val = MIValue()); + void SyncLocals(void); // Sync 'this' inspector data - void SyncThis(MIValue val = MIValue()); + void SyncThis(void); // sync watches treectrl - void SyncWatches(MIValue val = MIValue()); + void SyncWatches(void); + + // sync explorer pane + void SyncExplorer(); +#else + // sync local variables pane + void SyncLocals(Vector localVars = Vector()); + + // Sync 'this' inspector data + void SyncThis(Vector children = Vector()); + + // sync watches treectrl + void SyncWatches(Vector children = Vector()); + + // sync explorer pane + void SyncExplorer(Vector children = Vector()); +#endif // sync data tabs, depending on which tab is shown - bool dataSynced; + bool localSynced; + bool thisSynced; + bool watchesSynced; + bool explorerSynced; void SyncData(); // sync ide display with breakpoint position @@ -229,27 +274,6 @@ class Gdb_MI2 : public Debugger, public ParentCtrl String GetHostPath(const String& path) { return host->GetHostPath(path); } String GetLocalPath(const String& path) { return host->GetLocalPath(path); } - // now we scan the result and add some info - // so, for example, if we find tuple like this one: - // data = simplevalue - // this will be modified as - // data = { value = simplevalue, expr = evaluable_expression } - // and for a complex value - // data = { some=complex, not_simple=val } - // woll be modified as - // data = { = { some=complex, not_simple=val }, = evaluable_expression } - // More attributes will be added by type simplifier phase - void AddAttribs(String const &expr, MIValue &valExpr); - - // collects evaluated variables got with Evaluate - // hints are used to choose the visualizer when deep-inspecting members - // 0 for simple values, 1 for arrays, 2 for map - void CollectVariables(MIValue &val, Index &exprs, Vector &vals, Vector &hints); - - // collect evaluated variables got with Evaluate - // into a single-line string for short display - String CollectVariablesShort(MIValue &val); - // fill a pane with data from a couple of arrays without erasing it first // (avoid re-painting and resetting scroll if not needed) void FillPane(ArrayCtrl &pane, Index const &nam, Vector const &val); @@ -279,20 +303,9 @@ class Gdb_MI2 : public Debugger, public ParentCtrl // sends an MI command and get answer back MIValue MICmd(const char *cmdLine); - // known types simplifier - // takes a MIValue from '-data-evaluate-expression' command and try - // do simplify diplay of known types - // with deep = false it does just type simplification, no deep evaluation of containers - // with deep = true it does ONE deep evaluation step - // returns true if more deep evaluation steps are needed, false otherwise - bool TypeSimplify(MIValue &val, bool deep); - - // variable inspection support - // returns a MIValue with inspected data and some info fields added - // and known types simplified and cathegorized - // unknown and simple types are left as they are - // deep is true if we shall have a complete sub-elements evaluation - MIValue Evaluate(String expr, bool deep = false); + // quick exit from service thread when called and 'stopThread' is set + // throws a BreakExc exception + void RaiseIfStop(void); }; #endif diff --git a/uppsrc/ide/Debuggers/Gdb_MI2Eval.cpp b/uppsrc/ide/Debuggers/Gdb_MI2Eval.cpp deleted file mode 100644 index f5e4ea7ad..000000000 --- a/uppsrc/ide/Debuggers/Gdb_MI2Eval.cpp +++ /dev/null @@ -1,304 +0,0 @@ -#include "Debuggers.h" -#include - -#include "TypeSimplify.h" - -// now we scan the result and add some info -// so, for example, if we find tuple like this one: -// data = simplevalue -// this will be modified as -// data = { value = simplevalue, expr = evaluable_expression } -// and for a complex value -// data = { some=complex, not_simple=val } -// woll be modified as -// data = { = { some=complex, not_simple=val }, = evaluable_expression } -// More attributes will be added by type simplifier phase -void Gdb_MI2::AddAttribs(String const &expr, MIValue &valExpr) -{ - if(valExpr.IsTuple()) - { - for(int i = 0; i < valExpr.GetCount(); i++) - { - String nam = valExpr.GetKey(i); - String nExpr; - if(!nam.StartsWith("<")) - { - if(expr != "") - nExpr = expr + "." + nam; - else - nExpr = nam; - } - else - nExpr = expr; - MIValue v = valExpr[i]; - valExpr[i].Clear(); - valExpr[i].Add(SIMPLIFY_VALUE, v); - valExpr[i].Add(SIMPLIFY_EXPR, nExpr); - AddAttribs(nExpr, valExpr[i][SIMPLIFY_VALUE]); - } - } - else if(valExpr.IsArray()) - { - for(int i = 0; i < valExpr.GetCount(); i++) - AddAttribs(expr, valExpr[i]); - } -} - -bool Gdb_MI2::TypeSimplify(MIValue &val, bool deep) -{ - bool needMore = false; - if(val.IsTuple()) - { - for(int i = 0; i < val.GetCount(); i++) - { - // root of variable -- contains VALUE, EXPRESSION and will add HINTS - MIValue &vRoot = val[i]; - - // value part of variable - MIValue &v = vRoot[SIMPLIFY_VALUE]; - - if(v.IsTuple() && v.GetCount()) - { - String key = v.GetKey(0); - if(key.StartsWith(""); - needMore |= nm; - } - else - needMore |= TypeSimplify(v, deep); - } - else - needMore |= TypeSimplify(v, deep); - } - else - needMore |= TypeSimplify(v, deep); - } - } - else if(val.IsArray()) - { - for(int i = 0; i < val.GetCount(); i++) - { - // encapsulate each element inside a tuple - MIValue tuple; - tuple.Add("", val[i]); - - // simplify - needMore |= TypeSimplify(tuple, deep); - - // de-encapsulate the element - val[i] = tuple[0]; - if(deep && needMore) - return needMore; - } - } - return needMore; -} - -// variable inspection support -// returns a MIValue with inspected data and some info fields added -// and known types simplified and cathegorized -// unknown and simple types are left as they are -// if deep is true, partial evaluation of containers is done -MIValue Gdb_MI2::Evaluate(String expr, bool deep) -{ - // add parhentesis around expression... gdb is dumb - expr = "(" + expr + ")"; - - // ask gdb to evaluate expression - // and gather result in a tuple - MIValue valExpr = MICmd("data-evaluate-expression " + expr); - - // return empty value on error - if(!valExpr.IsTuple()) - return MIValue(); - - if(valExpr.Find("value") >= 0) - { - // weird but the value is quoted, so must be parsed again... - MIValue const &tup = valExpr.Get("value"); - if(!tup.IsString()) - return MIValue(); - String s = tup.ToString(); - MIValue parsed(s); - - // pack tuple names--remove spaces - parsed.PackNames(); - -//RLOG(parsed.Dump()); - AddAttribs(expr, parsed); - - // fix arrays - parsed.FixArrays(); - -//RLOG(parsed.Dump()); - // now we go through the type simplifier, which try to give simple representation - // for known types -- first step, just type simplification and NOT deep evaluation - bool needMore = TypeSimplify(parsed, false); - - // deep container evaluation on request - if(deep) - while(needMore) - needMore = TypeSimplify(parsed, true); -//RLOG(parsed.Dump()); - - // and finally return the cleaned evaluated data - return parsed; - } - - else if(valExpr.Find("variables") >= 0) - { -RLOG("ARRAY SIDE UNHANDLED...."); - return MIValue(); - } - else - { -RLOG("WEIRD STUFF HERE...."); - return MIValue(); - } -} - -// collects evaluated variables got with Evaluate -// hints are used to choose the visualizer when deep-inspecting members -// 0 for simple values, 1 for arrays, 2 for map -static void Collect0(MIValue &val, Index &exprs, Vector &vals, Vector &hints) -{ - if(!val.IsTuple()) - return; - - for(int i = 0; i < val.GetCount(); i++) - { - MIValue &v = val[i]; - - // variables still to be evaluated... - if(v.Find(SIMPLIFY_TEMPVAL) >= 0) - { - vals.Add(v[SIMPLIFY_TEMPVAL].ToString()); - exprs.Add(v[SIMPLIFY_EXPR].ToString()); - hints << 0; - } - else - { - MIValue &data = v[SIMPLIFY_VALUE]; - if(data.IsError()) - continue; - if(data.IsString()) - { - String d = data.ToString(); - if(!d.StartsWith("<")) - { - vals.Add(data.ToString()), - exprs.Add(v[SIMPLIFY_EXPR].ToString()); - String hint = v.Get(SIMPLIFY_HINT, SIMPLIFY_SIMPLE); - if(hint == SIMPLIFY_SIMPLE) - hints << 0; - else if(hint == SIMPLIFY_ARRAY) - hints << 1; - else if(hint == SIMPLIFY_MAP) - hints << 2; - else - hints << 0; - } - } - else if(data.IsTuple()) - Collect0(data, exprs, vals, hints); - } - } -} - -void Gdb_MI2::CollectVariables(MIValue &val, Index &exprs, Vector &vals, Vector &hints) -{ - exprs.Clear(); - vals.Clear(); - hints.Clear(); - - Collect0(val, exprs, vals, hints); -} - -static void CollectShort0(MIValue &val, String &s) -{ - if(val.IsTuple()) - { - for(int i = 0; i < val.GetCount(); i++) - { - MIValue &v = val[i]; - String expr = v[SIMPLIFY_EXPR].ToString(); - - // variables still to be evaluated... - if(v.Find(SIMPLIFY_TEMPVAL) >= 0) - s << expr << "=" << v[SIMPLIFY_TEMPVAL].ToString() << " , "; - else - { - MIValue &data = v[SIMPLIFY_VALUE]; - if(data.IsError()) - return; - if(data.IsString()) - { - // skip vtbls... quite useless - if(expr.StartsWith("_vptr.")) - return; - - String d = data.ToString(); - if(!d.StartsWith("<")) - s << v[SIMPLIFY_EXPR].ToString() << "=" << d << " , "; - } - else if(data.IsTuple()) - CollectShort0(data, s); - } - } - } - else if(val.IsArray()) - { - for(int i = 0; i < val.GetCount(); i++) - { - MIValue &v = val[i]; - - if(v.Find(SIMPLIFY_TEMPVAL) >= 0) - s << v[SIMPLIFY_TEMPVAL].ToString() << " , "; - else - { - MIValue &data = v[SIMPLIFY_VALUE]; - if(data.IsError()) - return; - if(data.IsString()) - { - String d = data.ToString(); - if(!d.StartsWith("<")) - s << d << " , "; - } - else if(data.IsTuple()) - CollectShort0(data, s); - } - } - } -} - -// collect evaluated variables got with Evaluate -// into a vector of single-line string for short display -String Gdb_MI2::CollectVariablesShort(MIValue &val) -{ - String s; - - CollectShort0(val, s); - if(s == "") - s = ""; - else if(s.EndsWith(" , ")) - s = s.Left(s.GetCount()-3); - return s; -} diff --git a/uppsrc/ide/Debuggers/Gdb_MI2Explore.cpp b/uppsrc/ide/Debuggers/Gdb_MI2Explore.cpp index 7a40dd777..6b9f079da 100644 --- a/uppsrc/ide/Debuggers/Gdb_MI2Explore.cpp +++ b/uppsrc/ide/Debuggers/Gdb_MI2Explore.cpp @@ -1,5 +1,154 @@ #include "Debuggers.h" -#include + +// sync explorer pane +#ifdef flagMT +void Gdb_MI2::SyncExplorer() +{ + // re-enter if called from main thread + if(IsMainThread()) + { + debugThread.Start(THISBACK(SyncExplorer)); + return; + } + + INTERLOCKED { + IncThreadRunning(); + + try + { + + VectorMap prev = DataMap(explorer); + + // get expression from editfield + String expr; + { + GuiLock __; + expr = explorerExprEdit; + } + + // create a vari object and evaluate '*this' expression + VarItem vItem(this); + vItem.Evaluate(expr); + + RaiseIfStop(); + + // get children if complex variable + Vector children; + if(vItem.kind == VarItem::COMPLEX) + children = vItem.GetChildren(); + else + children << vItem; + + RaiseIfStop(); + + // fill explorer memners expressions, short expressions and values + explorerExpressions.Clear(); + explorerValues.Clear(); + for(int iVar = 0; iVar < children.GetCount(); iVar++) + { + VarItem &v = children[iVar]; + explorerExpressions << v.shortExpression; + explorerValues << v.value; + } + + RaiseIfStop(); + + // update 'this' pane + FillPane(explorer, explorerExpressions, explorerValues); + + // simplify batch + for(int iVar = 0; iVar < children.GetCount(); iVar++) + { + RaiseIfStop(); + while(children[iVar].Simplify()) + RaiseIfStop(); + + VarItem &v = children[iVar]; + + explorerValues[iVar] = v.value; + { + GuiLock __; + explorer.Set(iVar, 1, v.value); + } + } + + // when finished, mark changed values + MarkChanged(prev, explorer); + + explorerSynced = true; + } + catch(...) + { + explorerSynced = false; + } + + DecThreadRunning(); + } +} +#else +void Gdb_MI2::SyncExplorer(Vector children) +{ + static VectorMap prev; + + if(children.IsEmpty()) + { + prev = DataMap(explorer); + + // get expression from editfield + String expr = explorerExprEdit; + if(expr.IsEmpty()) + { + explorerSynced = true; + return; + } + + // create a vari object and evaluate the expression + VarItem vItem(this, expr); + + // get children if complex variable + if(vItem.kind == VarItem::COMPLEX) + children = vItem.GetChildren(); + else + children << vItem; + + // fill explorer memners expressions, short expressions and values + explorerExpressions.Clear(); + explorerValues.Clear(); + for(int iVar = 0; iVar < children.GetCount(); iVar++) + { + VarItem &v = children[iVar]; + explorerExpressions << v.shortExpression; + explorerValues << v.value; + } + + // update 'this' pane + FillPane(explorer, explorerExpressions, explorerValues); + + exploreCallback.Set(500, THISBACK1(SyncExplorer, children)); + return; + } + + // simplify batch + for(int iVar = 0; iVar < children.GetCount(); iVar++) + { + if(children[iVar].Simplify()) + { + VarItem &v = children[iVar]; + explorer.Set(iVar, 1, v.value); + explorerValues[iVar] = v.value; + exploreCallback.Set(100, THISBACK1(SyncExplorer, children)); + return; + } + } + + for(int iVar = 0; iVar < children.GetCount(); iVar++) + explorer.Set(iVar, 1, children[iVar].value); + + // when finished, mark changed values + MarkChanged(prev, explorer); + explorerSynced = true; +} +#endif void Gdb_MI2::doExplore(String const &expr, bool appendHistory) { @@ -20,57 +169,9 @@ void Gdb_MI2::doExplore(String const &expr, bool appendHistory) explorerHistoryExpressions.Add(expr); } - // evaluate the expression, direct deep evaluation here - String s = ""; - MIValue valExpr = MICmd("data-evaluate-expression " + expr); - - if(valExpr.IsTuple() && valExpr.Find("value") >= 0) - { - MIValue const &tup = valExpr.Get("value"); - if(tup.IsString()) - s = tup.ToString(); - } + explorerSynced = false; + SyncExplorer(); - // special behaviour for pointers and references - // try to de-reference them - if(s.StartsWith("@0x") || s.StartsWith("0x")) - { - MIValue valExpr; - - if(s.StartsWith("@0x")) - // reference - valExpr = MICmd("data-evaluate-expression *&" + expr); - else - // pointer - valExpr = MICmd("data-evaluate-expression *" + expr); - - if(valExpr.IsTuple() && valExpr.Find("value") >= 0) - { - MIValue const &tup = valExpr.Get("value"); - if(tup.IsString()) - s = tup.ToString(); - } - } - - s = expr + "=" + s; - MIValue val(s); - - val.PackNames(); - AddAttribs("", val); - val.FixArrays(); - - bool more = TypeSimplify(val, false); - while(more) - more = TypeSimplify(val, true); - - // collect results - Vector vals; - Vector hints; - CollectVariables(val, explorerChildExpressions, vals, hints); - - // update locals pane - FillPane(explorer, explorerChildExpressions, vals); - // update history buttons visibility explorerBackBtn.Enable(explorerHistoryPos > 0); explorerForwardBtn.Enable(explorerHistoryPos < explorerHistoryExpressions.GetCount() - 1); @@ -115,8 +216,8 @@ void Gdb_MI2::onExplorerChild() int line = explorer.GetCursor(); if(line < 0) return; - if(line < explorerChildExpressions.GetCount()) - doExplore(explorerChildExpressions[line], true); + if(line < explorerExpressions.GetCount()) + doExplore(explorerExpressions[line], true); } void Gdb_MI2::onExplorerBack() diff --git a/uppsrc/ide/Debuggers/Gdb_MI2Gdb.cpp b/uppsrc/ide/Debuggers/Gdb_MI2Gdb.cpp new file mode 100644 index 000000000..0c9e9b130 --- /dev/null +++ b/uppsrc/ide/Debuggers/Gdb_MI2Gdb.cpp @@ -0,0 +1,313 @@ +#include "Debuggers.h" + +#ifdef PLATFORM_POSIX +// sends a ctrl-c to debugger, returns true on success, false otherwise +bool Gdb_MI2::InterruptDebugger(void) +{ + int killed = 0; + for(int iProc = 0; iProc < processes.GetCount(); iProc++) + if(kill(processes[iProc], SIGINT) == 0) + killed++; + return killed; +} +#endif + +#ifdef PLATFORM_POSIX +// current command break support -- ONLY POSIX, by now +// used to speed up operations in MT mode +bool Gdb_MI2::InterruptCommand(void) +{ + try + { + LocalProcess &proc = dynamic_cast(*dbg); + pid_t pid = proc.GetPid(); + bool res = (kill(pid, SIGINT) == 0); + return res; + } + catch(...) + { + return false; + } +} +#else +bool Gdb_MI2::InterruptCommand(void) +{ + return false; +} +#endif + +// 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; + } + + // catch process start/stop and store/remove pids + else if(s.StartsWith("=thread-group-started,id=")) + { + String id, pid; + int i = s.Find("id="); + if(i < 0) + continue; + i += 4; + while(s[i] && s[i] != '"') + id.Cat(s[i++]); + i = s.Find("pid="); + if(i < 0) + continue; + i += 5; + while(s[i] && s[i] != '"') + pid.Cat(s[i++]); + + processes.Add(id, atoi(pid)); + continue; + } + + else if(s.StartsWith("=thread-group-exited,id=")) + { + String id; + int i = s.Find("id="); + if(i < 0) + continue; + i += 4; + while(s[i] && s[i] != '"') + id.Cat(s[i++]); + i = processes.Find(id); + if(i >= 0) + processes.Remove(i); + 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, s; + MIValue res; + + // blocking path + // waits for 2 minutes max, then return empty value + // some commands (in particular if they return python exceptions) + // have a delay between returned exception text and command result + // so we shall wait up to the final (gdb) + bool stop = false; + int retries = 120 * 50; + while(dbg && --retries && !stop) + { + dbg->Read(s); + StringStream ss(s); + while(!ss.IsEof()) + { + String s2 = ss.GetLine(); + output << s2 << "\n"; + + // wait till (gdb) end marker appears + s2 = TrimBoth(s2); + if(s2 == "(gdb)" || s2 == "&\"quit\\n\"") + { + stop = true; + #ifdef flagMT + // exit if in service threa and thread is stoppint + if(!IsMainThread() && IsStopThread()) + throw BreakExc(); + #endif + } + } + + // non-blocking quick exit + if(!wait) + break; + + Sleep(20); + continue; + } + + 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) +{ + MIValue res; + +#ifdef flagMT + // on MT, we interrupt all non-main threads + // issued GDB commands (which normally can lag several seconds...) + // before issuing the command + if(IsMainThread() && IsThreadRunning()) + { + // signal all other threads to stop + SetStopThread(true); + + // interrupt any active GDB command + InterruptCommand(); + + // ugly hack, otherwise service thread can deadlock + // MUST CHECK THIS ONE.... + int n = LeaveGuiMutexAll(); + + // give some time to recover + do + { + Sleep(20); + } + while(IsThreadRunning()); + + // RE-ENTER GUI MUTEX -- SEE ABOVE... + EnterGuiMutex(n); + + // remove thread stopping flag + SetStopThread(false); + } + + // quick exit for service thread + if(!IsMainThread() && IsStopThread()) + throw BreakExc(); + + // lock other thread's access + INTERLOCKED { +#endif + + // 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"); + res = ReadGdb(); +#ifdef flagMT + } +#endif + return res; +} diff --git a/uppsrc/ide/Debuggers/MIValue.cpp b/uppsrc/ide/Debuggers/MIValue.cpp index 8940daa8c..18998e3b9 100644 --- a/uppsrc/ide/Debuggers/MIValue.cpp +++ b/uppsrc/ide/Debuggers/MIValue.cpp @@ -419,11 +419,26 @@ MIValue::MIValue(MIValue pick_ &v) MIValue::MIValue(String const &s) { Parse(s); + + // tuple with 1 element and unnamed key is a string + if(IsTuple() && tuple.GetCount() == 1 && tuple.GetKey(0) == "") + { + type = MIString; + string = tuple[0]; + tuple.Clear(); + } } MIValue &MIValue::operator=(String const &s) { Parse(s); + // tuple with 1 element and unnamed key is a string + if(IsTuple() && tuple.GetCount() == 1 && tuple.GetKey(0) == "") + { + type = MIString; + string = tuple[0]; + tuple.Clear(); + } return *this; } diff --git a/uppsrc/ide/Debuggers/TypeSimplify.h b/uppsrc/ide/Debuggers/TypeSimplify.h index c31e986ce..250199743 100644 --- a/uppsrc/ide/Debuggers/TypeSimplify.h +++ b/uppsrc/ide/Debuggers/TypeSimplify.h @@ -2,7 +2,7 @@ #define _ide_Debuggers_TypeSimplify_h_ #include "Debuggers.h" -#include +#include "VarItem.h" #define SIMPLIFY_EXPR "" #define SIMPLIFY_VALUE "" @@ -20,10 +20,12 @@ #define SIMPLIFY_MAP "" // Simplifier handler -// parameters: -// gdb handler to debugger object -- used mostly to inspect deeper -// expRoot gdb evaluable expression of 'val' expression root. The one used to get the 'val'. -typedef bool (*TYPE_SIMPLIFIER_HANDLER)(Gdb_MI2 &gdb, MIValue &val, bool deep); +// step is the simplifying step, used for arrays and maps +// step 0 -- base simplify, no deep evaluation of containers +// step i -- deep evaluation of element 'i' of container +// returns number of needed steps to complete optimization +// all this stuff is needed to allow gui to have priority on data display +typedef int (*TYPE_SIMPLIFIER_HANDLER)(VarItem &varItem, int step); void RegisterSimplifier(const char *pattern, TYPE_SIMPLIFIER_HANDLER handler); diff --git a/uppsrc/ide/Debuggers/UppSimplifiers.icpp b/uppsrc/ide/Debuggers/UppSimplifiers.icpp index 32ae96cbd..449d73f67 100644 --- a/uppsrc/ide/Debuggers/UppSimplifiers.icpp +++ b/uppsrc/ide/Debuggers/UppSimplifiers.icpp @@ -7,9 +7,19 @@ #define EVALDEEP_ARRAYMAP 5 #define EVALDEEP_INDEX 5 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SIMPLIFIERS MUST BE CODED AS STATE MACHINES -- THEY'LL BE CALLED MANY TIMES, WITH A 'step' PARAMETER +// STEP = 0 MEANS BASE SIMPLIFY AND CHECK IF MORE STEPS ARE NEEDED +// RETURN NEXT STEP, OR 0 IF NONE +// STEP = N MEANS A SIMPLIFY STEP +// RETURN NEXT STEP, OR 0 IF NONE +// THEY MUST CHANGE 'value' MEMBER OF PASSED VarItem object ON EACH STEP +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #define SLEN 15 #define LLEN 2 -static bool UppStringSimplify(Gdb_MI2 &gdb, MIValue &val, bool deep) +static int UppStringSimplify(VarItem &varItem, int step) { union { @@ -21,402 +31,412 @@ static bool UppStringSimplify(Gdb_MI2 &gdb, MIValue &val, bool deep) dword w[4]; qword q[2]; } u; - - // see Upp::String code for how it works.... - try - { - MIValue &v = val[SIMPLIFY_VALUE][0][0][0][0][0][SIMPLIFY_VALUE]; - MIValue &unn = v[1][SIMPLIFY_VALUE]; - - if(!v.IsTuple() || !unn.IsTuple()) - return false; - if(unn.Find("chr") < 0) - return false; - String chrs = unn["chr"][SIMPLIFY_VALUE]; - memcpy(u.chr, ~chrs, 16); - - bool isSmall = (u.chr[14] == 0); - String s; - if(isSmall) - { - byte len = u.chr[SLEN]; - s = chrs.Left(len); - } - else - { - if(unn.Find("ptr") < 0) - return false; - dword len = u.w[LLEN]; - s = unn["ptr"][SIMPLIFY_VALUE].ToString(); - - // strip address... - int i = s.Find('"'); - if(i >= 0) - { - s = s.Mid(i+1); - s = s.Left(s.GetCount()-1); - } - s = s.Left(len); - } - val[SIMPLIFY_VALUE].Set("\"" + s + "\""); - val.FindAdd(SIMPLIFY_HINT, SIMPLIFY_SIMPLE); - - // no need for further evaluation on this object - return false; - } - catch(...) - { - return false; - } -} - -static bool UppVectorSimplify(Gdb_MI2 &gdb, MIValue &val, bool deep) -{ - // if we're just doing first scan phase, signal that we need further evaluation later -#ifdef EVALDEEP - if(!deep) - return true; -#endif - try - { - MIValue &v = val[SIMPLIFY_VALUE]; - String vectorExpr = v["vector"][SIMPLIFY_EXPR]; - int items = atoi(v["items"][SIMPLIFY_VALUE].ToString()); - - val.FindAdd(SIMPLIFY_HINT, SIMPLIFY_ARRAY); - val.FindAdd(SIMPLIFY_START, "0"); - val.FindAdd(SIMPLIFY_COUNT, FormatInt(items)); - - // display max 5 elements of array, starting from 0 and ONLY if elements evaluate to a simple type - // otherwise display {...} string - // to do this, we shall ask gdb to evaluate the undelying vector - String vals; - int count = min(EVALDEEP_VECTOR, items); -//RLOG("COUNT : " << count); -#ifdef EVALDEEP - if(count) - { - vals = ": [ ... ]"; - String expr = vectorExpr + Format("[0]@%d", count); - MIValue vi = gdb.Evaluate(expr); -//RLOG(vi.Dump()); - if(vi.IsArray() && vi.GetCount() && vi[0][SIMPLIFY_VALUE].IsString()) - { - vals = ": [ "; - for(int i = 0; i < vi.GetCount(); i++) - vals = vals + vi[i][SIMPLIFY_VALUE].ToString() + " , "; - if(count < items) - vals << "... ]"; - else - vals = vals.Left(vals.GetCount() - 2) + "]"; - } - } -#else - if(count) - vals = ": [ ... ]"; -#endif - vals = Format("Upp::Vector with %d elements %s", items, vals); - val[SIMPLIFY_VALUE].Set(vals); - - // signal we've done a deep evaluation -#ifdef EVALDEEP - return true; -#else - return false; -#endif - } - catch(...) - { - return false; - } -} - -static bool UppVectorMapSimplify(Gdb_MI2 &gdb, MIValue &val, bool deep) -{ -//RLOG(val.Dump()); - // if we're just doing first scan phase, signal that we need further evaluation later -#ifdef EVALDEEP - if(!deep) - return true; -#endif - try - { - MIValue &v = val[SIMPLIFY_VALUE]; - - MIValue &keyPtr = v[1][0]["key"][0][1][0][0][0]; - String keyExpr = keyPtr["vector"][SIMPLIFY_EXPR].ToString(); - int items = atoi(keyPtr["items"][SIMPLIFY_VALUE].ToString()); - - MIValue &valPtr = v[1][0]["value"][SIMPLIFY_VALUE];; - String valExpr = valPtr["vector"][SIMPLIFY_EXPR].ToString(); - - val.FindAdd(SIMPLIFY_HINT, SIMPLIFY_MAP); - val.FindAdd(SIMPLIFY_START, "0"); - val.FindAdd(SIMPLIFY_COUNT, FormatInt(items)); - - - // display max 5 elements of map, starting from 0 and ONLY if elements evaluate to a simple type - // otherwise display {...} string - // to do this, we shall ask gdb to evaluate the undelying vector - String vals; - int count = min(EVALDEEP_VECTORMAP, items); -#ifdef EVALDEEP - if(count) - { - vals = ": { ... }"; - String kExpr = keyExpr + Format("[0]@%d", count); - MIValue viKey = gdb.Evaluate(kExpr); - if(viKey.IsArray() && viKey[0][SIMPLIFY_VALUE].IsString()) - { - String vExpr = valExpr + Format("[0]@%d", count); - MIValue viVal = gdb.Evaluate(vExpr); - if(viVal.IsArray() && viVal[0][SIMPLIFY_VALUE].IsString()) - { - vals = ": { "; - for(int i = 0; i < viKey.GetCount(); i++) - vals << "( " << viKey[i][SIMPLIFY_VALUE].ToString() << " , " << viVal[i][SIMPLIFY_VALUE].ToString() << " ) , "; - if(count < items) - vals << "... }"; - else - vals = vals.Left(vals.GetCount() - 2) + "}"; - } -//RLOG(vals); - } - } -#else - if(count) - vals = ": { ... }"; -#endif - vals = Format("Upp::VectorMap with %d elements %s", items, vals); - val[SIMPLIFY_VALUE].Set(vals); - - // signal we've done a deep evaluation -#ifdef EVALDEEP - return true; -#else - return false; -#endif - } - catch(...) - { - return false; - } -} - - -static bool UppArraySimplify(Gdb_MI2 &gdb, MIValue &val, bool deep) -{ -//RLOG(val.Dump()); - // if we're just doing first scan phase, signal that we need further evaluation later -#ifdef EVALDEEP - if(!deep) - return true; -#endif - try - { - MIValue &v = val[SIMPLIFY_VALUE]["vector"][SIMPLIFY_VALUE]; - - String vectorExpr = v["vector"][SIMPLIFY_EXPR]; - int items = atoi(v["items"][SIMPLIFY_VALUE].ToString()); - - val.FindAdd(SIMPLIFY_HINT, SIMPLIFY_ARRAY); - val.FindAdd(SIMPLIFY_START, "0"); - val.FindAdd(SIMPLIFY_COUNT, FormatInt(items)); - - // display max 5 elements of array, starting from 0 and ONLY if elements evaluate to a simple type - // otherwise display {...} string - // to do this, we shall ask gdb to evaluate the undelying vector - String vals; - int count = min(EVALDEEP_ARRAY, items); -#ifdef EVALDEEP - if(count) - { - // pre-fetch main array variable to speedup element request - gdb.MICmd("gdb-set variable $thearray=" + vectorExpr); - - // get array elements - vals = ": [ ... ]"; - String expr = "{"; - for(int i = 0; i < count; i++) - expr << Format("$thearray[%d][0]", i) << ","; - expr = expr.Left(expr.GetCount()-1) + "}"; - MIValue vi = gdb.Evaluate(expr); - - if(vi.IsArray() && vi.GetCount() && vi[0][SIMPLIFY_VALUE].IsString()) - { - vals = ": [ "; - for(int i = 0; i < vi.GetCount(); i++) - vals = vals + vi[i][SIMPLIFY_VALUE].ToString() + " , "; - if(count < items) - vals << "... ]"; - else - vals = vals.Left(vals.GetCount() - 2) + "]"; - } - } -#else - if(count) - vals = ": [ ... ]"; -#endif - vals = Format("Upp::Array with %d elements %s", items, vals); - val[SIMPLIFY_VALUE].Set(vals); - - // signal we've done a deep evaluation -#ifdef EVALDEEP - return true; -#else - return false; -#endif - } - catch(...) - { - return false; - } -} - -static bool UppArrayMapSimplify(Gdb_MI2 &gdb, MIValue &val, bool deep) -{ -//RLOG(val.Dump()); - // if we're just doing first scan phase, signal that we need further evaluation later -#ifdef EVALDEEP - if(!deep) - return true; -#endif - try - { - MIValue &v = val[SIMPLIFY_VALUE]; - - MIValue &keyPtr = v[1][0]["key"][0][1][0][0][0]; - String keyExpr = keyPtr["vector"][SIMPLIFY_EXPR].ToString(); - int items = atoi(keyPtr["items"][SIMPLIFY_VALUE].ToString()); - - MIValue &valPtr = v[1][0]["value"][SIMPLIFY_VALUE];; - String valExpr = valPtr["vector"][SIMPLIFY_VALUE]["vector"][SIMPLIFY_EXPR].ToString(); - - val.FindAdd(SIMPLIFY_HINT, SIMPLIFY_MAP); - val.FindAdd(SIMPLIFY_START, "0"); - val.FindAdd(SIMPLIFY_COUNT, FormatInt(items)); - - - // display max 3 elements of map, starting from 0 and ONLY if elements evaluate to a simple type - // otherwise display {...} string - // to do this, we shall ask gdb to evaluate the undelying vector - String vals; - int count = min(EVALDEEP_ARRAYMAP, items); -#ifdef EVALDEEP - if(count) - { - // pre-fetch main array variable to speedup element request - gdb.MICmd("gdb-set variable $thearray=" + valExpr); - - vals = ": { ... }"; - String kExpr = keyExpr + Format("[0]@%d", count); - MIValue viKey = gdb.Evaluate(kExpr); - if(viKey.IsArray() && viKey[0][SIMPLIFY_VALUE].IsString()) - { - String vExpr = "{"; - for(int i = 0; i < count; i++) - vExpr << Format("$thearray[%d][0]", i) << ","; - vExpr = vExpr.Left(vExpr.GetCount()-1) + "}"; - MIValue viVal = gdb.Evaluate(vExpr); - if(viVal.IsArray() && viVal[0][SIMPLIFY_VALUE].IsString()) - { - vals = ": { "; - for(int i = 0; i < viKey.GetCount(); i++) - vals << "( " << viKey[i][SIMPLIFY_VALUE].ToString() << " , " << viVal[i][SIMPLIFY_VALUE].ToString() << " ) , "; - if(count < items) - vals << "... }"; - else - vals = vals.Left(vals.GetCount() - 2) + "}"; - } -//RLOG(vals); - } - } -#else - if(count) - vals = ": { ... }"; -#endif - vals = Format("Upp::ArrayMap with %d elements %s", items, vals); - val[SIMPLIFY_VALUE].Set(vals); - - // signal we've done a deep evaluation -#ifdef EVALDEEP - return true; -#else - return false; -#endif - } - catch(...) + // see Upp::String code for how it works.... + MIValue val = varItem.EvaluateExpression(varItem.evaluableExpression + "." + "chr"); + if(!val.IsString()) + return 0; + String chrs = val.ToString(); + memcpy(u.chr, ~chrs, 16); + + bool isSmall = (u.chr[14] == 0); + String s; + if(isSmall) { - return false; + byte len = u.chr[SLEN]; + s = chrs.Left(len); } + else + { + dword len = u.w[LLEN]; + MIValue val = varItem.EvaluateExpression(varItem.evaluableExpression + "." + "ptr[0]@" + FormatInt(len)); + if(!val.IsString()) + return 0; + s = val.ToString(); + } + varItem.value = "\"" + s + "\""; + varItem.kind = VarItem::SIMPLE; + return 0; } -static bool UppIndexSimplify(Gdb_MI2 &gdb, MIValue &val, bool deep) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static int UppVectorSimplify(VarItem &varItem, int step) { -//RLOG(val.Dump()); + const char *placeHolder = " = [...]"; + + // setup item type + varItem.kind = VarItem::ARRAY; + // if we're just doing first scan phase, signal that we need further evaluation later #ifdef EVALDEEP - if(!deep) - return true; -#endif - try - { - MIValue &keyExpr = val[SIMPLIFY_VALUE][1][SIMPLIFY_VALUE]["key"][SIMPLIFY_VALUE]; - String vectorExpr = keyExpr["vector"][SIMPLIFY_EXPR]; - int items = atoi(keyExpr["items"][SIMPLIFY_VALUE].ToString()); - - val.FindAdd(SIMPLIFY_HINT, SIMPLIFY_ARRAY); - val.FindAdd(SIMPLIFY_START, "0"); - val.FindAdd(SIMPLIFY_COUNT, FormatInt(items)); - - // display max 5 elements of array, starting from 0 and ONLY if elements evaluate to a simple type - // otherwise display {...} string - // to do this, we shall ask gdb to evaluate the undelying vector - String vals; - int count = min(EVALDEEP_INDEX, items); -#ifdef EVALDEEP - if(count) - { - vals = ": [ ... ]"; - String expr = vectorExpr << Format("[0]@%d", count); - MIValue vi = gdb.Evaluate(expr); - if(vi.IsArray() && vi.GetCount() && vi[0][SIMPLIFY_VALUE].IsString()) - { - vals = ": [ "; - for(int i = 0; i < vi.GetCount(); i++) - vals = vals + vi[i][SIMPLIFY_VALUE].ToString() + " , "; - if(count < items) - vals << "... ]"; - else - vals = vals.Left(vals.GetCount() - 2) + "]"; - } - } + if(!step) + // next step is 1 + return 1; #else - if(count) - vals = ": [ ... ]"; + varItem.value = placeHolder; + return 0; #endif - vals = Format("Upp::Index with %d elements %s", items, vals); - val[SIMPLIFY_VALUE].Set(vals); + + // just getting items count... + if(step == 1) + { + // initialize default value + varItem.value = ""; + + // get items count + MIValue val = varItem.EvaluateExpression(varItem.evaluableExpression + ".items"); + if(val.IsError() || !val.IsString()) + return 0; + varItem.items = atoi(val.ToString()); + + // update value + varItem.value = Format("Upp::Vector with %d elements", varItem.items, ""); + + // if no elements, just quit + if(!varItem.items) + return 0; + return 2; + } + + int count = min(EVALDEEP_VECTOR, varItem.items); + + // start from item 0 + step -= 2; + + if(!step) + varItem.value << " = [ ]"; + + // fetch elements, check on first if they're SIMPLE, so displayable + VarItem vItem(&varItem.Debugger(), varItem.evaluableExpression + Format(".vector[%d]", step)); + if(!vItem) + { + varItem.value = " "; + return 0; + } + if(vItem.kind != VarItem::SIMPLE) + { + varItem.value = placeHolder; + return 0; + } + vItem.Simplify(); + const char *sep = step ? " , " : ""; + varItem.value = varItem.value.Left(varItem.value.GetCount() - 2) + sep + vItem.value + " ]"; + if(++step >= count) + return 0; + else + return step + 2; +} - // signal we've done a deep evaluation +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static int UppVectorMapSimplify(VarItem &varItem, int step) +{ + const char *placeHolder = " = {...}"; + + // setup item type + varItem.kind = VarItem::MAP; + + // if we're just doing first scan phase, signal that we need further evaluation later + // if we're just doing first scan phase, signal that we need further evaluation later #ifdef EVALDEEP - return true; + if(!step) + // next step is 1 + return 1; #else - return false; + varItem.value = placeHolder; + return 0; #endif - } - catch(...) + + // just getting items count... + if(step == 1) { - return false; + // initialize default value + varItem.value = ""; + + // get items count + MIValue val = varItem.EvaluateExpression(varItem.evaluableExpression + ".key.key.items"); + if(val.IsError() || !val.IsString()) + return 0; + varItem.items = atoi(val.ToString()); + + // update value + varItem.value = Format("Upp::VectorMap with %d elements", varItem.items, ""); + + // if no elements, just quit + if(!varItem.items) + return 0; + return 2; } + + int count = min(EVALDEEP_VECTORMAP, varItem.items); + + // start from item 0 + step -= 2; + + if(!step) + varItem.value << " = { }"; + + // fetch elements, check on first if they're SIMPLE, so displayable + VarItem kItem(&varItem.Debugger(), varItem.evaluableExpression + Format(".key.key.vector[%d]", step)); + if(!kItem) + { + varItem.value = " "; + return 0; + } + // for complex types, just return placeholder + if(kItem.kind != VarItem::SIMPLE) + { + varItem.value = placeHolder; + return 0; + } + kItem.Simplify(); + + VarItem vItem(&varItem.Debugger(), varItem.evaluableExpression + Format(".value.vector[%d]", step)); + if(!vItem) + { + varItem.value = " "; + return 0; + } + // for complex types, just return placeholder + if(vItem.kind != VarItem::SIMPLE) + { + varItem.value = placeHolder; + return true; + } + vItem.Simplify(); + + const char *sep = step ? " , " : ""; + varItem.value = varItem.value.Left(varItem.value.GetCount() - 2) + sep + "(" + kItem.value + " , " + vItem.value + ") }"; + if(++step >= count) + return 0; + else + return step + 2; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static int UppArraySimplify(VarItem &varItem, int step) +{ + const char *placeHolder = " = [...]"; + + // setup item type + varItem.kind = VarItem::ARRAY; + + // if we're just doing first scan phase, signal that we need further evaluation later +#ifdef EVALDEEP + if(!step) + // next step is 1 + return 1; +#else + varItem.value = placeHolder; + return 0; +#endif + + // just getting items count... + if(step == 1) + { + // initialize default value + varItem.value = ""; + + // get items count + MIValue val = varItem.EvaluateExpression(varItem.evaluableExpression + ".vector.items"); + if(val.IsError() || !val.IsString()) + return 0; + varItem.items = atoi(val.ToString()); + + // update value + varItem.value = Format("Upp::Array with %d elements", varItem.items, ""); + + // if no elements, just quit + if(!varItem.items) + return 0; + return 2; + } + + int count = min(EVALDEEP_VECTOR, varItem.items); + + // start from item 0 + step -= 2; + + if(!step) + varItem.value << " = [ ]"; + + // fetch elements, check on first if they're SIMPLE, so displayable + VarItem vItem(&varItem.Debugger(), varItem.evaluableExpression + Format(".vector.vector[%d][0]", step)); + if(!vItem) + { + varItem.value = " "; + return 0; + } + if(vItem.kind != VarItem::SIMPLE) + { + varItem.value = placeHolder; + return 0; + } + vItem.Simplify(); + const char *sep = step ? " , " : ""; + varItem.value = varItem.value.Left(varItem.value.GetCount() - 2) + sep + vItem.value + " ]"; + if(++step >= count) + return 0; + else + return step + 2; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static int UppArrayMapSimplify(VarItem &varItem, int step) +{ + const char *placeHolder = " = {...}"; + + // setup item type + varItem.kind = VarItem::MAP; + + // if we're just doing first scan phase, signal that we need further evaluation later + // if we're just doing first scan phase, signal that we need further evaluation later +#ifdef EVALDEEP + if(!step) + // next step is 1 + return 1; +#else + varItem.value = placeHolder; + return 0; +#endif + + // just getting items count... + if(step == 1) + { + // initialize default value + varItem.value = ""; + + // get items count + MIValue val = varItem.EvaluateExpression(varItem.evaluableExpression + ".key.key.items"); + if(val.IsError() || !val.IsString()) + return 0; + varItem.items = atoi(val.ToString()); + + // update value + varItem.value = Format("Upp::ArrayMap with %d elements", varItem.items, ""); + + // if no elements, just quit + if(!varItem.items) + return 0; + return 2; + } + + int count = min(EVALDEEP_VECTORMAP, varItem.items); + + // start from item 0 + step -= 2; + + if(!step) + varItem.value << " = { }"; + + // fetch elements, check on first if they're SIMPLE, so displayable + VarItem kItem(&varItem.Debugger(), varItem.evaluableExpression + Format(".key.key.vector[%d]", step)); + if(!kItem) + { + varItem.value = " "; + return 0; + } + // for complex types, just return placeholder + if(kItem.kind != VarItem::SIMPLE) + { + varItem.value = placeHolder; + return 0; + } + kItem.Simplify(); + + VarItem vItem(&varItem.Debugger(), varItem.evaluableExpression + Format(".value.vector.vector[%d][0]", step)); + if(!vItem) + { + varItem.value = " "; + return 0; + } + // for complex types, just return placeholder + if(vItem.kind != VarItem::SIMPLE) + { + varItem.value = placeHolder; + return true; + } + vItem.Simplify(); + + const char *sep = step ? " , " : ""; + varItem.value = varItem.value.Left(varItem.value.GetCount() - 2) + sep + "(" + kItem.value + " , " + vItem.value + ") }"; + if(++step >= count) + return 0; + else + return step + 2; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static int UppIndexSimplify(VarItem &varItem, int step) +{ + const char *placeHolder = " = [...]"; + + // setup item type + varItem.kind = VarItem::ARRAY; + + // if we're just doing first scan phase, signal that we need further evaluation later +#ifdef EVALDEEP + if(!step) + // next step is 1 + return 1; +#else + varItem.value = placeHolder; + return 0; +#endif + + // just getting items count... + if(step == 1) + { + // initialize default value + varItem.value = ""; + + // get items count + MIValue val = varItem.EvaluateExpression(varItem.evaluableExpression + ".key.items"); + if(val.IsError() || !val.IsString()) + return 0; + varItem.items = atoi(val.ToString()); + + // update value + varItem.value = Format("Upp::Index with %d elements", varItem.items, ""); + + // if no elements, just quit + if(!varItem.items) + return 0; + return 2; + } + + int count = min(EVALDEEP_VECTOR, varItem.items); + + // start from item 0 + step -= 2; + + if(!step) + varItem.value << " = [ ]"; + + // fetch elements, check on first if they're SIMPLE, so displayable + VarItem vItem(&varItem.Debugger(), varItem.evaluableExpression + Format(".key.vector[%d]", step)); + if(!vItem) + { + varItem.value = " "; + return 0; + } + if(vItem.kind != VarItem::SIMPLE) + { + varItem.value = placeHolder; + return 0; + } + vItem.Simplify(); + const char *sep = step ? " , " : ""; + varItem.value = varItem.value.Left(varItem.value.GetCount() - 2) + sep + vItem.value + " ]"; + if(++step >= count) + return 0; + else + return step + 2; } // Register the simplifiers -REGISTERSIMPLIFIER(">>" , UppStringSimplify); -REGISTERSIMPLIFIER("MICmd("var-delete " + varName); + + debugger = v.debugger; + empty = v.empty; + simplifyStep = v.simplifyStep; + + varName = v.varName; + ((VarItem &)v).varName.Clear(); + + shortExpression = v.shortExpression; + evaluableExpression = v.evaluableExpression; + type = v.type; + kind = v.kind; + value = v.value; + numChildren = v.numChildren; + items = v.items; + return *this; +} + +// clears contents +void VarItem::Clear(void) +{ + // store variable to be deleted later + if(!varName.IsEmpty() && varName.Find('.') < 0) + PutDeleted(varName); + + empty = true; + simplifyStep = -1; + varName.Clear(); + shortExpression.Clear(); + evaluableExpression.Clear(); + type.Clear(); + value.Clear(); + numChildren = 0; + items = 0; + kind = SIMPLE; +} + +bool VarItem::Simplify(void) +{ + // if already simplified, return false + if(!simplifyStep) + return false; + + // lookup for simplifier + TYPE_SIMPLIFIER_HANDLER simplifier = GetSimplifier(type); + if(!simplifier) + { + // none found, mark as already simplifie and leave + simplifyStep = 0; + return false; + } + + // simplifier found + if(simplifyStep == -1) + { + // fast, non-deep simplification + // set simplified to false if need deep one + simplifyStep = simplifier(*this, 0); + } + else + { + // slow, deep simplification + simplifyStep = simplifier(*this, simplifyStep); + } + return (simplifyStep != 0); +} + +// evaluate an expression usign gdb variables +bool VarItem::Evaluate(String const &expr) +{ + Clear(); + + // create the variable + MIValue var = debugger->MICmd("var-create - * " + expr); + if(var.IsError()) + return false; + empty = false; + + // store its name + varName = var["name"]; + + // store its value + value = var["value"]; + + // store type + type = var["type"]; + + // store number of children (temporary number...) + // and set temporary object kind + numChildren = atoi(var.Get("numchild", "0")); + kind = numChildren ? COMPLEX : SIMPLE; + + // get and store expression + evaluableExpression = expr; + shortExpression = expr; + + // fast simplify known types + Simplify(); + + return true; +} + +// fetch variable children +Vector VarItem::GetChildren0(MIValue const &val, String const &prePath) +{ + Vector res; + + MIValue const &children = val["children"]; + if(!children.IsArray()) + return res; + + for(int iChild = 0; iChild < children.GetCount(); iChild++) + { + MIValue const &child = children[iChild]; + + // for private, protected, public and inherited fake childs, just go deeper + String exp = child["exp"]; + String typ = child.Get("type", ""); + String nam = child.Get("name"); + if(exp == "private" || exp == "protected" || exp == "public" || exp == typ) + { + MIValue val2 = debugger->MICmd("var-list-children 1 " + nam); + if(!val2.IsTuple()) + continue; + res.Append(GetChildren0(val2, prePath)); + } + else + { + VarItem &v = res.Add(VarItem(debugger)); + v.empty = false; + v.varName = nam; + v.shortExpression = prePath + "." + exp; + v.type = typ; + + v.value = child["value"]; + v.numChildren = atoi(child.Get("numchild", "0")); + v.kind = v.numChildren ? COMPLEX : SIMPLE; + + MIValue vExp = debugger->MICmd("var-info-path-expression " + nam); + v.evaluableExpression = vExp.Get("path_expr", ""); + + // fast simplify known types + Simplify(); + } + } + + return res; +} + +// fetch variable children +Vector VarItem::GetChildren(void) +{ + Vector res; + + // do not enumerate children for non-complex types + // (for arrays and maps just use GetArray and GetMap functions) + if(kind != COMPLEX) + return res; + + // if no variable name, just return empty array + if(varName.IsEmpty()) + return res; + + // get children of current variable + MIValue val = debugger->MICmd("var-list-children 1 " + varName); + if(!val.IsTuple()) + return res; + + res = GetChildren0(val, evaluableExpression); + return res; +} + +// fetch array elements +Vector VarItem::GetArray(int start, int count) +{ + Vector res; + + return res; +} + +// fetch map elements +VectorMap VarItem::GetMap(int start, int count) +{ + VectorMap res; + + return res; +} + +// helpers for simplifiers + // helper for simplifiers +/* +void VarItem::ListChildren0(String const &varName, MIValue &res) const +{ + MIValue val = debugger->MICmd("var-list-children " + varName); + if(!val.IsTuple()) + return; + MIValue const &children = val["children"]; + if(!children.IsArray()) + return; + + for(int iChild = 0; iChild < children.GetCount(); iChild++) + { + MIValue const &child = children[iChild]; + + String exp = child["exp"]; + String typ = child.Get("type", ""); + String nam = child.Get("name"); + if(exp == "private" || exp == "protected" || exp == "public" || exp == typ) + { + MIValue val2 = debugger->MICmd("var-list-children 1 " + nam); + if(!val2.IsTuple()) + continue; + ListChildren0(val2, prePath)); + } + else + { + + +} + +MIValue VarItem::ListChildren(void) const +{ +} +*/ + +MIValue VarItem::EvaluateExpression(String const &exp) const +{ + MIValue val = debugger->MICmd("data-evaluate-expression " + exp); + if(!val.IsTuple()) + return MIValue(); + MIValue v = val["value"]; + if(v.IsError() || !v.IsString()) + return MIValue(); + String s = v.ToString(); + return MIValue(s); +} + + +// cleanup support -- variables can't be deleted in destructor +// as usually destructor is called when thread is hard-stopped +// by main thread with an exception. +// so we store all var names in a static index and delete them +// when back to main thread +StaticMutex VarItem::varMutex; +Vector VarItem::deletedVars; + +void VarItem::PutDeleted(String const &name) +{ + INTERLOCKED_(varMutex) { + deletedVars.Add(name); + } +} + +void VarItem::CleanVariables(Gdb_MI2 *deb) +{ + ASSERT(IsMainThread()); + INTERLOCKED_(varMutex) { + for(int i = 0; i < deletedVars.GetCount(); i++) + deb->MICmd("var-delete " + deletedVars[i]); + } +} + diff --git a/uppsrc/ide/Debuggers/VarItem.h b/uppsrc/ide/Debuggers/VarItem.h new file mode 100644 index 000000000..6b6012e89 --- /dev/null +++ b/uppsrc/ide/Debuggers/VarItem.h @@ -0,0 +1,118 @@ +#ifndef _ide_Debuggers_VarItem_h_ +#define _ide_Debuggers_VarItem_h_ + +#include +using namespace Upp; + +#include "MIValue.h" + +// item for a GDB variable +class Gdb_MI2; + +class VarItem : Moveable +{ + private: + + // connected debugger object + Gdb_MI2 *debugger; + + // error/empty state + bool empty; + + // next simplify step, 0 if completed + int simplifyStep; + + // gdb internal variable name + String varName; + + // fetch variable children + Vector GetChildren0(MIValue const &children, String const &prePath); + + // helper for simplifiers + void ListChildren0(String const &varName, MIValue &res) const; + + // cleanup support -- variables can't be deleted in destructor + // as usually destructor is called when thread is hard-stopped + // by main thread with an exception. + // so we store all var names in a static index and delete them + // when back to main thread + static StaticMutex varMutex; + static Vector deletedVars; + void PutDeleted(String const &name); + + public: + typedef enum { SIMPLE, COMPLEX, ARRAY, MAP } VarKind; + + // short name + String shortExpression; + + // evaluable expression + String evaluableExpression; + + // type + String type; + + // kind + int kind; + + // value of expression for non-sequence types + String value; + + // children + int numChildren; + + // number of items for array and maps + int items; + + // check if value contains an error + bool IsEmpty(void) const { return empty; } + bool operator!(void) const { return IsEmpty(); } + operator bool() { return !IsEmpty(); } + + // check if value is simplified + bool IsSimplified(void) { return simplifyStep == 0; } + + // clears contents + void Clear(void); + + // evaluate expression + bool Evaluate(String const &expr); + + // deep simplify known types + bool Simplify(void); + + // constructors + VarItem(Gdb_MI2 *dbg); + VarItem(Gdb_MI2 *dbg, String const &expr); + + // destructor + ~VarItem(); + + // copy (pick) + VarItem(pick_ VarItem &v); + VarItem const &operator=(pick_ VarItem &v); + + // get children + VectorGetChildren(void); + + // fetch array elements + Vector GetArray(int start = 0, int count = -1); + + // fetch map elements + VectorMap GetMap(int start = 0, int count = -1); + + // helpers for simplifiers + Gdb_MI2 &Debugger() { return *debugger; } + MIValue ListChildren(void) const; + MIValue EvaluateExpression(String const &exp) const; + + // cleanup support -- variables can't be deleted in destructor + // as usually destructor is called when thread is hard-stopped + // by main thread with an exception. + // so we store all var names in a static index and delete them + // when back to main thread + static void CleanVariables(Gdb_MI2 *deb); + +}; + +#endif