Commit b8ae9fd4 authored by hjk's avatar hjk Committed by Christian Stenger

Debugger: Adjust native mixed debugging after upstream changes

Change-Id: I4d137fadd0de2aa346f2f49932faac4ee9ed41e7
Reviewed-by: default avatarUlf Hermann <ulf.hermann@theqtcompany.com>
Reviewed-by: default avatarChristian Stenger <christian.stenger@theqtcompany.com>
parent be1e0f7e
......@@ -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
......@@ -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()
......@@ -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.
......
......@@ -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"]):
......
......@@ -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")
......
......@@ -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());
}
......
......@@ -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.