diff --git a/uppsrc/ide/Debuggers/Gdb_MI2.cpp b/uppsrc/ide/Debuggers/Gdb_MI2.cpp index 2f8fb7a05..2b2359f18 100644 --- a/uppsrc/ide/Debuggers/Gdb_MI2.cpp +++ b/uppsrc/ide/Debuggers/Gdb_MI2.cpp @@ -40,7 +40,8 @@ void Gdb_MI2::DebugBar(Bar& bar) { bar.Add("Stop debugging", THISBACK(Stop)).Key(K_SHIFT_F5); bar.Separator(); - bar.Add(!stopped, "Asynchronous break", THISBACK(AsyncBrk)); +// see note on Run() function -- crashes X on my machine, so removed by now +// bar.Add(!stopped, "Asynchronous break", THISBACK(AsyncBrk)); bool b = !IdeIsDebugLock(); bar.Add(b, "Step into", DbgImg::StepInto(), THISBACK1(Step, disas.HasFocus() ? "exec-step-instruction" : "exec-step")).Key(K_F11); bar.Add(b, "Step over", DbgImg::StepOver(), THISBACK1(Step, disas.HasFocus() ? "exec-next-instruction" : "exec-next")).Key(K_F10); @@ -73,14 +74,18 @@ bool Gdb_MI2::SetBreakpoint(const String& filename, int line, const String& bp) // and remove it MIValue brk = bps.FindBreakpoint(file, line); if(!brk.IsEmpty()) - ASSERT(!MICmd(Format("break-delete %s", brk["number"].Get())).IsError()); + if(!MICmd(Format("break-delete %s", brk["number"].Get()))) + { + Exclamation(t_("Couldn't remove breakpoint")); + return false; + } if(bp.IsEmpty()) return true; else if(bp[0] == 0xe) - return !MICmd(Format("break-insert %s:%d", file, line)).IsError(); + return MICmd(Format("break-insert %s:%d", file, line)); else - return !MICmd(Format("break-insert -c \"%s\" %s:%d", bp, file, line)).IsError(); + return MICmd(Format("break-insert -c \"%s\" %s:%d", bp, file, line)); } bool Gdb_MI2::RunTo() @@ -115,7 +120,15 @@ void Gdb_MI2::Run() { MIValue val; if(firstRun) + // GDB up to 7.1 has a bug that maps -exec-run ro run, not to run& + // making so async mode useless; we use the console run& command instead +// 2012-07-08 update : interrupting GDB in async mode without having +// non-stop mode enabled crashes X...don't know if it's a GDB bug or Theide one. +// anyways, by now we give up with async mode and remove 'Asynchronous break' function val = MICmd("exec-run"); + +// val = MICmd("interpreter-exec console run&"); + else val = MICmd("exec-continue --all"); int i = 50; @@ -139,17 +152,8 @@ void Gdb_MI2::Run() } Unlock(); if(stopped) - { CheckStopReason(); - // 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 - MICmd("exec-interrupt --all"); - } - started = stopped = false; firstRun = false; IdeActivateBottom(); @@ -158,16 +162,18 @@ void Gdb_MI2::Run() void Gdb_MI2::AsyncBrk() { // send an interrupt command to all running threads - MICmd("exec-interrupt --all"); + StopAllThreads(); // gdb usually returns to command prompt instantly, BEFORE // giving out stop reason, which we need. So, we wait some // milliseconds and re-read (non blocking) GDB output to get it +/* for(int i = 0; i < 20 && !stopped; i++) { Sleep(100); ReadGdb(false); } +*/ // if target is correctly stopped, 'stopped' flag should be already set // so we don't care here. If not set, target can't be stopped. @@ -378,8 +384,10 @@ void Gdb_MI2::Unlock() MIValue Gdb_MI2::ParseGdb(String const &output, bool wait) { MIValue res; + // parse result data StringStream ss(output); + int iSubstr; while(!ss.IsEof()) { String s = TrimBoth(ss.GetLine()); @@ -391,6 +399,11 @@ MIValue Gdb_MI2::ParseGdb(String const &output, bool wait) 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; @@ -398,6 +411,14 @@ MIValue Gdb_MI2::ParseGdb(String const &output, bool wait) stopReason = MIValue(s); continue; } +*/ + else if( (iSubstr = s.Find("*stopped,reason=")) >= 0) + { + stopped = true; + s = '{' + s.Mid(iSubstr + 9) + '}'; + stopReason = MIValue(s); + continue; + } // skip asynchronous responses // in future, we could be gather/use them @@ -733,7 +754,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"]; - + // setup frame droplist frame.Clear(); frame.Add(0, FormatFrame(fInfo, fArgs)); @@ -754,14 +775,61 @@ 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) { + // we need to store stop reason BEFORE interrupting all other + // threads, otherwise it'll be lost + MIValue stReason = stopReason; + + // get the reason string String reason; - if(stopReason.IsEmpty()) + if(stReason.IsEmpty()) reason = "unknown reason"; else - reason = stopReason["reason"]; + 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(); @@ -774,21 +842,27 @@ void Gdb_MI2::CheckStopReason(void) } else if(reason == "breakpoint-hit") { - LogFrame("Hit breakpoint", stopReason["frame"]); + LogFrame("Hit breakpoint", stReason["frame"]); + SyncIde(); + } + else if(reason == "end-stepping-range") + { + LogFrame("End stepping range", stReason["frame"]); SyncIde(); } else if(reason == "unknown reason") { PutConsole("Stopped by unknown reason"); + SyncIde(); } else { // weird stop reasons (i.e., signals, segfaults... may not have a frame // data inside - if(stopReason.Find("frame") < 0) + if(stReason.Find("frame") < 0) PutConsole(Format("Stopped, reason '%s'", reason)); else - LogFrame(Format("Stopped, reason '%s'", reason), stopReason["frame"]); + LogFrame(Format("Stopped, reason '%s'", reason), stReason["frame"]); SyncIde(); } } @@ -808,7 +882,8 @@ void Gdb_MI2::Step(const char *cmd) } if(!started) { - Exclamation(t_("Failed to start application")); + Stop(); + Exclamation(t_("Step failed - terminating debugger")); return; } @@ -866,7 +941,11 @@ void Gdb_MI2::DisasFocus() // 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()); + int idx = atoi(fInfo("level", "-1")); + if(idx < 0) + return t_("invalid frame info"); + if(!fArgs.IsArray()) + return t_("invalid frame args"); String func = fInfo("func", ""); String file = fInfo("file", ""); String line = fInfo("line", ""); @@ -874,7 +953,7 @@ String Gdb_MI2::FormatFrame(MIValue &fInfo, MIValue &fArgs) String argLine; for(int iArg = 0; iArg < nArgs; iArg++) { - argLine += fArgs[iArg]["name"]; + argLine += fArgs[iArg]["name"].Get(); if(fArgs[iArg].Find("value") >= 0) argLine << "=" << fArgs[iArg]["value"]; argLine << ','; @@ -892,11 +971,19 @@ void Gdb_MI2::DropFrames() // get a list of frames MIValue frameList = MICmd("stack-list-frames")["stack"]; - frameList.AssertArray(); + if(frameList.IsError() || !frameList.IsArray()) + { + Exclamation("Couldn't get stack frame list"); + return; + } // get the arguments for all frames, values just for simple types MIValue frameArgs = MICmd("stack-list-arguments 1")["stack-args"]; - frameArgs.AssertArray(); + if(frameArgs.IsError() || !frameArgs.IsArray()) + { + Exclamation("Couldn't get stack arguments list"); + return; + } // fill the droplist for(int iFrame = 0; iFrame < frameArgs.GetCount(); iFrame++) @@ -912,7 +999,11 @@ void Gdb_MI2::DropFrames() void Gdb_MI2::ShowFrame() { int i = (int)~frame; - MICmd(Format("stack-select-frame %d", i)); + if(!MICmd(Format("stack-select-frame %d", i))) + { + Exclamation(Format(t_("Couldn't select frame #%d"), i)); + return; + } SyncIde(i); } @@ -925,6 +1016,11 @@ void Gdb_MI2::dropThreads() // get a list of all available threads MIValue tInfo = MICmd("thread-info"); MIValue &threads = tInfo["threads"]; + if(!tInfo.IsTuple() || !threads.IsArray()) + { + Exclamation(t_("couldn't get thread info")); + return; + } int currentId = atoi(tInfo["current-thread-id"].Get()); for(int iThread = 0; iThread < threads.GetCount(); iThread++) { @@ -987,9 +1083,11 @@ void Gdb_MI2::UpdateLocalVars(void) // re-reading as a whole is too time expensive. // So, at first we build a list of local variable NAMES only MIValue iLoc = MICmd("stack-list-variables 0"); - if(iLoc.IsEmpty() || iLoc.IsError()) + if(!iLoc || iLoc.IsEmpty()) return; MIValue &loc = iLoc["variables"]; + if(!loc.IsArray()) + return; IndexlocIdx; for(int iLoc = 0; iLoc < loc.GetCount(); iLoc++) locIdx.Add(loc[iLoc]["name"]); @@ -1076,7 +1174,7 @@ void Gdb_MI2::UpdateWatches(void) // sometimes it has problem creating vars... maybe because they're // still not active; we just skip them - if(var.IsError() || var.IsEmpty()) + if(!var || var.IsEmpty() || !var.IsTuple()) continue; watchesNames.Add(var["name"]); watchesExpressions.Add(exprs[i]); @@ -1120,7 +1218,7 @@ void Gdb_MI2::UpdateAutos(void) // sometimes it has problem creating vars... maybe because they're // still not active; we just skip them - if(var.IsError() || var.IsEmpty()) + if(!var || var.IsEmpty() || !var.IsTuple()) continue; autosNames.Add(var["name"]); autosExpressions.Add(exprs[i]); @@ -1281,49 +1379,64 @@ String Gdb_MI2::FormatWatchLine(String exp, String const &val, int level) } // deep watch current quickwatch variable -void Gdb_MI2::WatchDeep(String parentExp, String const &var, int level) +void Gdb_MI2::WatchDeep0(String parentExp, String const &var, int level, int &maxRemaining) { + // avoid endless recursion for circularly linked vars + if(--maxRemaining <= 0) + return; + MIValue childInfo = MICmd("var-list-children 1 \"" + var + "\" 0 100"); - int nChilds = min(atoi(childInfo["numchild"].Get()), 100); - if(nChilds) - { - MIValue &childs = childInfo["children"]; - for(int i = 0; i < childs.GetCount(); i++) - { - MIValue child = childs[i]; - String exp = child["exp"]; - - // handle pseudo children... -/* - while(exp == "public" || exp == "private" || exp == "protected") - { - child = MICmd(String("var-list-children 1 \"") + child["name"] + "\"")["children"][0]; - exp = child["exp"]; - } -*/ - if(isdigit(exp[0])) - { - exp = '[' + exp + ']'; - if(parentExp.Mid(parentExp.GetCount() - 1, 1) == ".") - parentExp = parentExp.Left(parentExp.GetCount() - 1); - } - if(exp[0] != '.' && exp[0] != '[') - exp = '.' + exp; + if(!childInfo || !childInfo.IsTuple()) + return; + int nChilds = min(atoi(childInfo("numchild", "-1")), 100); + if(nChilds <= 0) + return; - String type = child("type", ""); - if(!type.IsEmpty()) - type = "(" + type + ")"; - String value = child["value"]; - - // try to format nicely results... - quickwatch.value <<= (String)~quickwatch.value + "\n" + FormatWatchLine(parentExp + exp, type + value, level); - - // recursive deep watch - WatchDeep(exp, child["name"], level + 1); + MIValue &childs = childInfo["children"]; + for(int i = 0; i < childs.GetCount() && maxRemaining > 0; i++) + { + MIValue child = childs[i]; + String exp = child["exp"]; + + // handle pseudo children... +/* + while(exp == "public" || exp == "private" || exp == "protected") + { + child = MICmd(String("var-list-children 1 \"") + child["name"] + "\"")["children"][0]; + exp = child["exp"]; } +*/ + if(isdigit(exp[0])) + { + exp = '[' + exp + ']'; + if(parentExp.Mid(parentExp.GetCount() - 1, 1) == ".") + parentExp = parentExp.Left(parentExp.GetCount() - 1); + } + if(exp[0] != '.' && exp[0] != '[') + exp = '.' + exp; + + String type = child("type", ""); + if(!type.IsEmpty()) + type = "(" + type + ")"; + String value = child["value"]; + + // try to format nicely results... + quickwatch.value <<= (String)~quickwatch.value + "\n" + FormatWatchLine(parentExp + exp, type + value, level); + + // recursive deep watch + WatchDeep0(exp, child["name"], level + 1, maxRemaining); } } +void Gdb_MI2::WatchDeep(String parentExp, String const &name) +{ + // this is to avoid circular endless recursion + // we limit the total watched (sub)variables to this count + int maxRemaining = 300; + + WatchDeep0(parentExp, name, 1, maxRemaining); +} + // opens quick watch dialog void Gdb_MI2::QuickWatch() { @@ -1375,7 +1488,7 @@ void Gdb_MI2::QuickWatch() quickwatch.value <<= FormatWatchLine(exp, type + value, 0); quickwatch.expression.AddHistory(); String name = v["name"]; - WatchDeep(exp, name, 1); + WatchDeep(exp, name); MICmd("var-delete " + name); } else @@ -1612,9 +1725,15 @@ bool Gdb_MI2::Create(One _host, const String& exefile, const String& cmdli tab <<= THISBACK(SyncData); // this one will allow asynchronous break of running app - MICmd("gdb-set target-async 1"); +// 2012-07-08 -- DISABLED because of GDB bugs... +// MICmd("gdb-set target-async 1"); MICmd("gdb-set pagination off"); - MICmd("gdb-set non-stop on"); + +// Don't enable this one -- brings every sort of bugs with +// It was useful to issue Asynchronous break, but too many bugs +// to be useable +// MICmd("gdb-set non-stop on"); + // MICmd("gdb-set interactive-mode off"); MICmd("gdb-set disassembly-flavor intel"); diff --git a/uppsrc/ide/Debuggers/Gdb_MI2.h b/uppsrc/ide/Debuggers/Gdb_MI2.h index 3f773969f..edb76f5fc 100644 --- a/uppsrc/ide/Debuggers/Gdb_MI2.h +++ b/uppsrc/ide/Debuggers/Gdb_MI2.h @@ -139,6 +139,9 @@ class Gdb_MI2 : public Debugger, public ParentCtrl // check for stop reason void CheckStopReason(void); + // stop all running threads and re-select previous current thread + void StopAllThreads(void); + // single step command handler void Step(const char *cmd); @@ -197,7 +200,8 @@ class Gdb_MI2 : public Debugger, public ParentCtrl String FormatWatchLine(String exp, String const &val, int level); // deep watch current quickwatch variable - void WatchDeep(String parentExp, String const &name, int level = 0); + void WatchDeep0(String parentExp, String const &name, int level, int &maxRemaining); + void WatchDeep(String parentExp, String const &name); // copy stack frame list to clipboard void CopyStack(void); diff --git a/uppsrc/ide/Debuggers/MIValue.cpp b/uppsrc/ide/Debuggers/MIValue.cpp index 1bf58ba20..d702c04e9 100644 --- a/uppsrc/ide/Debuggers/MIValue.cpp +++ b/uppsrc/ide/Debuggers/MIValue.cpp @@ -247,13 +247,13 @@ MIValue &MIValue::SetError(String const &msg) } // check if value contains an error -bool MIValue::IsError(void) +bool MIValue::IsError(void) const { return type == MIString && string.StartsWith("error:"); } // check for emptyness -bool MIValue::IsEmpty(void) +bool MIValue::IsEmpty(void) const { return type == MIString && string == ""; } @@ -261,6 +261,8 @@ bool MIValue::IsEmpty(void) // simple accessors int MIValue::GetCount(void) const { + if(IsError()) + return 0; if(type == MIArray) return array.GetCount(); else if(type == MITuple) @@ -278,6 +280,8 @@ int MIValue::Find(const char *key) const MIValue &MIValue::Get(int i) { + if(IsError()) + return *this; if(type != MIArray) return ErrorMIValue("Not an Array value type"); return array[i]; @@ -308,7 +312,7 @@ String const &MIValue::Get(void) const String MIValue::Get(const char *key, const char *def) const { if(type != MITuple) - return def; + return ErrorMIValue("Not a Tuple value type"); int i = tuple.Find(key); if(i >= 0) { @@ -321,7 +325,7 @@ String MIValue::Get(const char *key, const char *def) const } // data dump -String MIValue::Dump(int level) +String MIValue::Dump(int level) const { String spacer(' ', level); switch(type) @@ -338,7 +342,7 @@ String MIValue::Dump(int level) { String s1 = spacer + tuple.GetKey(i) + "="; s += s1; - MIValue &val = tuple[i]; + MIValue const &val = tuple[i]; if(val.type == MIString) s += val.Dump(); else @@ -362,7 +366,7 @@ String MIValue::Dump(int level) level += 4; for(int i = 0; i < array.GetCount(); i++) { - MIValue &val = array[i]; + MIValue const &val = array[i]; s += val.Dump(level); if(val.type != MIString) s = s.Left(s.GetCount()-1); diff --git a/uppsrc/ide/Debuggers/MIValue.h b/uppsrc/ide/Debuggers/MIValue.h index b0aa869ec..25fa955c7 100644 --- a/uppsrc/ide/Debuggers/MIValue.h +++ b/uppsrc/ide/Debuggers/MIValue.h @@ -1,76 +1,78 @@ -#ifndef _ide_Debuggers_MIValue_h_ -#define _ide_Debuggers_MIValue_h_ - -#include - -using namespace Upp; - -// this struct contains values returned by GDB MI interface -typedef enum { MIString, MIArray, MITuple } MIValueType; -class MIValue : public Moveable -{ - private: - int ParsePair(String &name, MIValue &val, String const &s, int i = 0); - int ParseTuple(String const &s, int i = 0); - int ParseArray(String const &s, int i = 0); - int ParseString(String const &s, int i = 0); - int ParseValue(String const &s, int i = 0); - int Parse(String const &s, int i = 0); - - MIValueType type; - String string; - Vector array; - VectorMap tuple; - - public: - // sets value to an error condition - MIValue &SetError(String const &msg); - - // check if value contains an error - bool IsError(void); - - // check for emptyness - bool IsEmpty(void); - - MIValue &operator=(pick_ MIValue &v); - MIValue &operator=(String const &s); - MIValue(); - MIValue(MIValue pick_ &v); - MIValue(String const &s); - - void Clear(void); - - // simple accessors - int GetCount(void) const; - int Find(const char *key) const; - MIValue &Get(int i); - MIValue &operator[](int i) { return Get(i); } - MIValue &Get(const char *s); - MIValue &operator[](const char *key) { return Get(key); } - String &Get(void); - String const &Get(void) const; - operator String&() { return Get(); } - operator const String &() const { return Get(); } - String &ToString(void) { return Get(); } - String const &ToString(void) const { return Get(); } - - // tuple string member accessor with default value if not found - String Get(const char *key, const char *def) const; - String operator()(const char *key, const char *def) const { return Get(key, def); } - - // some type checking - bool IsArray(void) { return type == MIArray; } - void AssertArray(void) { ASSERT(type == MIArray); } - bool IsTuple(void) { return type == MITuple; } - void AssertTuple(void) { ASSERT(type == MITuple); } - bool IsString(void) { return type == MIString; } - void AssertString(void) { ASSERT(type == MIString); } - - // data dump - String Dump(int level = 0); - - // finds breakpoint data given file and line - MIValue &FindBreakpoint(String const &file, int line); -}; - -#endif +#ifndef _ide_Debuggers_MIValue_h_ +#define _ide_Debuggers_MIValue_h_ + +#include + +using namespace Upp; + +// this struct contains values returned by GDB MI interface +typedef enum { MIString, MIArray, MITuple } MIValueType; +class MIValue : public Moveable +{ + private: + int ParsePair(String &name, MIValue &val, String const &s, int i = 0); + int ParseTuple(String const &s, int i = 0); + int ParseArray(String const &s, int i = 0); + int ParseString(String const &s, int i = 0); + int ParseValue(String const &s, int i = 0); + int Parse(String const &s, int i = 0); + + MIValueType type; + String string; + Vector array; + VectorMap tuple; + + public: + // sets value to an error condition + MIValue &SetError(String const &msg); + + // check if value contains an error + bool IsError(void) const; + bool operator!(void) const { return IsError(); } + operator bool() { return !IsError(); } + + // check for emptyness + bool IsEmpty(void) const; + + MIValue &operator=(pick_ MIValue &v); + MIValue &operator=(String const &s); + MIValue(); + MIValue(MIValue pick_ &v); + MIValue(String const &s); + + void Clear(void); + + // simple accessors + int GetCount(void) const; + int Find(const char *key) const; + MIValue &Get(int i); + MIValue &operator[](int i) { return Get(i); } + MIValue &Get(const char *s); + MIValue &operator[](const char *key) { return Get(key); } + String &Get(void); + String const &Get(void) const; + operator String&() { return Get(); } + operator const String &() const { return Get(); } + String &ToString(void) { return Get(); } + String const &ToString(void) const { return Get(); } + + // tuple string member accessor with default value if not found + String Get(const char *key, const char *def) const; + String operator()(const char *key, const char *def) const { return Get(key, def); } + + // some type checking + bool IsArray(void) const { return type == MIArray; } + void AssertArray(void) const { ASSERT(type == MIArray); } + bool IsTuple(void) const { return type == MITuple; } + void AssertTuple(void) const { ASSERT(type == MITuple); } + bool IsString(void) const { return type == MIString; } + void AssertString(void) const { ASSERT(type == MIString); } + + // data dump + String Dump(int level = 0) const; + + // finds breakpoint data given file and line + MIValue &FindBreakpoint(String const &file, int line); +}; + +#endif