Debugger: Make basic native-mixed debugging work with LLDB

Change-Id: I4d55c6a486d5adbccaa93eaa1ee461238fecfea3
Reviewed-by: Christian Stenger <christian.stenger@theqtcompany.com>
This commit is contained in:
hjk
2015-10-09 15:00:20 +02:00
parent d6ef70573d
commit ec2e01faec
7 changed files with 176 additions and 177 deletions

View File

@@ -841,9 +841,32 @@ class DumperBase:
return '{' + ','.join(['%s=%s' % (k, self.dictToMi(v))
for (k, v) in list(value.items())]) + '}'
if type(value) is list:
return '[' + ','.join([self.dictToMi(v) for v in value]) + ']'
index = 0
pairs = []
for item in value:
name = item.get('name', '')
if len(name) == 0:
name = str(index)
index += 1
pairs.append((name, self.dictToMi(item)))
pairs.sort(key = lambda pair: pair[0])
return '[' + ','.join([pair[1] for pair in pairs]) + ']'
return '"%s"' % value
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)
def putField(self, name, value):
self.put('%s="%s",' % (name, value))
@@ -1763,7 +1786,7 @@ class DumperBase:
buf = self.parseAndEvaluate("qt_qmlDebugEventBuffer")
size = self.parseAndEvaluate("qt_qmlDebugEventLength")
resdict = self.readJsonFromMemory(buf, size)
warn("RES DICT : %s" % resdict)
warn("Interpreter event received: %s" % resdict)
return resdict.get('event') == 'break'
def removeInterpreterBreakpoint(self, args):
@@ -1779,8 +1802,12 @@ class DumperBase:
def sendInterpreterRequest(self, command, args = {}):
self.interpreterSeq += 1
cmd = { 'seq': self.interpreterSeq, 'type': 'request', 'command': command, 'arguments': args }
encoded = json.dumps(cmd)
encoded = json.dumps({
'seq': self.interpreterSeq,
'type': 'request',
'command': command,
'arguments': args
})
hexdata = self.hexencode(encoded)
expr = 'qt_qmlDebugSendDataToService("NativeQmlDebugger","%s")' % hexdata
try:

View File

@@ -220,12 +220,11 @@ class Dumper(DumperBase):
# These values will be kept between calls to 'fetchVariables'.
self.isGdb = True
self.childEventAddress = None
self.typeCache = {}
self.typesReported = {}
self.typesToReport = {}
self.qtNamespaceToReport = None
self.interpreterBreakpoints = []
self.interpreterBreakpointResolvers = []
def prepare(self, args):
self.output = []
@@ -359,12 +358,9 @@ class Dumper(DumperBase):
partialVariable = args.get("partialVariable", "")
isPartial = len(partialVariable) > 0
if self.nativeMixed:
context = args.get('context', '')
if len(context):
res = self.extractInterpreterVariables(args)
if res:
safePrint('data=%s' % self.dictToMi(res.get('data', {})))
(ok, res) = self.tryFetchInterpreterVariables(args)
if ok:
safePrint(res)
return
#
@@ -1655,7 +1651,7 @@ class Dumper(DumperBase):
self.enabled = False
return False
self.interpreterBreakpoints.append(Resolver(self, args))
self.interpreterBreakpointResolvers.append(Resolver(self, args))
def exitGdb(self, _):
gdb.execute("quit")
@@ -1804,18 +1800,6 @@ registerCommand("threadnames", threadnames)
#
#######################################################################
class TriggeredBreakpointHookBreakpoint(gdb.Breakpoint):
def __init__(self):
spec = "qt_v4TriggeredBreakpointHook"
super(TriggeredBreakpointHookBreakpoint, self).\
__init__(spec, gdb.BP_BREAKPOINT, internal=True)
def stop(self):
print("QML engine stopped.")
return True
TriggeredBreakpointHookBreakpoint()
class QmlEngineEventBreakpoint(gdb.Breakpoint):
def __init__(self):
spec = "qt_qmlDebugEventFromService"
@@ -1823,7 +1807,7 @@ class QmlEngineEventBreakpoint(gdb.Breakpoint):
__init__(spec, gdb.BP_BREAKPOINT, internal=True)
def stop(self):
print("QML engine event received.")
print("Interpreter event received.")
return theDumper.handleInterpreterEvent()
QmlEngineEventBreakpoint()

View File

@@ -48,9 +48,6 @@ from dumper import *
qqWatchpointOffset = 10000
def warn(message):
print('\n\nWARNING="%s",\n' % message.encode("latin1").replace('"', "'"))
def showException(msg, exType, exValue, exTraceback):
warn("**** CAUGHT EXCEPTION: %s ****" % msg)
import traceback
@@ -230,8 +227,7 @@ class Dumper(DumperBase):
self.voidPtrType_ = None
self.isShuttingDown_ = False
self.isInterrupting_ = False
self.qmlBreakpointResolvers = {}
self.qmlTriggeredBreakpoint = None
self.interpreterBreakpointResolvers = []
self.report('lldbversion=\"%s\"' % lldb.SBDebugger.GetVersionString())
self.reportState("enginesetupok")
@@ -549,7 +545,7 @@ class Dumper(DumperBase):
self.report('error="%s"' % result.GetError())
def put(self, stuff):
self.out += stuff
self.output += stuff
def isMovableType(self, type):
if type.GetTypeClass() in (lldb.eTypeClassBuiltin, lldb.eTypeClassPointer):
@@ -667,6 +663,7 @@ class Dumper(DumperBase):
self.sysRoot_ = args.get('sysRoot', '')
self.remoteChannel_ = args.get('remoteChannel', '')
self.platform_ = args.get('platform', '')
self.nativeMixed = int(args.get('nativemixed', 0))
self.ignoreStops = 0
self.silentStops = 0
@@ -689,12 +686,15 @@ class Dumper(DumperBase):
if self.sysRoot_:
self.debugger.SetCurrentPlatformSDKRoot(self.sysRoot_)
if os.path.isfile(self.executable_):
self.target = self.debugger.CreateTarget(self.executable_, None, None, True, error)
else:
self.target = self.debugger.CreateTarget(None, None, None, True, error)
if self.nativeMixed:
self.interpreterEventBreakpoint = \
self.target.BreakpointCreateByName("qt_qmlDebugEventFromService")
state = 1 if self.target.IsValid() else 0
self.reportResult('success="%s",msg="%s",exe="%s"' % (state, error, self.executable_), args)
@@ -775,9 +775,11 @@ class Dumper(DumperBase):
def describeLocation(self, frame):
if int(frame.pc) == 0xffffffffffffffff:
return ''
file = fileNameAsString(frame.line_entry.file)
fileName = fileNameAsString(frame.line_entry.file)
function = frame.GetFunctionName()
line = frame.line_entry.line
return 'location={file="%s",line="%s",addr="%s"}' % (file, line, frame.pc)
return 'location={file="%s",line="%s",address="%s",function="%s"}' \
% (fileName, line, frame.pc, function)
def currentThread(self):
return None if self.process is None else self.process.GetSelectedThread()
@@ -847,8 +849,6 @@ class Dumper(DumperBase):
self.reportResult('msg="No thread"', args)
return
self.report(self.describeLocation(thread.GetFrameAtIndex(0))) # FIXME
isNativeMixed = int(args.get('nativemixed', 0))
limit = args.get('stacklimit', -1)
@@ -868,38 +868,27 @@ class Dumper(DumperBase):
pc = frame.GetPC()
level = frame.idx
addr = frame.GetPCAddress().GetLoadAddress(self.target)
functionName = frame.GetFunctionName()
if isNativeMixed and functionName == "::qt_qmlDebugEventFromService()":
interpreterStack = self.extractInterpreterStack()
for interpreterFrame in interpreterStack.get('frames', []):
function = interpreterFrame.get('function', '')
fileName = interpreterFrame.get('file', '')
language = interpreterFrame.get('language', '')
lineNumber = interpreterFrame.get('line', 0)
context = interpreterFrame.get('context', 0)
result += ('frame={function="%s",file="%s",'
'line="%s",language="%s",context="%s"}'
% (function, fileName, lineNumber, language, context))
fileName = fileNameAsString(lineEntry.file)
usable = None
language = None
if False and isNativeMixed:
if self.isReportableInterpreterFrame(functionName):
engine = frame.FindVariable("engine")
self.context = engine
h = self.extractQmlLocation(engine)
pc = 0
functionName = h['function']
fileName = h['file']
lineNumber = h['line']
addr = h['context']
language = 'js'
#elif not functionName is None:
# if functionName.startswith("qt_v4"):
# usable = 0
# elif functionName.find("QV4::") >= 0:
# usable = 0
result += '{pc="0x%x"' % pc
result += ',level="%d"' % level
result += ',address="0x%x"' % addr
if not usable is None:
result += ',usable="%s"' % usable
result += ',function="%s"' % functionName
result += ',line="%d"' % lineNumber
if not language is None:
result += ',language="%s"' % language
result += ',file="%s"},' % fileName
result += ']'
result += ',hasmore="%d"' % isLimited
@@ -1155,16 +1144,28 @@ class Dumper(DumperBase):
with SubItem(self, child):
self.putItem(child)
def reportVariables(self, args):
self.out = ""
self.reportVariablesHelper(args)
self.reportResult(self.out, args)
def reportVariablesHelper(self, args = {}):
frame = self.currentFrame()
if frame is None:
def fetchVariables(self, args):
(ok, res) = self.tryFetchInterpreterVariables(args)
if ok:
self.reportResult(res, args)
return
self.expandedINames = set(args.get('expanded', []))
self.autoDerefPointers = int(args.get('autoderef', '0'))
self.sortStructMembers = bool(args.get('sortStructMembers', True));
self.useDynamicType = int(args.get('dyntype', '0'))
self.useFancy = int(args.get('fancy', '0'))
self.passExceptions = int(args.get('passexceptions', '0'))
self.currentWatchers = args.get('watchers', {})
self.typeformats = args.get("typeformats", {})
self.formats = args.get("formats", {})
frame = self.currentFrame()
if frame is None:
self.reportResult('error="No frame"', args)
return
self.output = ''
partialVariable = args.get("partialVariable", "")
isPartial = len(partialVariable) > 0
@@ -1232,6 +1233,7 @@ class Dumper(DumperBase):
self.handleWatches(args)
self.put('],partial="%d"' % isPartial)
self.reportResult(self.output, args)
def fetchRegisters(self, args = None):
if self.process is None:
@@ -1281,7 +1283,7 @@ class Dumper(DumperBase):
else:
self.isInterrupting_ = True
error = self.process.Stop()
self.reportResult(describeError(error), args)
self.reportResult(self.describeError(error), args)
def detachInferior(self, args):
if self.process is None:
@@ -1330,28 +1332,31 @@ class Dumper(DumperBase):
frame = stoppedThread.GetFrameAtIndex(0)
#self.report("FRAME: %s" % frame)
function = frame.GetFunction()
#self.report("FUNCTION: %s" % function)
if function.GetName() == "qt_v4ResolvePendingBreakpointsHook":
#self.report("RESOLVER HIT")
for bp in self.qmlBreakpointResolvers:
self.qmlBreakpointResolvers[bp]()
self.target.BreakpointDelete(bp.GetID())
self.qmlBreakpointResolvers = {}
functionName = function.GetName()
if functionName == "::qt_qmlDebugConnectorOpen()":
self.report("RESOLVER HIT")
for resolver in self.interpreterBreakpointResolvers:
resolver()
self.report("AUTO-CONTINUE AFTER RESOLVING")
self.reportState("inferiorstopok")
self.process.Continue();
return
if functionName == "::qt_qmlDebugEventFromService()":
self.report("EVENT FROM SERVICE")
res = self.handleInterpreterEvent()
if not res:
self.report("EVENT NEEDS NO STOP")
self.reportState("stopped")
self.process.Continue();
return
if self.isInterrupting_:
self.isInterrupting_ = False
self.reportState("inferiorstopok")
self.reportState("stopped")
elif self.ignoreStops > 0:
self.ignoreStops -= 1
self.process.Continue()
elif self.silentStops > 0:
self.silentStops -= 1
#elif bp and bp in self.qmlBreakpointResolvers:
# self.report("RESOLVER HIT")
# self.qmlBreakpointResolvers[bp]()
# self.process.Continue();
else:
self.reportState("stopped")
else:
@@ -1645,19 +1650,6 @@ class Dumper(DumperBase):
error = str(result.GetError())
self.report('success="%d",output="%s",error="%s"' % (success, output, error))
def fetchLocals(self, args):
self.output = ''
self.expandedINames = set(args.get('expanded', []))
self.autoDerefPointers = int(args.get('autoderef', '0'))
self.sortStructMembers = bool(args.get("sortStructMembers", True));
self.useDynamicType = int(args.get('dyntype', '0'))
self.useFancy = int(args.get('fancy', '0'))
self.passExceptions = int(args.get('passexceptions', '0'))
self.currentWatchers = args.get('watchers', {})
self.typeformats = args.get("typeformats", {})
self.formats = args.get("formats", {})
self.reportVariables(args)
def fetchDisassembler(self, args):
functionName = args.get('function', '')
flavor = args.get('flavor', '')
@@ -1744,17 +1736,13 @@ class Dumper(DumperBase):
value = self.hexdecode(args['value'])
lhs = self.findValueByExpression(exp)
lhs.SetValueFromCString(value, error)
self.reportResult(describeError(error), args)
self.reportResult(self.describeError(error), args)
def createResolvePendingBreakpointsHookBreakpoint(self, args):
if self.qmlTriggeredBreakpoint is None:
self.qmlTriggeredBreakpoint = \
self.target.BreakpointCreateByName("qt_v4TriggeredBreakpointHook")
bp = self.target.BreakpointCreateByName("qt_v4ResolvePendingBreakpointsHook")
bp = self.target.BreakpointCreateByName("qt_qmlDebugConnectorOpen")
bp.SetOneShot(True)
self.qmlBreakpointResolvers[bp] = lambda: \
self.doInsertQmlBreakpoint(args)
self.interpreterBreakpointResolvers.append(
lambda: self.doInsertInterpreterBreakpoint(args, True))
# Used in dumper auto test.
@@ -1825,7 +1813,7 @@ class Tester(Dumper):
if line != 0:
self.report = savedReport
self.process.SetSelectedThread(stoppedThread)
self.reportVariables({'token':2})
self.fetchVariables({'token':2, 'fancy':1})
#self.describeLocation(frame)
self.report("@NS@%s@" % self.qtNamespace())
#self.report("ENV=%s" % os.environ.items())

View File

@@ -3272,54 +3272,14 @@ void GdbEngine::handleStackListFrames(const DebuggerResponse &response, bool isF
return;
}
QList<StackFrame> stackFrames;
GdbMi stack = response.data["stack"]; // C++
if (!stack.isValid() || stack.childCount() == 0) { // Mixed.
stack.fromStringMultiple(response.consoleStreamOutput);
stack = stack["frames"];
GdbMi frames = response.data["stack"]; // C++
if (!frames.isValid() || frames.childCount() == 0) { // Mixed.
frames.fromStringMultiple(response.consoleStreamOutput);
frames = frames["frames"];
}
if (!stack.isValid()) {
qDebug() << "FIXME: stack:" << stack.toString();
return;
}
int targetFrame = -1;
int n = stack.childCount();
for (int i = 0; i != n; ++i) {
stackFrames.append(StackFrame::parseFrame(stack.childAt(i), runParameters()));
const StackFrame &frame = stackFrames.back();
// Initialize top frame to the first valid frame.
const bool isValid = frame.isUsable() && !frame.function.isEmpty();
if (isValid && targetFrame == -1)
targetFrame = i;
}
bool canExpand = !isFull && (n >= action(MaximalStackDepth)->value().toInt());
action(ExpandStack)->setEnabled(canExpand);
stackHandler()->setFrames(stackFrames, canExpand);
// We can't jump to any file if we don't have any frames.
if (stackFrames.isEmpty())
return;
// targetFrame contains the top most frame for which we have source
// information. That's typically the frame we'd like to jump to, with
// a few exceptions:
// Always jump to frame #0 when stepping by instruction.
if (boolSetting(OperateByInstruction))
targetFrame = 0;
// If there is no frame with source, jump to frame #0.
if (targetFrame == -1)
targetFrame = 0;
stackHandler()->setCurrentIndex(targetFrame);
activateFrame(targetFrame);
stackHandler()->setFramesAndCurrentIndex(frames, isFull);
activateFrame(stackHandler()->currentIndex());
}
void GdbEngine::activateFrame(int frameIndex)

View File

@@ -335,6 +335,7 @@ void LldbEngine::setupInferior()
cmd2.arg("breakOnMain", rp.breakOnMain);
cmd2.arg("useTerminal", rp.useTerminal);
cmd2.arg("startMode", rp.startMode);
cmd2.arg("nativemixed", isNativeMixedActive());
QJsonArray processArgs;
foreach (const QString &arg, args.toUnixArgs())
@@ -757,8 +758,9 @@ void LldbEngine::fetchStack(int limit)
cmd.arg("context", stackHandler()->currentFrame().context);
cmd.callback = [this](const DebuggerResponse &response) {
const GdbMi &stack = response.data["stack"];
stackHandler()->setAllFrames(stack["frames"], stack["hasmore"].toInt());
updateLocals();
const bool isFull = !stack["hasmore"].toInt();
stackHandler()->setFramesAndCurrentIndex(stack["frames"], isFull);
activateFrame(stackHandler()->currentIndex());
};
runCommand(cmd);
}
@@ -782,15 +784,9 @@ void LldbEngine::assignValueInDebugger(WatchItem *,
void LldbEngine::doUpdateLocals(const UpdateParameters &params)
{
if (stackHandler()->stackSize() == 0) {
showMessage(_("SKIPPING LOCALS DUE TO EMPTY STACK"));
return;
}
watchHandler()->notifyUpdateStarted(params.partialVariables());
DebuggerCommand cmd("fetchLocals");
cmd.arg("nativemixed", isNativeMixedActive());
DebuggerCommand cmd("fetchVariables");
watchHandler()->appendFormatRequests(&cmd);
watchHandler()->appendWatchersAndTooltipRequests(&cmd);
@@ -802,6 +798,10 @@ void LldbEngine::doUpdateLocals(const UpdateParameters &params)
cmd.arg("partialVariable", params.partialVariable);
cmd.arg("sortStructMembers", boolSetting(SortStructMembers));
StackFrame frame = stackHandler()->currentFrame();
cmd.arg("context", frame.context);
cmd.arg("nativemixed", isNativeMixedActive());
//cmd.arg("resultvarname", m_resultVarName);
m_lastDebuggableCommand = cmd;
@@ -908,7 +908,6 @@ void LldbEngine::handleStateNotification(const GdbMi &reportedState)
}
} else if (newState == "inferiorstopok") {
notifyInferiorStopOk();
updateAll();
} else if (newState == "inferiorstopfailed")
notifyInferiorStopFailed();
else if (newState == "inferiorill")
@@ -941,16 +940,22 @@ void LldbEngine::handleStateNotification(const GdbMi &reportedState)
void LldbEngine::handleLocationNotification(const GdbMi &reportedLocation)
{
qulonglong addr = reportedLocation["addr"].toAddress();
QString file = reportedLocation["file"].toUtf8();
int line = reportedLocation["line"].toInt();
Location loc = Location(file, line);
if (boolSetting(OperateByInstruction) || !QFileInfo::exists(file) || line <= 0) {
loc = Location(addr);
qulonglong address = reportedLocation["address"].toAddress();
QString fileName = reportedLocation["file"].toUtf8();
QByteArray function = reportedLocation["function"].data();
int lineNumber = reportedLocation["line"].toInt();
Location loc = Location(fileName, lineNumber);
if (boolSetting(OperateByInstruction) || !QFileInfo::exists(fileName) || lineNumber <= 0) {
loc = Location(address);
loc.setNeedsMarker(true);
loc.setUseAssembler(true);
}
gotoLocation(loc);
// Quickly set the location marker.
if (lineNumber > 0
&& QFileInfo::exists(fileName)
&& function != "::qt_qmlDebugEventFromService()")
gotoLocation(Location(fileName, lineNumber));
}
void LldbEngine::reloadRegisters()

View File

@@ -33,6 +33,7 @@
#include "debuggeractions.h"
#include "debuggercore.h"
#include "debuggerengine.h"
#include "debuggerprotocol.h"
#include "simplifytype.h"
#include <utils/fileutils.h>
@@ -168,12 +169,6 @@ StackFrame StackHandler::currentFrame() const
return m_stackFrames.at(m_currentIndex);
}
void StackHandler::setAllFrames(const GdbMi &frames, bool canExpand)
{
action(ExpandStack)->setEnabled(canExpand);
setFrames(StackFrame::parseFrames(frames, m_engine->runParameters()), canExpand);
}
void StackHandler::setCurrentIndex(int level)
{
if (level == -1 || level == m_currentIndex)
@@ -214,6 +209,45 @@ void StackHandler::setFrames(const StackFrames &frames, bool canExpand)
emit stackChanged();
}
void StackHandler::setFramesAndCurrentIndex(const GdbMi &frames, bool isFull)
{
int targetFrame = -1;
StackFrames stackFrames;
const int n = frames.childCount();
for (int i = 0; i != n; ++i) {
stackFrames.append(StackFrame::parseFrame(frames.childAt(i), m_engine->runParameters()));
const StackFrame &frame = stackFrames.back();
// Initialize top frame to the first valid frame.
const bool isValid = frame.isUsable() && !frame.function.isEmpty();
if (isValid && targetFrame == -1)
targetFrame = i;
}
bool canExpand = !isFull && (n >= action(MaximalStackDepth)->value().toInt());
action(ExpandStack)->setEnabled(canExpand);
setFrames(stackFrames, canExpand);
// We can't jump to any file if we don't have any frames.
if (stackFrames.isEmpty())
return;
// targetFrame contains the top most frame for which we have source
// information. That's typically the frame we'd like to jump to, with
// a few exceptions:
// Always jump to frame #0 when stepping by instruction.
if (boolSetting(OperateByInstruction))
targetFrame = 0;
// If there is no frame with source, jump to frame #0.
if (targetFrame == -1)
targetFrame = 0;
setCurrentIndex(targetFrame);
}
void StackHandler::prependFrames(const StackFrames &frames)
{
if (frames.isEmpty())

View File

@@ -59,6 +59,8 @@ public:
~StackHandler();
void setFrames(const StackFrames &frames, bool canExpand = false);
void setFramesAndCurrentIndex(const GdbMi &frames, bool isFull);
int updateTargetFrame(bool isFull);
void prependFrames(const StackFrames &frames);
const StackFrames &frames() const;
void setCurrentIndex(int index);
@@ -68,7 +70,6 @@ public:
const StackFrame &frameAt(int index) const { return m_stackFrames.at(index); }
int stackSize() const { return m_stackFrames.size(); }
quint64 topAddress() const { return m_stackFrames.at(0).address; }
void setAllFrames(const GdbMi &frames, bool canExpand);
// Called from StackHandler after a new stack list has been received
void removeAll();