diff options
author | hjk <hjk@theqtcompany.com> | 2015-10-14 13:26:22 +0200 |
---|---|---|
committer | Christian Stenger <christian.stenger@theqtcompany.com> | 2015-10-19 12:40:29 +0000 |
commit | b8ae9fd46d2c02e2b4901abf140ac106e35bdf51 (patch) | |
tree | be44a7952b8eef22b6f177721c05388245caf245 | |
parent | be1e0f7ec00fd0e23abb49dfe989b48a816b538e (diff) | |
download | qt-creator-b8ae9fd46d2c02e2b4901abf140ac106e35bdf51.tar.gz |
Debugger: Adjust native mixed debugging after upstream changes
Change-Id: I4d137fadd0de2aa346f2f49932faac4ee9ed41e7
Reviewed-by: Ulf Hermann <ulf.hermann@theqtcompany.com>
Reviewed-by: Christian Stenger <christian.stenger@theqtcompany.com>
-rw-r--r-- | share/qtcreator/debugger/dumper.py | 212 | ||||
-rw-r--r-- | share/qtcreator/debugger/gdbbridge.py | 16 | ||||
-rw-r--r-- | share/qtcreator/debugger/lldbbridge.py | 14 | ||||
-rw-r--r-- | share/qtcreator/debugger/qttypes.py | 8 | ||||
-rw-r--r-- | src/plugins/debugger/breakhandler.cpp | 2 | ||||
-rw-r--r-- | src/plugins/debugger/debuggerprotocol.cpp | 2 | ||||
-rw-r--r-- | src/plugins/debugger/gdb/gdbengine.cpp | 426 | ||||
-rw-r--r-- | src/plugins/debugger/gdb/gdbengine.h | 3 | ||||
-rw-r--r-- | src/plugins/debugger/lldb/lldbengine.cpp | 2 | ||||
-rw-r--r-- | src/plugins/debugger/stackframe.cpp | 41 | ||||
-rw-r--r-- | src/plugins/debugger/watchdata.cpp | 7 |
11 files changed, 446 insertions, 287 deletions
diff --git a/share/qtcreator/debugger/dumper.py b/share/qtcreator/debugger/dumper.py index dfe5f7da15..a94dccedef 100644 --- a/share/qtcreator/debugger/dumper.py +++ b/share/qtcreator/debugger/dumper.py @@ -414,8 +414,6 @@ class DumperBase: "personaltypes", ] - self.interpreterSeq = 0 - def resetCaches(self): # This is a cache mapping from 'type name' to 'display alternatives'. @@ -601,9 +599,6 @@ class DumperBase: data = self.extractBlob(addr, size).toBytes() return self.hexencode(data) - def readJsonFromMemory(self, addr, size): - return json.loads(self.hexdecode(self.readMemory(addr, size))) - def encodeByteArray(self, value, limit = 0): elided, data = self.encodeByteArrayHelper(self.extractPointer(value), limit) return data @@ -834,38 +829,98 @@ class DumperBase: self.putSpecialValue(SpecialItemCountValue, count) self.putNumChild(count) - def dictToMi(self, value): + def resultToMi(self, value): if type(value) is bool: return '"%d"' % int(value) if type(value) is dict: - return '{' + ','.join(['%s=%s' % (k, self.dictToMi(v)) + return '{' + ','.join(['%s=%s' % (k, self.resultToMi(v)) for (k, v) in list(value.items())]) + '}' if type(value) is list: + return '[' + ','.join([self.resultToMi(k) + for k in list(value.items())]) + ']' + return '"%s"' % value + + def variablesToMi(self, value, prefix): + if type(value) is bool: + return '"%d"' % int(value) + if type(value) is dict: + pairs = [] + for (k, v) in list(value.items()): + if k == 'iname': + if v.startswith('.'): + v = '"%s%s"' % (prefix, v) + else: + v = '"%s"' % v + else: + v = self.variablesToMi(v, prefix) + pairs.append('%s=%s' % (k, v)) + return '{' + ','.join(pairs) + '}' + if type(value) is list: index = 0 pairs = [] for item in value: + if item.get('type', '') == 'function': + continue name = item.get('name', '') if len(name) == 0: name = str(index) index += 1 - pairs.append((name, self.dictToMi(item))) + pairs.append((name, self.variablesToMi(item, prefix))) pairs.sort(key = lambda pair: pair[0]) return '[' + ','.join([pair[1] for pair in pairs]) + ']' return '"%s"' % value + def filterPrefix(self, prefix, items): + return [i[len(prefix):] for i in items if i.startswith(prefix)] + def tryFetchInterpreterVariables(self, args): if not int(args.get('nativemixed', 0)): return (False, '') context = args.get('context', '') if not len(context): return (False, '') - res = self.extractInterpreterVariables(args) - if res: - return (True, 'data=%s' % self.dictToMi(res.get('data', {}))) - return (False, '') - def variableDictToMi(self, res): - return self.dictToMi(res.get('data', {}), self.SortStructMembers) + expanded = args.get('expanded') + args['expanded'] = self.filterPrefix('local', expanded) + + res = self.sendInterpreterRequest('variables', args) + if not res: + return (False, '') + + reslist = [] + for item in res.get('variables', {}): + if not 'iname' in item: + item['iname'] = '.' + item.get('name') + reslist.append(self.variablesToMi(item, 'local')) + + watchers = args.get('watchers', None) + if watchers: + toevaluate = [] + name2expr = {} + seq = 0 + for watcher in watchers: + expr = self.hexdecode(watcher.get('exp')) + name = str(seq) + toevaluate.append({'name': name, 'expression': expr}) + name2expr[name] = expr + seq += 1 + args['expressions'] = toevaluate + + args['expanded'] = self.filterPrefix('watch', expanded) + del args['watchers'] + res = self.sendInterpreterRequest('expressions', args) + + if res: + for item in res.get('expressions', {}): + name = item.get('name') + iname = 'watch.' + name + expr = name2expr.get(name) + item['iname'] = iname + item['wname'] = self.hexencode(expr) + item['exp'] = expr + reslist.append(self.variablesToMi(item, 'watch')) + + return (True, 'data=[%s]' % ','.join(reslist)) def putField(self, name, value): self.put('%s="%s",' % (name, value)) @@ -1776,38 +1831,86 @@ class DumperBase: value = struct.unpack_from("!I", buf, offset)[0] return (value, offset + 4) - def readInterpreterOutput(self): - buf = self.parseAndEvaluate("qt_qmlDebugOutputBuffer") - size = self.parseAndEvaluate("qt_qmlDebugOutputLength") - return self.readJsonFromMemory(buf, size) - - def handleInterpreterEvent(self): + def handleInterpreterMessage(self): """ Return True if inferior stopped """ - buf = self.parseAndEvaluate("qt_qmlDebugEventBuffer") - size = self.parseAndEvaluate("qt_qmlDebugEventLength") - resdict = self.readJsonFromMemory(buf, size) - warn("Interpreter event received: %s" % resdict) + resdict = self.fetchInterpreterResult() return resdict.get('event') == 'break' + def reportInterpreterResult(self, resdict, args): + print('interpreterresult=%s,token="%s"' + % (self.resultToMi(resdict), args.get('token', -1))) + + def reportInterpreterAsync(self, resdict, asyncclass): + print('interpreterasync=%s,asyncclass="%s"' + % (self.resultToMi(resdict), asyncclass)) + def removeInterpreterBreakpoint(self, args): res = self.sendInterpreterRequest('removebreakpoint', { 'id' : args['id'] }) return res def insertInterpreterBreakpoint(self, args): args['condition'] = self.hexdecode(args.get('condition', '')) - warn("Insert interpreter breakpoint %s:%s (%s)" - % (args['file'], args['line'], args['condition'])) - bp = self.doInsertInterpreterBreakpoint(args, False) - return str(bp) + # Will fail if the service is not yet up and running. + response = self.sendInterpreterRequest('setbreakpoint', args) + resdict = args.copy() + bp = None if response is None else response.get("breakpoint", None) + if bp: + resdict['number'] = bp + resdict['pending'] = 0 + else: + self.createResolvePendingBreakpointsHookBreakpoint(args) + resdict['number'] = -1 + resdict['pending'] = 1 + resdict['warning'] = 'Direct interpreter breakpoint insertion failed.' + self.reportInterpreterResult(resdict, args) + + def resolvePendingInterpreterBreakpoint(self, args): + self.parseAndEvaluate('qt_qmlDebugEnableService("NativeQmlDebugger")') + response = self.sendInterpreterRequest('setbreakpoint', args) + bp = None if response is None else response.get("breakpoint", None) + resdict = args.copy() + if bp: + resdict['number'] = bp + resdict['pending'] = 0 + else: + resdict['number'] = -1 + resdict['pending'] = 0 + resdict['error'] = 'Pending interpreter breakpoint insertion failed.' + self.reportInterpreterAsync(resdict, 'breakpointmodified') + + def fetchInterpreterResult(self): + buf = self.parseAndEvaluate("qt_qmlDebugMessageBuffer") + size = self.parseAndEvaluate("qt_qmlDebugMessageLength") + msg = self.hexdecode(self.readMemory(buf, size)) + # msg is a sequence of 'servicename<space>msglen<space>msg' items. + resdict = {} # Native payload. + while len(msg): + pos0 = msg.index(' ') # End of service name + pos1 = msg.index(' ', pos0 + 1) # End of message length + service = msg[0:pos0] + msglen = int(msg[pos0+1:pos1]) + msgend = pos1+1+msglen + payload = msg[pos1+1:msgend] + msg = msg[msgend:] + if service == 'NativeQmlDebugger': + try: + resdict = json.loads(payload) + continue + except: + warn("Cannot parse native payload: %s" % payload) + else: + print('interpreteralien=%s' + % {'service': service, 'payload': self.hexencode(payload)}) + try: + expr = 'qt_qmlDebugClearBuffer()' + res = self.parseAndEvaluate(expr) + except RuntimeError as error: + warn("Cleaning buffer failed: %s: %s" % (expr, error)) + + return resdict def sendInterpreterRequest(self, command, args = {}): - self.interpreterSeq += 1 - encoded = json.dumps({ - 'seq': self.interpreterSeq, - 'type': 'request', - 'command': command, - 'arguments': args - }) + encoded = json.dumps({ 'command': command, 'arguments': args }) hexdata = self.hexencode(encoded) expr = 'qt_qmlDebugSendDataToService("NativeQmlDebugger","%s")' % hexdata try: @@ -1819,21 +1922,10 @@ class DumperBase: # Happens with LLDB and 'None' current thread. warn("Interpreter command failed: %s: %s" % (encoded, error)) return {} - if not res: warn("Interpreter command failed: %s " % encoded) return {} - resdict = self.readInterpreterOutput() - warn("Interpreter command output: '%s'" % resdict) - service = resdict.get("service") - if service == "NativeQmlDebugger": - messages = resdict.get("messages", []) - if len(messages) == 1: - return messages[0] - warn("Unexpected multiple interpreter messages: %s" % messages) - else: - warn("Interpreter result from alien service: %s" % service) - return {'messages': messages } + return self.fetchInterpreterResult() def executeStep(self, args): if self.nativeMixed: @@ -1856,23 +1948,22 @@ class DumperBase: self.doContinue() def doInsertInterpreterBreakpoint(self, args, wasPending): - warn("DO INSERT INTERPRETER BREAKPOINT, WAS PENDING: %s" % wasPending) + #warn("DO INSERT INTERPRETER BREAKPOINT, WAS PENDING: %s" % wasPending) # Will fail if the service is not yet up and running. response = self.sendInterpreterRequest('setbreakpoint', args) bp = None if response is None else response.get("breakpoint", None) if wasPending: if not bp: - warn("ERROR: Pending interpreter breakpoint insertion failed.") - return -1 + self.reportInterpreterResult({'bpnr': -1, 'pending': 1, + 'error': 'Pending interpreter breakpoint insertion failed.'}, args) + return else: if not bp: - warn("Direct interpreter breakpoint insertion failed.") - warn("Make pending.") + self.reportInterpreterResult({'bpnr': -1, 'pending': 1, + 'warning': 'Direct interpreter breakpoint insertion failed.'}, args) self.createResolvePendingBreakpointsHookBreakpoint(args) - return -1 - - warn("Resolved interpreter breakpoint: BP: %s" % bp) - return int(bp) + return + self.reportInterpreterResult({'bpnr': bp, 'pending': 0}, args) def isInternalInterpreterFrame(self, functionName): if functionName is None: @@ -1902,6 +1993,11 @@ class DumperBase: def extractInterpreterStack(self): return self.sendInterpreterRequest('backtrace', {'limit': 10 }) - def extractInterpreterVariables(self, args): - return self.sendInterpreterRequest('variables', args) + def polishWatchers(self, watchers): + out = [] + for watcher in watchers: + iname = watcher.get('iname') + exp = self.hexdecode(watcher.get('exp')) + out.append({'iname': iname, 'expression': exp, 'name': exp }) + return out diff --git a/share/qtcreator/debugger/gdbbridge.py b/share/qtcreator/debugger/gdbbridge.py index c6baf25b9b..492ec4144a 100644 --- a/share/qtcreator/debugger/gdbbridge.py +++ b/share/qtcreator/debugger/gdbbridge.py @@ -1603,7 +1603,7 @@ class Dumper(DumperBase): objfile = fromNativePath(symtab.objfile.filename) fileName = fromNativePath(symtab.fullname()) - if self.nativeMixed and functionName == "qt_qmlDebugEventFromService": + if self.nativeMixed and functionName == "qt_qmlDebugMessageAvailable": interpreterStack = self.extractInterpreterStack() #print("EXTRACTED INTEPRETER STACK: %s" % interpreterStack) for interpreterFrame in interpreterStack.get('frames', []): @@ -1641,13 +1641,11 @@ class Dumper(DumperBase): self.dumper = dumper self.args = args spec = "qt_qmlDebugConnectorOpen" - print("Preparing hook to resolve pending QML breakpoint at %s" % args) super(Resolver, self).\ __init__(spec, gdb.BP_BREAKPOINT, internal=True, temporary=False) def stop(self): - bp = self.dumper.doInsertInterpreterBreakpoint(args, True) - print("Resolving QML breakpoint %s -> %s" % (args, bp)) + self.dumper.resolvePendingInterpreterBreakpoint(args) self.enabled = False return False @@ -1800,14 +1798,14 @@ registerCommand("threadnames", threadnames) # ####################################################################### -class QmlEngineEventBreakpoint(gdb.Breakpoint): +class InterpreterMessageBreakpoint(gdb.Breakpoint): def __init__(self): - spec = "qt_qmlDebugEventFromService" - super(QmlEngineEventBreakpoint, self).\ + spec = "qt_qmlDebugMessageAvailable" + super(InterpreterMessageBreakpoint, self).\ __init__(spec, gdb.BP_BREAKPOINT, internal=True) def stop(self): print("Interpreter event received.") - return theDumper.handleInterpreterEvent() + return theDumper.handleInterpreterMessage() -QmlEngineEventBreakpoint() +InterpreterMessageBreakpoint() diff --git a/share/qtcreator/debugger/lldbbridge.py b/share/qtcreator/debugger/lldbbridge.py index f54ab9cdd8..093797e7ab 100644 --- a/share/qtcreator/debugger/lldbbridge.py +++ b/share/qtcreator/debugger/lldbbridge.py @@ -693,7 +693,7 @@ class Dumper(DumperBase): if self.nativeMixed: self.interpreterEventBreakpoint = \ - self.target.BreakpointCreateByName("qt_qmlDebugEventFromService") + self.target.BreakpointCreateByName("qt_qmlDebugMessageAvailable") state = 1 if self.target.IsValid() else 0 self.reportResult('success="%s",msg="%s",exe="%s"' % (state, error, self.executable_), args) @@ -871,7 +871,7 @@ class Dumper(DumperBase): functionName = frame.GetFunctionName() - if isNativeMixed and functionName == "::qt_qmlDebugEventFromService()": + if isNativeMixed and functionName == "::qt_qmlDebugMessageAvailable()": interpreterStack = self.extractInterpreterStack() for interpreterFrame in interpreterStack.get('frames', []): function = interpreterFrame.get('function', '') @@ -1341,9 +1341,9 @@ class Dumper(DumperBase): self.reportState("inferiorstopok") self.process.Continue(); return - if functionName == "::qt_qmlDebugEventFromService()": - self.report("EVENT FROM SERVICE") - res = self.handleInterpreterEvent() + if functionName == "::qt_qmlDebugMessageAvailable()": + self.report("ASYNC MESSAGE FROM SERVICE") + res = self.handleInterpreterMessage() if not res: self.report("EVENT NEEDS NO STOP") self.reportState("stopped") @@ -1427,7 +1427,7 @@ class Dumper(DumperBase): if bpType == BreakpointByFileAndLine: fileName = args["file"] if fileName.endswith(".js") or fileName.endswith(".qml"): - self.doInsertInterpreterBreakpoint(args, False) + self.insertInterpreterBreakpoint(args) return extra = '' @@ -1742,7 +1742,7 @@ class Dumper(DumperBase): bp = self.target.BreakpointCreateByName("qt_qmlDebugConnectorOpen") bp.SetOneShot(True) self.interpreterBreakpointResolvers.append( - lambda: self.doInsertInterpreterBreakpoint(args, True)) + lambda: self.resolvePendingInterpreterBreakpoint(args)) # Used in dumper auto test. diff --git a/share/qtcreator/debugger/qttypes.py b/share/qtcreator/debugger/qttypes.py index 41f515e542..5518a12065 100644 --- a/share/qtcreator/debugger/qttypes.py +++ b/share/qtcreator/debugger/qttypes.py @@ -2308,7 +2308,7 @@ def qdump__QV4__String(d, value): d.putStringValue(d.addressOf(value) + 2 * d.ptrSize()) def qdump__QV4__Value(d, value): - v = toInteger(str(value["val"])) + v = toInteger(str(value["_val"])) NaNEncodeMask = 0xffff800000000000 IsInt32Mask = 0x0002000000000000 IsDoubleMask = 0xfffc000000000000 @@ -2319,7 +2319,10 @@ def qdump__QV4__Value(d, value): ns = d.qtNamespace() if v & IsInt32Mask: d.putBetterType("%sQV4::Value (int32)" % ns) - d.putValue(value["int_32"]) + vv = v & 0xffffffff + vv = vv if vv < 0x80000000 else -(0x100000000 - vv) + d.putBetterType("%sQV4::Value (int32)" % ns) + d.putValue("%d" % vv) elif v & IsDoubleMask: d.putBetterType("%sQV4::Value (double)" % ns) d.putValue("%x" % (v ^ 0xffff800000000000), Hex2EncodedFloat8) @@ -2332,6 +2335,7 @@ def qdump__QV4__Value(d, value): elif v & IsNullOrBooleanMask: d.putBetterType("%sQV4::Value (null/bool)" % ns) d.putValue("(null/bool)") + d.putValue(v & 1) else: vtable = value["m"]["vtable"] if toInteger(vtable["isString"]): diff --git a/src/plugins/debugger/breakhandler.cpp b/src/plugins/debugger/breakhandler.cpp index b95d196589..6f913f5a1c 100644 --- a/src/plugins/debugger/breakhandler.cpp +++ b/src/plugins/debugger/breakhandler.cpp @@ -1470,6 +1470,8 @@ QString BreakpointItem::toToolTip() const << "</td><td>" << QDir::toNativeSeparators(markerFileName()) << "</td></tr>" << "<tr><td>" << tr("Marker Line:") << "</td><td>" << markerLineNumber() << "</td></tr>" + << "<tr><td>" << tr("Hit Count:") + << "</td><td>" << m_response.hitCount << "</td></tr>" << "</table><br><hr><table>" << "<tr><th>" << tr("Property") << "</th><th>" << tr("Requested") diff --git a/src/plugins/debugger/debuggerprotocol.cpp b/src/plugins/debugger/debuggerprotocol.cpp index 7f3525b0a0..35c2d220f3 100644 --- a/src/plugins/debugger/debuggerprotocol.cpp +++ b/src/plugins/debugger/debuggerprotocol.cpp @@ -877,6 +877,8 @@ DebuggerEncoding debuggerEncoding(const QByteArray &data) return SpecialNullValue; if (data == "itemcount") return SpecialItemCountValue; + if (data == "notaccessible") + return SpecialNotAccessibleValue; return DebuggerEncoding(data.toInt()); } diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index 6563e527e0..5303b65c9b 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -437,206 +437,34 @@ void GdbEngine::handleResponse(const QByteArray &buff) result.m_type = GdbMi::Tuple; } } - if (asyncClass == "stopped") { - if (m_inUpdateLocals) { - showMessage(_("UNEXPECTED *stopped NOTIFICATION IGNORED"), LogWarning); - } else { - handleStopResponse(result); - m_pendingLogStreamOutput.clear(); - m_pendingConsoleStreamOutput.clear(); - } - } else if (asyncClass == "running") { - if (m_inUpdateLocals) { - showMessage(_("UNEXPECTED *running NOTIFICATION IGNORED"), LogWarning); - } else { - GdbMi threads = result["thread-id"]; - threadsHandler()->notifyRunning(threads.data()); - if (state() == InferiorRunOk || state() == InferiorSetupRequested) { - // We get multiple *running after thread creation and in Windows terminals. - showMessage(QString::fromLatin1("NOTE: INFERIOR STILL RUNNING IN STATE %1."). - arg(QLatin1String(DebuggerEngine::stateName(state())))); - } else if (HostOsInfo::isWindowsHost() && (state() == InferiorStopRequested - || state() == InferiorShutdownRequested)) { - // FIXME: Breakpoints on Windows are exceptions which are thrown in newly - // created threads so we have to filter out the running threads messages when - // we request a stop. - } else { - notifyInferiorRunOk(); - } - } - } else if (asyncClass == "library-loaded") { - // Archer has 'id="/usr/lib/libdrm.so.2", - // target-name="/usr/lib/libdrm.so.2", - // host-name="/usr/lib/libdrm.so.2", - // symbols-loaded="0" - - // id="/lib/i386-linux-gnu/libc.so.6" - // target-name="/lib/i386-linux-gnu/libc.so.6" - // host-name="/lib/i386-linux-gnu/libc.so.6" - // symbols-loaded="0",thread-group="i1" - QByteArray id = result["id"].data(); - if (!id.isEmpty()) - showStatusMessage(tr("Library %1 loaded").arg(_(id)), 1000); - progressPing(); - Module module; - module.startAddress = 0; - module.endAddress = 0; - module.hostPath = _(result["host-name"].data()); - module.modulePath = _(result["target-name"].data()); - module.moduleName = QFileInfo(module.hostPath).baseName(); - modulesHandler()->updateModule(module); - } else if (asyncClass == "library-unloaded") { - // Archer has 'id="/usr/lib/libdrm.so.2", - // target-name="/usr/lib/libdrm.so.2", - // host-name="/usr/lib/libdrm.so.2" - QByteArray id = result["id"].data(); - progressPing(); - showStatusMessage(tr("Library %1 unloaded").arg(_(id)), 1000); - } else if (asyncClass == "thread-group-added") { - // 7.1-symbianelf has "{id="i1"}" - } else if (asyncClass == "thread-group-created" - || asyncClass == "thread-group-started") { - // Archer had only "{id="28902"}" at some point of 6.8.x. - // *-started seems to be standard in 7.1, but in early - // 7.0.x, there was a *-created instead. - progressPing(); - // 7.1.50 has thread-group-started,id="i1",pid="3529" - QByteArray id = result["id"].data(); - showStatusMessage(tr("Thread group %1 created").arg(_(id)), 1000); - int pid = id.toInt(); - if (!pid) { - id = result["pid"].data(); - pid = id.toInt(); - } - if (pid) - notifyInferiorPid(pid); - handleThreadGroupCreated(result); - } else if (asyncClass == "thread-created") { - //"{id="1",group-id="28902"}" - QByteArray id = result["id"].data(); - showStatusMessage(tr("Thread %1 created").arg(_(id)), 1000); - ThreadData thread; - thread.id = ThreadId(id.toLong()); - thread.groupId = result["group-id"].data(); - threadsHandler()->updateThread(thread); - } else if (asyncClass == "thread-group-exited") { - // Archer has "{id="28902"}" - QByteArray id = result["id"].data(); - showStatusMessage(tr("Thread group %1 exited").arg(_(id)), 1000); - handleThreadGroupExited(result); - } else if (asyncClass == "thread-exited") { - //"{id="1",group-id="28902"}" - QByteArray id = result["id"].data(); - QByteArray groupid = result["group-id"].data(); - showStatusMessage(tr("Thread %1 in group %2 exited") - .arg(_(id)).arg(_(groupid)), 1000); - threadsHandler()->removeThread(ThreadId(id.toLong())); - } else if (asyncClass == "thread-selected") { - QByteArray id = result["id"].data(); - showStatusMessage(tr("Thread %1 selected").arg(_(id)), 1000); - //"{id="2"}" - } else if (asyncClass == "breakpoint-modified") { - // New in FSF gdb since 2011-04-27. - // "{bkpt={number="3",type="breakpoint",disp="keep", - // enabled="y",addr="<MULTIPLE>",times="1", - // original-location="\\",simple_gdbtest_app.cpp\\":135"}, - // {number="3.1",enabled="y",addr="0x0805ff68", - // func="Vector<int>::Vector(int)", - // file="simple_gdbtest_app.cpp", - // fullname="/data/...line="135"},{number="3.2"...}}.." - - // Note the leading comma in original-location. Filter it out. - // We don't need the field anyway. - QByteArray ba = result.toString(); - ba = '[' + ba.mid(6, ba.size() - 7) + ']'; - const int pos1 = ba.indexOf(",original-location"); - const int pos2 = ba.indexOf("\":", pos1 + 2); - const int pos3 = ba.indexOf('"', pos2 + 2); - ba.remove(pos1, pos3 - pos1 + 1); - result = GdbMi(); - result.fromString(ba); - BreakHandler *handler = breakHandler(); - Breakpoint bp; - BreakpointResponse br; - foreach (const GdbMi &bkpt, result.children()) { - const QByteArray nr = bkpt["number"].data(); - BreakpointResponseId rid(nr); - if (!isHiddenBreakpoint(rid)) { - if (nr.contains('.')) { - // A sub-breakpoint. - BreakpointResponse sub; - updateResponse(sub, bkpt); - sub.id = rid; - sub.type = br.type; - bp.insertSubBreakpoint(sub); - } else { - // A primary breakpoint. - bp = handler->findBreakpointByResponseId(rid); - //qDebug() << "NR: " << nr << "RID: " << rid - // << "ID: " << bp.id(); - br = bp.response(); - updateResponse(br, bkpt); - bp.setResponse(br); - } - } - } - } else if (asyncClass == "breakpoint-created") { - // "{bkpt={number="1",type="breakpoint",disp="del",enabled="y", - // addr="<PENDING>",pending="main",times="0", - // original-location="main"}}" -- or -- - // {bkpt={number="2",type="hw watchpoint",disp="keep",enabled="y", - // what="*0xbfffed48",times="0",original-location="*0xbfffed48"}} - BreakHandler *handler = breakHandler(); - foreach (const GdbMi &bkpt, result.children()) { - BreakpointResponse br; - br.type = BreakpointByFileAndLine; - updateResponse(br, bkpt); - handler->handleAlienBreakpoint(br, this); - } - } else if (asyncClass == "breakpoint-deleted") { - // "breakpoint-deleted" "{id="1"}" - // New in FSF gdb since 2011-04-27. - QByteArray nr = result["id"].data(); - BreakpointResponseId rid(nr); - if (Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid)) { - // This also triggers when a temporary breakpoint is hit. - // We do not really want that, as this loses all information. - // FIXME: Use a special marker for this case? - // if (!bp.isOneShot()) ... is not sufficient. - // It keeps temporary "Jump" breakpoints alive. - bp.removeAlienBreakpoint(); - } - } else if (asyncClass == "cmd-param-changed") { - // New since 2012-08-09 - // "{param="debug remote",value="1"}" - } else if (asyncClass == "memory-changed") { - // New since 2013 - // "{thread-group="i1",addr="0x0918a7a8",len="0x10"}" - } else if (asyncClass == "tsv-created") { - // New since 2013-02-06 - } else if (asyncClass == "tsv-modified") { - // New since 2013-02-06 - } else { - qDebug() << "IGNORED ASYNC OUTPUT" - << asyncClass << result.toString(); - } + handleAsyncOutput(asyncClass, result); break; } case '~': { QByteArray data = GdbMi::parseCString(from, to); if (data.startsWith("bridgemessage={")) { - //showMessage(_(data), LogDebug); + // It's already logged. break; } - if (data.startsWith("bridgeresult={")) { - //showMessage(_(data), LogDebug); + if (data.startsWith("interpreterresult={")) { + GdbMi allData; + allData.fromStringMultiple(data); DebuggerResponse response; response.resultClass = ResultDone; - response.data.fromStringMultiple(data); + response.data = allData["interpreterresult"]; + response.token = allData["token"].toInt(); handleResultRecord(&response); break; } + if (data.startsWith("interpreterasync={")) { + GdbMi allData; + allData.fromStringMultiple(data); + QByteArray asyncClass = allData["asyncclass"].data(); + if (asyncClass == "breakpointmodified") + handleInterpreterBreakpointModified(allData["interpreterasync"]); + break; + } m_pendingConsoleStreamOutput += data; // Parse pid from noise. @@ -781,6 +609,191 @@ void GdbEngine::handleResponse(const QByteArray &buff) } } +void GdbEngine::handleAsyncOutput(const QByteArray &asyncClass, const GdbMi &result) +{ + if (asyncClass == "stopped") { + if (m_inUpdateLocals) { + showMessage(_("UNEXPECTED *stopped NOTIFICATION IGNORED"), LogWarning); + } else { + handleStopResponse(result); + m_pendingLogStreamOutput.clear(); + m_pendingConsoleStreamOutput.clear(); + } + } else if (asyncClass == "running") { + if (m_inUpdateLocals) { + showMessage(_("UNEXPECTED *running NOTIFICATION IGNORED"), LogWarning); + } else { + GdbMi threads = result["thread-id"]; + threadsHandler()->notifyRunning(threads.data()); + if (state() == InferiorRunOk || state() == InferiorSetupRequested) { + // We get multiple *running after thread creation and in Windows terminals. + showMessage(QString::fromLatin1("NOTE: INFERIOR STILL RUNNING IN STATE %1."). + arg(QLatin1String(DebuggerEngine::stateName(state())))); + } else if (HostOsInfo::isWindowsHost() && (state() == InferiorStopRequested + || state() == InferiorShutdownRequested)) { + // FIXME: Breakpoints on Windows are exceptions which are thrown in newly + // created threads so we have to filter out the running threads messages when + // we request a stop. + } else { + notifyInferiorRunOk(); + } + } + } else if (asyncClass == "library-loaded") { + // Archer has 'id="/usr/lib/libdrm.so.2", + // target-name="/usr/lib/libdrm.so.2", + // host-name="/usr/lib/libdrm.so.2", + // symbols-loaded="0" + + // id="/lib/i386-linux-gnu/libc.so.6" + // target-name="/lib/i386-linux-gnu/libc.so.6" + // host-name="/lib/i386-linux-gnu/libc.so.6" + // symbols-loaded="0",thread-group="i1" + QByteArray id = result["id"].data(); + if (!id.isEmpty()) + showStatusMessage(tr("Library %1 loaded").arg(_(id)), 1000); + progressPing(); + Module module; + module.startAddress = 0; + module.endAddress = 0; + module.hostPath = _(result["host-name"].data()); + module.modulePath = _(result["target-name"].data()); + module.moduleName = QFileInfo(module.hostPath).baseName(); + modulesHandler()->updateModule(module); + } else if (asyncClass == "library-unloaded") { + // Archer has 'id="/usr/lib/libdrm.so.2", + // target-name="/usr/lib/libdrm.so.2", + // host-name="/usr/lib/libdrm.so.2" + QByteArray id = result["id"].data(); + progressPing(); + showStatusMessage(tr("Library %1 unloaded").arg(_(id)), 1000); + } else if (asyncClass == "thread-group-added") { + // 7.1-symbianelf has "{id="i1"}" + } else if (asyncClass == "thread-group-created" + || asyncClass == "thread-group-started") { + // Archer had only "{id="28902"}" at some point of 6.8.x. + // *-started seems to be standard in 7.1, but in early + // 7.0.x, there was a *-created instead. + progressPing(); + // 7.1.50 has thread-group-started,id="i1",pid="3529" + QByteArray id = result["id"].data(); + showStatusMessage(tr("Thread group %1 created").arg(_(id)), 1000); + int pid = id.toInt(); + if (!pid) { + id = result["pid"].data(); + pid = id.toInt(); + } + if (pid) + notifyInferiorPid(pid); + handleThreadGroupCreated(result); + } else if (asyncClass == "thread-created") { + //"{id="1",group-id="28902"}" + QByteArray id = result["id"].data(); + showStatusMessage(tr("Thread %1 created").arg(_(id)), 1000); + ThreadData thread; + thread.id = ThreadId(id.toLong()); + thread.groupId = result["group-id"].data(); + threadsHandler()->updateThread(thread); + } else if (asyncClass == "thread-group-exited") { + // Archer has "{id="28902"}" + QByteArray id = result["id"].data(); + showStatusMessage(tr("Thread group %1 exited").arg(_(id)), 1000); + handleThreadGroupExited(result); + } else if (asyncClass == "thread-exited") { + //"{id="1",group-id="28902"}" + QByteArray id = result["id"].data(); + QByteArray groupid = result["group-id"].data(); + showStatusMessage(tr("Thread %1 in group %2 exited") + .arg(_(id)).arg(_(groupid)), 1000); + threadsHandler()->removeThread(ThreadId(id.toLong())); + } else if (asyncClass == "thread-selected") { + QByteArray id = result["id"].data(); + showStatusMessage(tr("Thread %1 selected").arg(_(id)), 1000); + //"{id="2"}" + } else if (asyncClass == "breakpoint-modified") { + // New in FSF gdb since 2011-04-27. + // "{bkpt={number="3",type="breakpoint",disp="keep", + // enabled="y",addr="<MULTIPLE>",times="1", + // original-location="\\",simple_gdbtest_app.cpp\\":135"}, + // {number="3.1",enabled="y",addr="0x0805ff68", + // func="Vector<int>::Vector(int)", + // file="simple_gdbtest_app.cpp", + // fullname="/data/...line="135"},{number="3.2"...}}.." + + // Note the leading comma in original-location. Filter it out. + // We don't need the field anyway. + QByteArray ba = result.toString(); + ba = '[' + ba.mid(6, ba.size() - 7) + ']'; + const int pos1 = ba.indexOf(",original-location"); + const int pos2 = ba.indexOf("\":", pos1 + 2); + const int pos3 = ba.indexOf('"', pos2 + 2); + ba.remove(pos1, pos3 - pos1 + 1); + GdbMi res; + res.fromString(ba); + BreakHandler *handler = breakHandler(); + Breakpoint bp; + BreakpointResponse br; + foreach (const GdbMi &bkpt, res.children()) { + const QByteArray nr = bkpt["number"].data(); + BreakpointResponseId rid(nr); + if (!isHiddenBreakpoint(rid)) { + if (nr.contains('.')) { + // A sub-breakpoint. + BreakpointResponse sub; + updateResponse(sub, bkpt); + sub.id = rid; + sub.type = br.type; + bp.insertSubBreakpoint(sub); + } else { + // A primary breakpoint. + bp = handler->findBreakpointByResponseId(rid); + br = bp.response(); + updateResponse(br, bkpt); + bp.setResponse(br); + } + } + } + } else if (asyncClass == "breakpoint-created") { + // "{bkpt={number="1",type="breakpoint",disp="del",enabled="y", + // addr="<PENDING>",pending="main",times="0", + // original-location="main"}}" -- or -- + // {bkpt={number="2",type="hw watchpoint",disp="keep",enabled="y", + // what="*0xbfffed48",times="0",original-location="*0xbfffed48"}} + BreakHandler *handler = breakHandler(); + foreach (const GdbMi &bkpt, result.children()) { + BreakpointResponse br; + br.type = BreakpointByFileAndLine; + updateResponse(br, bkpt); + handler->handleAlienBreakpoint(br, this); + } + } else if (asyncClass == "breakpoint-deleted") { + // "breakpoint-deleted" "{id="1"}" + // New in FSF gdb since 2011-04-27. + QByteArray nr = result["id"].data(); + BreakpointResponseId rid(nr); + if (Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid)) { + // This also triggers when a temporary breakpoint is hit. + // We do not really want that, as this loses all information. + // FIXME: Use a special marker for this case? + // if (!bp.isOneShot()) ... is not sufficient. + // It keeps temporary "Jump" breakpoints alive. + bp.removeAlienBreakpoint(); + } + } else if (asyncClass == "cmd-param-changed") { + // New since 2012-08-09 + // "{param="debug remote",value="1"}" + } else if (asyncClass == "memory-changed") { + // New since 2013 + // "{thread-group="i1",addr="0x0918a7a8",len="0x10"}" + } else if (asyncClass == "tsv-created") { + // New since 2013-02-06 + } else if (asyncClass == "tsv-modified") { + // New since 2013-02-06 + } else { + qDebug() << "IGNORED ASYNC OUTPUT" + << asyncClass << result.toString(); + } +} + void GdbEngine::readGdbStandardError() { QByteArray err = m_gdbProc.readAllStandardError(); @@ -901,9 +914,13 @@ void GdbEngine::runCommand(const QByteArray &command, int flags) void GdbEngine::runCommand(const DebuggerCommand &command) { + const int token = ++currentToken(); + DebuggerCommand cmd = command; - if (command.flags & PythonCommand) + if (command.flags & PythonCommand) { + cmd.arg("token", token); cmd.function = "python theDumper." + cmd.function + "(" + cmd.argsToPython() + ")"; + } if (!stateAcceptsGdbCommands(state())) { PENDING_DEBUG(_("NO GDB PROCESS RUNNING, CMD IGNORED: " + cmd.function)); @@ -943,8 +960,6 @@ void GdbEngine::runCommand(const DebuggerCommand &command) QTC_ASSERT(m_gdbProc.state() == QProcess::Running, return); - const int token = ++currentToken(); - cmd.postTime = QTime::currentTime().msecsSinceStartOfDay(); m_commandForToken[token] = cmd; m_flagsForToken[token] = command.flags; @@ -1417,7 +1432,7 @@ void GdbEngine::handleStopResponse(const GdbMi &data) && QFileInfo::exists(fullName) && !isQFatalBreakpoint(rid) && function != "qt_v4TriggeredBreakpointHook" - && function != "qt_qmlDebugEventFromService" + && function != "qt_qmlDebugMessageAvailable" && language != "js") gotoLocation(Location(fullName, lineNumber)); @@ -2356,6 +2371,8 @@ void GdbEngine::updateResponse(BreakpointResponse &response, const GdbMi &bkpt) else if (catchType == "syscall") response.type = BreakpointAtSysCall; } + } else if (child.hasName("hitcount")) { + response.hitCount = child.toInt(); } else if (child.hasName("original-location")) { originalLocation = child.data(); } @@ -2432,6 +2449,29 @@ QByteArray GdbEngine::breakpointLocation2(const BreakpointParameters &data) + QByteArray::number(data.lineNumber); } +void GdbEngine::handleInsertInterpreterBreakpoint(const DebuggerResponse &response, Breakpoint bp) +{ + BreakpointResponse br = bp.response(); + bool pending = response.data["pending"].toInt(); + if (pending) { + bp.notifyBreakpointInsertOk(); + } else { + br.id = BreakpointResponseId(response.data["number"].data()); + updateResponse(br, response.data); + bp.setResponse(br); + bp.notifyBreakpointInsertOk(); + } +} + +void GdbEngine::handleInterpreterBreakpointModified(const GdbMi &data) +{ + BreakpointModelId id(data["modelid"].data()); + Breakpoint bp = breakHandler()->breakpointById(id); + BreakpointResponse br = bp.response(); + updateResponse(br, data); + bp.setResponse(br); +} + void GdbEngine::handleWatchInsert(const DebuggerResponse &response, Breakpoint bp) { if (bp && response.resultClass == ResultDone) { @@ -2696,10 +2736,10 @@ void GdbEngine::insertBreakpoint(Breakpoint bp) const BreakpointParameters &data = bp.parameters(); if (!data.isCppBreakpoint()) { - DebuggerCommand cmd("insertInterpreterBreakpoint", PythonCommand); + DebuggerCommand cmd("insertInterpreterBreakpoint", PythonCommand | NeedsStop); bp.addToCommand(&cmd); + cmd.callback = [this, bp](const DebuggerResponse &r) { handleInsertInterpreterBreakpoint(r, bp); }; runCommand(cmd); - bp.notifyBreakpointInsertOk(); return; } diff --git a/src/plugins/debugger/gdb/gdbengine.h b/src/plugins/debugger/gdb/gdbengine.h index 70e2816404..cee83ca3a9 100644 --- a/src/plugins/debugger/gdb/gdbengine.h +++ b/src/plugins/debugger/gdb/gdbengine.h @@ -211,6 +211,7 @@ private: private: ////////// Gdb Output, State & Capability Handling ////////// protected: Q_SLOT void handleResponse(const QByteArray &buff); + void handleAsyncOutput(const QByteArray &asyncClass, const GdbMi &result); void handleStopResponse(const GdbMi &data); void handleResultRecord(DebuggerResponse *response); void handleStop1(const GdbMi &data); @@ -282,6 +283,8 @@ private: ////////// View & Data Stuff ////////// void handleBreakCondition(const DebuggerResponse &response, Breakpoint bp); void handleBreakThreadSpec(const DebuggerResponse &response, Breakpoint bp); void handleBreakLineNumber(const DebuggerResponse &response, Breakpoint bp); + void handleInsertInterpreterBreakpoint(const DebuggerResponse &response, Breakpoint bp); + void handleInterpreterBreakpointModified(const GdbMi &data); void handleWatchInsert(const DebuggerResponse &response, Breakpoint bp); void handleCatchInsert(const DebuggerResponse &response, Breakpoint bp); void handleBkpt(const GdbMi &bkpt, Breakpoint bp); diff --git a/src/plugins/debugger/lldb/lldbengine.cpp b/src/plugins/debugger/lldb/lldbengine.cpp index 3a04f5bb52..c9536e92c6 100644 --- a/src/plugins/debugger/lldb/lldbengine.cpp +++ b/src/plugins/debugger/lldb/lldbengine.cpp @@ -954,7 +954,7 @@ void LldbEngine::handleLocationNotification(const GdbMi &reportedLocation) // Quickly set the location marker. if (lineNumber > 0 && QFileInfo::exists(fileName) - && function != "::qt_qmlDebugEventFromService()") + && function != "::qt_qmlDebugMessageAvailable()") gotoLocation(Location(fileName, lineNumber)); } diff --git a/src/plugins/debugger/stackframe.cpp b/src/plugins/debugger/stackframe.cpp index 11fa1a99e5..689437b52a 100644 --- a/src/plugins/debugger/stackframe.cpp +++ b/src/plugins/debugger/stackframe.cpp @@ -167,6 +167,21 @@ QString StackFrame::toToolTip() const return res; } +static QString findFile(const QString &baseDir, const QString &relativeFile) +{ + QDir dir(baseDir); + while (true) { + const QString path = dir.absoluteFilePath(relativeFile); + const QFileInfo fi(path); + if (fi.isFile()) + return path; + if (dir.isRoot()) + break; + dir.cdUp(); + } + return QString(); +} + // Try to resolve files coming from resource files. void StackFrame::fixQrcFrame(const DebuggerRunParameters &rp) { @@ -179,21 +194,19 @@ void StackFrame::fixQrcFrame(const DebuggerRunParameters &rp) } if (!file.startsWith(QLatin1String("qrc:/"))) return; - const QString relativeFile = file.right(file.size() - 5); - if (rp.projectSourceDirectory.isEmpty()) - return; - const QFileInfo pFi(rp.projectSourceDirectory + QLatin1Char('/') + relativeFile); - if (pFi.isFile()) { - file = pFi.absoluteFilePath(); - usable = true; - return; - } - const QFileInfo cFi(QDir::currentPath() + QLatin1Char('/') + relativeFile); - if (cFi.isFile()) { - file = cFi.absoluteFilePath(); - usable = true; + + QString relativeFile = file.right(file.size() - 5); + while (relativeFile.startsWith(QLatin1Char('/'))) + relativeFile = relativeFile.mid(1); + + QString absFile = findFile(rp.projectSourceDirectory, relativeFile); + if (absFile.isEmpty()) + absFile = findFile(QDir::currentPath(), relativeFile); + + if (absFile.isEmpty()) return; - } + file = absFile; + usable = true; } QDebug operator<<(QDebug d, const StackFrame &f) diff --git a/src/plugins/debugger/watchdata.cpp b/src/plugins/debugger/watchdata.cpp index 188fe5cd82..8b271c92b1 100644 --- a/src/plugins/debugger/watchdata.cpp +++ b/src/plugins/debugger/watchdata.cpp @@ -391,8 +391,8 @@ QByteArray WatchData::hexAddress() const void WatchData::updateValue(const GdbMi &item) { GdbMi value = item["value"]; - if (value.isValid()) { - DebuggerEncoding encoding = debuggerEncoding(item["valueencoded"].data()); + DebuggerEncoding encoding = debuggerEncoding(item["valueencoded"].data()); + if (value.isValid() || encoding != Unencoded8Bit) { setValue(decodeData(value.data(), encoding)); } else { setValueNeeded(); @@ -576,7 +576,8 @@ void parseChildrenData(const WatchData &data0, const GdbMi &item, setWatchDataValueEnabled(data, item["valueenabled"]); setWatchDataValueEditable(data, item["valueeditable"]); - data.updateChildCount(item["numchild"]); + data.updateChildCount(item["numchild"]); // GDB/MI + data.updateChildCount(item["haschild"]); // native-mixed itemHandler(data); bool ok = false; |