From d4a32fd6b39e214930e0675f293f881f746a59d6 Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 4 Feb 2015 13:29:42 +0100 Subject: [PATCH] Debugger: Implement native mixed breakpoints with LLDB Breakpoints are hit, stack frames are identified as JS or native. No further data yet. Change-Id: I84a02422fd36dc7645003114dd8519bedd913c06 Reviewed-by: hjk --- share/qtcreator/debugger/dumper.py | 16 ++- share/qtcreator/debugger/gdbbridge.py | 8 -- share/qtcreator/debugger/lldbbridge.py | 121 ++++++++++++++++------- src/plugins/debugger/lldb/lldbengine.cpp | 14 ++- 4 files changed, 109 insertions(+), 50 deletions(-) diff --git a/share/qtcreator/debugger/dumper.py b/share/qtcreator/debugger/dumper.py index e01f8ecca5c..705e856ba92 100644 --- a/share/qtcreator/debugger/dumper.py +++ b/share/qtcreator/debugger/dumper.py @@ -1718,10 +1718,14 @@ class DumperBase: expr = 'qt_v4DebuggerHook("{%s}")' % data try: res = self.parseAndEvaluate(expr) - #print("QML command ok, RES: %s, CMD: %s" % (res, expr)) + print("QML command ok, RES: %s, CMD: %s" % (res, expr)) except RuntimeError as error: #print("QML command failed: %s: %s" % (expr, error)) res = None + except AttributeError as error: + # Happens with LLDB and 'None' current thread. + #print("QML command failed: %s: %s" % (expr, error)) + res = None return res def prepareQmlStep(self, _): @@ -1765,6 +1769,16 @@ class DumperBase: print("Resolving QML breakpoint: %s" % bp) return int(bp) + def isInternalQmlFrame(self, functionName): + if functionName is None: + return False + if functionName.startswith("qt_v4"): + return True + return functionName.startswith(self.qtNamespace() + "QV4::") + + def isReportableQmlFrame(self, functionName): + return functionName and functionName.find("QV4::Moth::VME::exec") >= 0 + # Some "Enums" diff --git a/share/qtcreator/debugger/gdbbridge.py b/share/qtcreator/debugger/gdbbridge.py index ce3e7d2179f..2d2240d05ef 100644 --- a/share/qtcreator/debugger/gdbbridge.py +++ b/share/qtcreator/debugger/gdbbridge.py @@ -1752,14 +1752,6 @@ class Dumper(DumperBase): 'fullname="%s",line="%s",language="js",addr="0x%x"}') % (level, functionName, fileName, fileName, lineNumber, context)) - def isInternalQmlFrame(self, functionName): - if functionName.startswith("qt_v4"): - return True - return functionName.startswith(self.qtNamespace() + "QV4::") - - def isReportableQmlFrame(self, functionName): - return functionName.find("QV4::Moth::VME::exec") >= 0 - def stackListFrames(self, n, options): self.prepare("options:" + options + ",pe") self.output = [] diff --git a/share/qtcreator/debugger/lldbbridge.py b/share/qtcreator/debugger/lldbbridge.py index a1a8f569340..ab825831968 100644 --- a/share/qtcreator/debugger/lldbbridge.py +++ b/share/qtcreator/debugger/lldbbridge.py @@ -256,6 +256,9 @@ class Dumper(DumperBase): self.isInterrupting_ = False self.dummyValue = None self.breakpointsToCheck = set([]) + self.qmlBreakpointResolvers = {} + self.qmlTriggeredBreakpoint = None + self.nativeMixed = False def enterSubItem(self, item): if isinstance(item.name, lldb.SBValue): @@ -809,15 +812,41 @@ class Dumper(DumperBase): if not frame.IsValid(): isLimited = False break + lineEntry = frame.GetLineEntry() - line = lineEntry.GetLine() - result += '{pc="0x%x"' % frame.GetPC() - result += ',level="%d"' % frame.idx - result += ',addr="0x%x"' % frame.GetPCAddress().GetLoadAddress(self.target) - result += ',func="%s"' % frame.GetFunctionName() - result += ',line="%d"' % line - result += ',fullname="%s"' % fileName(lineEntry.file) - result += ',file="%s"},' % fileName(lineEntry.file) + lineNumber = lineEntry.GetLine() + + pc = frame.GetPC() + level = frame.idx + addr = frame.GetPCAddress().GetLoadAddress(self.target) + functionName = frame.GetFunctionName() + fullname = fileName(lineEntry.file) + usable = None + language = None + + if self.nativeMixed: + if self.isReportableQmlFrame(functionName): + #self.putQmlLocation(i, frame, sal) + functionName = "### JS ###"; + 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 += ',addr="0x%x"' % addr + if not usable is None: + result += ',usable="%s"' % usable + result += ',func="%s"' % functionName + result += ',line="%d"' % lineNumber + if not language is None: + result += ',language="%s"' % language + result += ',fullname="%s"' % fullname + result += ',file="%s"},' % fullname result += ']' result += ',hasmore="%d"' % isLimited result += ',limit="%d"' % limit @@ -1216,8 +1245,9 @@ class Dumper(DumperBase): msg = lldb.SBEvent.GetCStringFromEvent(event) flavor = event.GetDataFlavor() state = lldb.SBProcess.GetStateFromEvent(event) - self.report('event={type="%s",data="%s",msg="%s",flavor="%s",state="%s"}' - % (eventType, out.GetData(), msg, flavor, state)) + bp = lldb.SBBreakpoint.GetBreakpointFromEvent(event) + self.report('event={type="%s",data="%s",msg="%s",flavor="%s",state="%s",bp="%s"}' + % (eventType, out.GetData(), msg, flavor, state, bp)) if state != self.eventState: self.eventState = state if state == lldb.eStateExited: @@ -1228,12 +1258,32 @@ class Dumper(DumperBase): self.report('exited={status="%s",desc="%s"}' % (self.process.GetExitStatus(), self.process.GetExitDescription())) elif state == lldb.eStateStopped: + stoppedThread = self.firstStoppedThread() + if stoppedThread: + #self.report("STOPPED THREAD: %s" % stoppedThread) + 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 = {} + self.process.Continue(); + return + if self.isInterrupting_: self.isInterrupting_ = False self.reportState("inferiorstopok") elif self.ignoreStops > 0: self.ignoreStops -= 1 self.process.Continue() + #elif bp and bp in self.qmlBreakpointResolvers: + # self.report("RESOLVER HIT") + # self.qmlBreakpointResolvers[bp]() + # self.process.Continue(); else: self.reportState("stopped") else: @@ -1311,20 +1361,24 @@ class Dumper(DumperBase): "main", self.target.GetExecutable().GetFilename()) def insertBreakpoint(self, args): - more = True - modelId = args['modelid'] bpType = args["type"] + if bpType == BreakpointByFileAndLine: + fileName = args["fileName"] + if fileName.endswith(".js") or fileName.endswith(".qml"): + self.insertQmlBreakpoint(args) + return + + more = True + modelId = args['modelid'] if bpType == BreakpointByFileAndLine: bp = self.target.BreakpointCreateByLocation( - str(args["file"]), int(args["line"])) + str(args["fileName"]), int(args["lineNumber"])) elif bpType == BreakpointByFunction: bp = self.target.BreakpointCreateByName(args["function"]) elif bpType == BreakpointByAddress: bp = self.target.BreakpointCreateByAddress(args["address"]) elif bpType == BreakpointAtMain: bp = self.createBreakpointAtMain() - elif bpType == BreakpointByFunction: - bp = self.target.BreakpointCreateByName(args["function"]) elif bpType == BreakpointAtThrow: bp = self.target.BreakpointCreateForException( lldb.eLanguageTypeC_plus_plus, False, True) @@ -1621,27 +1675,22 @@ class Dumper(DumperBase): self.reportError(error) self.reportVariables() -def convertHash(args): - if sys.version_info[0] == 3: - return args - if isinstance(args, str): - return args - if isinstance(args, unicode): - return args.encode('utf8') - cargs = {} - for arg in args: - rhs = args[arg] - if type(rhs) == type([]): - rhs = [convertHash(i) for i in rhs] - elif type(rhs) == type({}): - rhs = convertHash(rhs) - else: - try: - rhs = rhs.encode('utf8') - except: - pass - cargs[arg.encode('utf8')] = rhs - return cargs + def createResolvePendingBreakpointsHookBreakpoint(self, fullName, lineNumber): + self.nativeMixed = True + if self.qmlTriggeredBreakpoint is None: + self.qmlTriggeredBreakpoint = \ + self.target.BreakpointCreateByName("qt_v4TriggeredBreakpointHook") + + bp = self.target.BreakpointCreateByName("qt_v4ResolvePendingBreakpointsHook") + bp.SetOneShot(True) + self.qmlBreakpointResolvers[bp] = lambda: \ + self.resolvePendingQmlBreakpoint(fullName, lineNumber) + + def resolvePendingQmlBreakpoint(self, fullName, lineNumber): + bp = self.doInsertQmlBreakPoint(fullName, lineNumber) + print("Resolving QML breakpoint %s:%s -> %s" % (fullName, lineNumber, bp)) + + #ns = self.qtNamespace() # Used in dumper auto test. diff --git a/src/plugins/debugger/lldb/lldbengine.cpp b/src/plugins/debugger/lldb/lldbengine.cpp index 8d3b65b740e..f6fb545ce84 100644 --- a/src/plugins/debugger/lldb/lldbengine.cpp +++ b/src/plugins/debugger/lldb/lldbengine.cpp @@ -574,8 +574,8 @@ void LldbEngine::insertBreakpointHelper(DebuggerCommand *cmd, Breakpoint bp) con cmd->arg("function", bp.functionName().toUtf8()); cmd->arg("oneshot", bp.isOneShot()); cmd->arg("enabled", bp.isEnabled()); - cmd->arg("file", bp.fileName().toUtf8()); - cmd->arg("line", bp.lineNumber()); + cmd->arg("fileName", bp.fileName().toUtf8()); + cmd->arg("lineNumber", bp.lineNumber()); cmd->arg("address", bp.address()); cmd->arg("expression", bp.expression()); bp.notifyBreakpointInsertProceeding(); @@ -593,8 +593,8 @@ void LldbEngine::changeBreakpoint(Breakpoint bp) cmd.arg("function", bp.functionName().toUtf8()); cmd.arg("oneshot", bp.isOneShot()); cmd.arg("enabled", bp.isEnabled()); - cmd.arg("file", bp.fileName().toUtf8()); - cmd.arg("line", bp.lineNumber()); + cmd.arg("fileName", bp.fileName().toUtf8()); + cmd.arg("lineNumber", bp.lineNumber()); cmd.arg("address", bp.address()); cmd.arg("expression", bp.expression()); bp.notifyBreakpointChangeProceeding(); @@ -1020,7 +1020,11 @@ void LldbEngine::refreshStack(const GdbMi &stack) frame.from = item["func"].toUtf8(); frame.line = item["line"].toInt(); frame.address = item["addr"].toAddress(); - frame.usable = QFileInfo(frame.file).isReadable(); + GdbMi usable = item["usable"]; + if (usable.isValid()) + frame.usable = usable.data().toInt(); + else + frame.usable = QFileInfo(frame.file).isReadable(); frames.append(frame); } bool canExpand = stack["hasmore"].toInt();