Ide/Debuggers : fixed some bugs in Gdb_MI2 frontend -- removed Asynchronous break because of problems

git-svn-id: svn://ultimatepp.org/upp/trunk@5152 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
micio 2012-07-08 16:12:17 +00:00
parent 74466cf40d
commit f7679cc1c8
4 changed files with 280 additions and 151 deletions

View file

@ -40,7 +40,8 @@ void Gdb_MI2::DebugBar(Bar& bar)
{ {
bar.Add("Stop debugging", THISBACK(Stop)).Key(K_SHIFT_F5); bar.Add("Stop debugging", THISBACK(Stop)).Key(K_SHIFT_F5);
bar.Separator(); 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(); 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 into", DbgImg::StepInto(), THISBACK1(Step, disas.HasFocus() ? "exec-step-instruction" : "exec-step")).Key(K_F11);
bar.Add(b, "Step over", DbgImg::StepOver(), THISBACK1(Step, disas.HasFocus() ? "exec-next-instruction" : "exec-next")).Key(K_F10); bar.Add(b, "Step 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 // and remove it
MIValue brk = bps.FindBreakpoint(file, line); MIValue brk = bps.FindBreakpoint(file, line);
if(!brk.IsEmpty()) 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()) if(bp.IsEmpty())
return true; return true;
else if(bp[0] == 0xe) 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 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() bool Gdb_MI2::RunTo()
@ -115,7 +120,15 @@ void Gdb_MI2::Run()
{ {
MIValue val; MIValue val;
if(firstRun) 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("exec-run");
// val = MICmd("interpreter-exec console run&");
else else
val = MICmd("exec-continue --all"); val = MICmd("exec-continue --all");
int i = 50; int i = 50;
@ -139,17 +152,8 @@ void Gdb_MI2::Run()
} }
Unlock(); Unlock();
if(stopped) if(stopped)
{
CheckStopReason(); 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; started = stopped = false;
firstRun = false; firstRun = false;
IdeActivateBottom(); IdeActivateBottom();
@ -158,16 +162,18 @@ void Gdb_MI2::Run()
void Gdb_MI2::AsyncBrk() void Gdb_MI2::AsyncBrk()
{ {
// send an interrupt command to all running threads // send an interrupt command to all running threads
MICmd("exec-interrupt --all"); StopAllThreads();
// gdb usually returns to command prompt instantly, BEFORE // gdb usually returns to command prompt instantly, BEFORE
// giving out stop reason, which we need. So, we wait some // giving out stop reason, which we need. So, we wait some
// milliseconds and re-read (non blocking) GDB output to get it // milliseconds and re-read (non blocking) GDB output to get it
/*
for(int i = 0; i < 20 && !stopped; i++) for(int i = 0; i < 20 && !stopped; i++)
{ {
Sleep(100); Sleep(100);
ReadGdb(false); ReadGdb(false);
} }
*/
// if target is correctly stopped, 'stopped' flag should be already set // 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. // 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 Gdb_MI2::ParseGdb(String const &output, bool wait)
{ {
MIValue res; MIValue res;
// parse result data // parse result data
StringStream ss(output); StringStream ss(output);
int iSubstr;
while(!ss.IsEof()) while(!ss.IsEof())
{ {
String s = TrimBoth(ss.GetLine()); String s = TrimBoth(ss.GetLine());
@ -391,6 +399,11 @@ MIValue Gdb_MI2::ParseGdb(String const &output, bool wait)
stopReason.Clear(); stopReason.Clear();
continue; 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")) else if(s.StartsWith("*stopped"))
{ {
stopped = true; stopped = true;
@ -398,6 +411,14 @@ MIValue Gdb_MI2::ParseGdb(String const &output, bool wait)
stopReason = MIValue(s); stopReason = MIValue(s);
continue; continue;
} }
*/
else if( (iSubstr = s.Find("*stopped,reason=")) >= 0)
{
stopped = true;
s = '{' + s.Mid(iSubstr + 9) + '}';
stopReason = MIValue(s);
continue;
}
// skip asynchronous responses // skip asynchronous responses
// in future, we could be gather/use them // in future, we could be gather/use them
@ -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)); 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 // check for stop reason
void Gdb_MI2::CheckStopReason(void) 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; String reason;
if(stopReason.IsEmpty()) if(stReason.IsEmpty())
reason = "unknown reason"; reason = "unknown reason";
else 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") if(reason == "exited-normally")
{ {
Stop(); Stop();
@ -774,21 +842,27 @@ void Gdb_MI2::CheckStopReason(void)
} }
else if(reason == "breakpoint-hit") 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(); SyncIde();
} }
else if(reason == "unknown reason") else if(reason == "unknown reason")
{ {
PutConsole("Stopped by unknown reason"); PutConsole("Stopped by unknown reason");
SyncIde();
} }
else else
{ {
// weird stop reasons (i.e., signals, segfaults... may not have a frame // weird stop reasons (i.e., signals, segfaults... may not have a frame
// data inside // data inside
if(stopReason.Find("frame") < 0) if(stReason.Find("frame") < 0)
PutConsole(Format("Stopped, reason '%s'", reason)); PutConsole(Format("Stopped, reason '%s'", reason));
else else
LogFrame(Format("Stopped, reason '%s'", reason), stopReason["frame"]); LogFrame(Format("Stopped, reason '%s'", reason), stReason["frame"]);
SyncIde(); SyncIde();
} }
} }
@ -808,7 +882,8 @@ void Gdb_MI2::Step(const char *cmd)
} }
if(!started) if(!started)
{ {
Exclamation(t_("Failed to start application")); Stop();
Exclamation(t_("Step failed - terminating debugger"));
return; return;
} }
@ -866,7 +941,11 @@ void Gdb_MI2::DisasFocus()
// create a string representation of frame given its info and args // create a string representation of frame given its info and args
String Gdb_MI2::FormatFrame(MIValue &fInfo, MIValue &fArgs) 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", "<unknown>"); String func = fInfo("func", "<unknown>");
String file = fInfo("file", "<unknown>"); String file = fInfo("file", "<unknown>");
String line = fInfo("line", "<unknown>"); String line = fInfo("line", "<unknown>");
@ -874,7 +953,7 @@ String Gdb_MI2::FormatFrame(MIValue &fInfo, MIValue &fArgs)
String argLine; String argLine;
for(int iArg = 0; iArg < nArgs; iArg++) for(int iArg = 0; iArg < nArgs; iArg++)
{ {
argLine += fArgs[iArg]["name"]; argLine += fArgs[iArg]["name"].Get();
if(fArgs[iArg].Find("value") >= 0) if(fArgs[iArg].Find("value") >= 0)
argLine << "=" << fArgs[iArg]["value"]; argLine << "=" << fArgs[iArg]["value"];
argLine << ','; argLine << ',';
@ -892,11 +971,19 @@ void Gdb_MI2::DropFrames()
// get a list of frames // get a list of frames
MIValue frameList = MICmd("stack-list-frames")["stack"]; 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 // get the arguments for all frames, values just for simple types
MIValue frameArgs = MICmd("stack-list-arguments 1")["stack-args"]; 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 // fill the droplist
for(int iFrame = 0; iFrame < frameArgs.GetCount(); iFrame++) for(int iFrame = 0; iFrame < frameArgs.GetCount(); iFrame++)
@ -912,7 +999,11 @@ void Gdb_MI2::DropFrames()
void Gdb_MI2::ShowFrame() void Gdb_MI2::ShowFrame()
{ {
int i = (int)~frame; 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); SyncIde(i);
} }
@ -925,6 +1016,11 @@ void Gdb_MI2::dropThreads()
// get a list of all available threads // get a list of all available threads
MIValue tInfo = MICmd("thread-info"); MIValue tInfo = MICmd("thread-info");
MIValue &threads = tInfo["threads"]; 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()); int currentId = atoi(tInfo["current-thread-id"].Get());
for(int iThread = 0; iThread < threads.GetCount(); iThread++) 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. // re-reading as a whole is too time expensive.
// So, at first we build a list of local variable NAMES only // So, at first we build a list of local variable NAMES only
MIValue iLoc = MICmd("stack-list-variables 0"); MIValue iLoc = MICmd("stack-list-variables 0");
if(iLoc.IsEmpty() || iLoc.IsError()) if(!iLoc || iLoc.IsEmpty())
return; return;
MIValue &loc = iLoc["variables"]; MIValue &loc = iLoc["variables"];
if(!loc.IsArray())
return;
Index<String>locIdx; Index<String>locIdx;
for(int iLoc = 0; iLoc < loc.GetCount(); iLoc++) for(int iLoc = 0; iLoc < loc.GetCount(); iLoc++)
locIdx.Add(loc[iLoc]["name"]); locIdx.Add(loc[iLoc]["name"]);
@ -1076,7 +1174,7 @@ void Gdb_MI2::UpdateWatches(void)
// sometimes it has problem creating vars... maybe because they're // sometimes it has problem creating vars... maybe because they're
// still not active; we just skip them // still not active; we just skip them
if(var.IsError() || var.IsEmpty()) if(!var || var.IsEmpty() || !var.IsTuple())
continue; continue;
watchesNames.Add(var["name"]); watchesNames.Add(var["name"]);
watchesExpressions.Add(exprs[i]); watchesExpressions.Add(exprs[i]);
@ -1120,7 +1218,7 @@ void Gdb_MI2::UpdateAutos(void)
// sometimes it has problem creating vars... maybe because they're // sometimes it has problem creating vars... maybe because they're
// still not active; we just skip them // still not active; we just skip them
if(var.IsError() || var.IsEmpty()) if(!var || var.IsEmpty() || !var.IsTuple())
continue; continue;
autosNames.Add(var["name"]); autosNames.Add(var["name"]);
autosExpressions.Add(exprs[i]); autosExpressions.Add(exprs[i]);
@ -1281,14 +1379,21 @@ String Gdb_MI2::FormatWatchLine(String exp, String const &val, int level)
} }
// deep watch current quickwatch variable // 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"); MIValue childInfo = MICmd("var-list-children 1 \"" + var + "\" 0 100");
int nChilds = min(atoi(childInfo["numchild"].Get()), 100); if(!childInfo || !childInfo.IsTuple())
if(nChilds) return;
{ int nChilds = min(atoi(childInfo("numchild", "-1")), 100);
if(nChilds <= 0)
return;
MIValue &childs = childInfo["children"]; MIValue &childs = childInfo["children"];
for(int i = 0; i < childs.GetCount(); i++) for(int i = 0; i < childs.GetCount() && maxRemaining > 0; i++)
{ {
MIValue child = childs[i]; MIValue child = childs[i];
String exp = child["exp"]; String exp = child["exp"];
@ -1319,9 +1424,17 @@ void Gdb_MI2::WatchDeep(String parentExp, String const &var, int level)
quickwatch.value <<= (String)~quickwatch.value + "\n" + FormatWatchLine(parentExp + exp, type + value, level); quickwatch.value <<= (String)~quickwatch.value + "\n" + FormatWatchLine(parentExp + exp, type + value, level);
// recursive deep watch // recursive deep watch
WatchDeep(exp, child["name"], level + 1); 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 // opens quick watch dialog
@ -1375,7 +1488,7 @@ void Gdb_MI2::QuickWatch()
quickwatch.value <<= FormatWatchLine(exp, type + value, 0); quickwatch.value <<= FormatWatchLine(exp, type + value, 0);
quickwatch.expression.AddHistory(); quickwatch.expression.AddHistory();
String name = v["name"]; String name = v["name"];
WatchDeep(exp, name, 1); WatchDeep(exp, name);
MICmd("var-delete " + name); MICmd("var-delete " + name);
} }
else else
@ -1612,9 +1725,15 @@ bool Gdb_MI2::Create(One<Host> _host, const String& exefile, const String& cmdli
tab <<= THISBACK(SyncData); tab <<= THISBACK(SyncData);
// this one will allow asynchronous break of running app // 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 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 interactive-mode off");
MICmd("gdb-set disassembly-flavor intel"); MICmd("gdb-set disassembly-flavor intel");

View file

@ -139,6 +139,9 @@ class Gdb_MI2 : public Debugger, public ParentCtrl
// check for stop reason // check for stop reason
void CheckStopReason(void); void CheckStopReason(void);
// stop all running threads and re-select previous current thread
void StopAllThreads(void);
// single step command handler // single step command handler
void Step(const char *cmd); 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); String FormatWatchLine(String exp, String const &val, int level);
// deep watch current quickwatch variable // 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 // copy stack frame list to clipboard
void CopyStack(void); void CopyStack(void);

View file

@ -247,13 +247,13 @@ MIValue &MIValue::SetError(String const &msg)
} }
// check if value contains an error // check if value contains an error
bool MIValue::IsError(void) bool MIValue::IsError(void) const
{ {
return type == MIString && string.StartsWith("error:"); return type == MIString && string.StartsWith("error:");
} }
// check for emptyness // check for emptyness
bool MIValue::IsEmpty(void) bool MIValue::IsEmpty(void) const
{ {
return type == MIString && string == ""; return type == MIString && string == "";
} }
@ -261,6 +261,8 @@ bool MIValue::IsEmpty(void)
// simple accessors // simple accessors
int MIValue::GetCount(void) const int MIValue::GetCount(void) const
{ {
if(IsError())
return 0;
if(type == MIArray) if(type == MIArray)
return array.GetCount(); return array.GetCount();
else if(type == MITuple) else if(type == MITuple)
@ -278,6 +280,8 @@ int MIValue::Find(const char *key) const
MIValue &MIValue::Get(int i) MIValue &MIValue::Get(int i)
{ {
if(IsError())
return *this;
if(type != MIArray) if(type != MIArray)
return ErrorMIValue("Not an Array value type"); return ErrorMIValue("Not an Array value type");
return array[i]; return array[i];
@ -308,7 +312,7 @@ String const &MIValue::Get(void) const
String MIValue::Get(const char *key, const char *def) const String MIValue::Get(const char *key, const char *def) const
{ {
if(type != MITuple) if(type != MITuple)
return def; return ErrorMIValue("Not a Tuple value type");
int i = tuple.Find(key); int i = tuple.Find(key);
if(i >= 0) if(i >= 0)
{ {
@ -321,7 +325,7 @@ String MIValue::Get(const char *key, const char *def) const
} }
// data dump // data dump
String MIValue::Dump(int level) String MIValue::Dump(int level) const
{ {
String spacer(' ', level); String spacer(' ', level);
switch(type) switch(type)
@ -338,7 +342,7 @@ String MIValue::Dump(int level)
{ {
String s1 = spacer + tuple.GetKey(i) + "="; String s1 = spacer + tuple.GetKey(i) + "=";
s += s1; s += s1;
MIValue &val = tuple[i]; MIValue const &val = tuple[i];
if(val.type == MIString) if(val.type == MIString)
s += val.Dump(); s += val.Dump();
else else
@ -362,7 +366,7 @@ String MIValue::Dump(int level)
level += 4; level += 4;
for(int i = 0; i < array.GetCount(); i++) for(int i = 0; i < array.GetCount(); i++)
{ {
MIValue &val = array[i]; MIValue const &val = array[i];
s += val.Dump(level); s += val.Dump(level);
if(val.type != MIString) if(val.type != MIString)
s = s.Left(s.GetCount()-1); s = s.Left(s.GetCount()-1);

View file

@ -27,10 +27,12 @@ class MIValue : public Moveable<MIValue>
MIValue &SetError(String const &msg); MIValue &SetError(String const &msg);
// check if value contains an error // check if value contains an error
bool IsError(void); bool IsError(void) const;
bool operator!(void) const { return IsError(); }
operator bool() { return !IsError(); }
// check for emptyness // check for emptyness
bool IsEmpty(void); bool IsEmpty(void) const;
MIValue &operator=(pick_ MIValue &v); MIValue &operator=(pick_ MIValue &v);
MIValue &operator=(String const &s); MIValue &operator=(String const &s);
@ -59,15 +61,15 @@ class MIValue : public Moveable<MIValue>
String operator()(const char *key, const char *def) const { return Get(key, def); } String operator()(const char *key, const char *def) const { return Get(key, def); }
// some type checking // some type checking
bool IsArray(void) { return type == MIArray; } bool IsArray(void) const { return type == MIArray; }
void AssertArray(void) { ASSERT(type == MIArray); } void AssertArray(void) const { ASSERT(type == MIArray); }
bool IsTuple(void) { return type == MITuple; } bool IsTuple(void) const { return type == MITuple; }
void AssertTuple(void) { ASSERT(type == MITuple); } void AssertTuple(void) const { ASSERT(type == MITuple); }
bool IsString(void) { return type == MIString; } bool IsString(void) const { return type == MIString; }
void AssertString(void) { ASSERT(type == MIString); } void AssertString(void) const { ASSERT(type == MIString); }
// data dump // data dump
String Dump(int level = 0); String Dump(int level = 0) const;
// finds breakpoint data given file and line // finds breakpoint data given file and line
MIValue &FindBreakpoint(String const &file, int line); MIValue &FindBreakpoint(String const &file, int line);