Debugger: Provider Qt version externally to bridges

Extracting within the bridges is expensive.

Change-Id: Icf69db4b112230cc23e331abc0b3eb0de1323f46
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
hjk
2024-04-26 13:48:06 +02:00
parent bee7cdfd1e
commit 6aab6f61b5
9 changed files with 88 additions and 181 deletions

View File

@@ -172,9 +172,11 @@ class DumperBase():
self.qtCustomEventFunc = 0
self.qtCustomEventPltFunc = 0
self.qtPropertyFunc = 0
self.fallbackQtVersion = 0x60200
self.qtversion = None
self.qtns = None
self.passExceptions = False
self.isTesting = False
self.qtLoaded = False
self.isBigEndian = False
self.packCode = '<'
@@ -231,6 +233,8 @@ class DumperBase():
self.useTimeStamps = int(args.get('timestamps', '0'))
self.partialVariable = args.get('partialvar', '')
self.uninitialized = args.get('uninitialized', [])
self.qtversion = args.get('qtversion', 0x060602)
self.qtnamespace = args.get('qtnamespace', '')
self.uninitialized = list(map(lambda x: self.hexdecode(x), self.uninitialized))
#self.warn('NAMESPACE: "%s"' % self.qtNamespace())
#self.warn('EXPANDED INAMES: %s' % self.expandedINames)
@@ -252,9 +256,11 @@ class DumperBase():
args['partialvar'] = ''
self.fetchVariables(args)
def setFallbackQtVersion(self, args):
version = int(args.get('version', self.fallbackQtVersion))
self.fallbackQtVersion = version
def qtVersion(self):
return self.qtversion
def qtNamespace(self):
return self.qtnamespace
def resetPerStepCaches(self):
self.perStepCache = {}
@@ -1566,7 +1572,16 @@ class DumperBase():
if address is not None:
self.put('origaddr="0x%x",' % address)
def wantQObjectNames(self):
return self.showQObjectNames and self.qtLoaded
def fetchInternalFunctions(self):
# Overrridden
pass
def putQObjectNameValue(self, value):
self.fetchInternalFunctions()
try:
# dd = value['d_ptr']['d'] is just behind the vtable.
(vtable, dd) = self.split('pp', value)
@@ -3022,7 +3037,7 @@ typename))
self.putExpandable()
self.putEmptyValue()
#self.warn('STRUCT GUTS: %s ADDRESS: 0x%x ' % (value.name, value.address()))
if self.showQObjectNames:
if self.wantQObjectNames():
#with self.timer(self.currentIName):
self.putQObjectNameValue(value)
if self.isExpanded():
@@ -3030,7 +3045,7 @@ typename))
self.putField('sortable', 1)
with Children(self, 1, childType=None):
self.putFields(value)
if self.showQObjectNames:
if self.wantQObjectNames():
self.tryPutQObjectGuts(value)
def symbolAddress(self, symbolName):

View File

@@ -852,37 +852,6 @@ class Dumper(DumperBase):
except:
return '0x%x' % address
def qtVersionString(self):
try:
return str(gdb.lookup_symbol('qVersion')[0].value()())
except:
pass
try:
ns = self.qtNamespace()
return str(gdb.parse_and_eval("((const char*(*)())'%sqVersion')()" % ns))
except:
pass
return None
def qtVersion(self):
try:
# Only available with Qt 5.3+
qtversion = int(str(gdb.parse_and_eval('((void**)&qtHookData)[2]')), 16)
self.qtVersion = lambda: qtversion
return qtversion
except:
pass
try:
version = self.qtVersionString()
(major, minor, patch) = version[version.find('"') + 1:version.rfind('"')].split('.')
qtversion = 0x10000 * int(major) + 0x100 * int(minor) + int(patch)
self.qtVersion = lambda: qtversion
return qtversion
except:
# Use fallback until we have a better answer.
return self.fallbackQtVersion
def createSpecialBreakpoints(self, args):
self.specialBreakpoints = []
@@ -1008,10 +977,6 @@ class Dumper(DumperBase):
for obj in gdb.objfiles():
self.importPlainDumpersForObj(obj)
def qtNamespace(self):
# This function is replaced by handleQtCoreLoaded()
return ''
def findSymbol(self, symbolName):
try:
return int(gdb.parse_and_eval("(size_t)&'%s'" % symbolName))
@@ -1044,41 +1009,38 @@ class Dumper(DumperBase):
pass
def handleQtCoreLoaded(self, objfile):
fd, tmppath = tempfile.mkstemp()
os.close(fd)
cmd = 'maint print msymbols -objfile "%s" -- %s' % (objfile.filename, tmppath)
symbols = gdb.execute(cmd, to_string=True)
ns = ''
with open(tmppath) as f:
ns1re = re.compile(r'_ZN?(\d*)(\w*)L17msgHandlerGrabbedE? ')
ns2re = re.compile(r'_ZN?(\d*)(\w*)L17currentThreadDataE? ')
for line in f:
if 'msgHandlerGrabbed ' in line:
# [11] b 0x7ffff683c000 _ZN4MynsL17msgHandlerGrabbedE
# section .tbss Myns::msgHandlerGrabbed qlogging.cpp
ns = ns1re.split(line)[2]
if len(ns):
ns += '::'
break
if 'currentThreadData ' in line:
# [ 0] b 0x7ffff67d3000 _ZN2UUL17currentThreadDataE
# section .tbss UU::currentThreadData qthread_unix.cpp\\n
ns = ns2re.split(line)[2]
if len(ns):
ns += '::'
break
os.remove(tmppath)
self.qtLoaded = True
# FIXME: Namespace auto-detection. Is it worth the price?
# fd, tmppath = tempfile.mkstemp()
# os.close(fd)
# cmd = 'maint print msymbols -objfile "%s" -- %s' % (objfile.filename, tmppath)
# symbols = gdb.execute(cmd, to_string=True)
# ns = ''
# with open(tmppath) as f:
# ns1re = re.compile(r'_ZN?(\d*)(\w*)L17msgHandlerGrabbedE? ')
# ns2re = re.compile(r'_ZN?(\d*)(\w*)L17currentThreadDataE? ')
# for line in f:
# if 'msgHandlerGrabbed ' in line:
# # [11] b 0x7ffff683c000 _ZN4MynsL17msgHandlerGrabbedE
# # section .tbss Myns::msgHandlerGrabbed qlogging.cpp
# ns = ns1re.split(line)[2]
# if len(ns):
# ns += '::'
# break
# if 'currentThreadData ' in line:
# # [ 0] b 0x7ffff67d3000 _ZN2UUL17currentThreadDataE
# # section .tbss UU::currentThreadData qthread_unix.cpp\\n
# ns = ns2re.split(line)[2]
# if len(ns):
# ns += '::'
# break
# os.remove(tmppath)
def fetchInternalFunctions(self):
ns = self.qtNamespace()
lenns = len(ns)
strns = ('%d%s' % (lenns - 2, ns[:lenns - 2])) if lenns else ''
if lenns:
# This might be wrong, but we can't do better: We found
# a libQt5Core and could not extract a namespace.
# The best guess is that there isn't any.
self.qtNamespaceToReport = ns
self.qtNamespace = lambda: ns
sym = '_ZN%s7QObject11customEventEPNS_6QEventE' % strns
else:
sym = '_ZN7QObject11customEventEP6QEvent'
@@ -1091,6 +1053,8 @@ class Dumper(DumperBase):
if not self.isWindowsTarget(): # prevent calling the property function on windows
self.qtPropertyFunc = self.findSymbol(sym)
self.fetchInternalFunctions = lambda: None
def assignValue(self, args):
type_name = self.hexdecode(args['type'])
expr = self.hexdecode(args['expr'])

View File

@@ -768,80 +768,7 @@ class Dumper(DumperBase):
symbol = funcs[0].GetSymbol()
self.qtPropertyFunc = symbol.GetStartAddress().GetLoadAddress(self.target)
def fetchQtVersionAndNamespace(self):
for func in self.target.FindFunctions('qVersion'):
name = func.GetSymbol().GetName()
if name == None:
continue
if name.endswith('()'):
name = name[:-2]
if name.count(':') > 2:
continue
qtNamespace = name[:name.find('qVersion')]
self.qtNamespace = lambda: qtNamespace
options = lldb.SBExpressionOptions()
res = self.target.EvaluateExpression(name + '()', options)
if not res.IsValid() or not res.GetType().IsPointerType():
exp = '((const char*())%s)()' % name
res = self.target.EvaluateExpression(exp, options)
if not res.IsValid() or not res.GetType().IsPointerType():
exp = '((const char*())_Z8qVersionv)()'
res = self.target.EvaluateExpression(exp, options)
if not res.IsValid() or not res.GetType().IsPointerType():
continue
version = str(res)
if version.count('.') != 2:
continue
version.replace("'", '"') # Both seem possible
version = version[version.find('"') + 1:version.rfind('"')]
(major, minor, patch) = version.split('.')
qtVersion = 0x10000 * int(major) + 0x100 * int(minor) + int(patch)
self.qtVersion = lambda: qtVersion
return (qtNamespace, qtVersion)
try:
versionValue = self.target.EvaluateExpression('qtHookData[2]').GetNonSyntheticValue()
if versionValue.IsValid():
return ('', versionValue.unsigned)
except:
pass
return ('', self.fallbackQtVersion)
def qtVersionAndNamespace(self):
qtVersionAndNamespace = None
try:
qtVersionAndNamespace = self.fetchQtVersionAndNamespace()
self.report("Detected Qt Version: 0x%0x (namespace='%s')" %
(qtVersionAndNamespace[1], qtVersionAndNamespace[0] or "no namespace"))
except Exception as e:
DumperBase.warn('[lldb] Error detecting Qt version: %s' % e)
try:
self.fetchInternalFunctions()
self.report('Found function QObject::property: 0x%0x' % self.qtPropertyFunc)
self.report('Found function QObject::customEvent: 0x%0x' % self.qtCustomEventFunc)
except Exception as e:
DumperBase.warn('[lldb] Error fetching internal Qt functions: %s' % e)
# Cache version information by overriding this function.
self.qtVersionAndNamespace = lambda: qtVersionAndNamespace
return qtVersionAndNamespace
def qtNamespace(self):
return self.qtVersionAndNamespace()[0]
def qtVersion(self):
return self.qtVersionAndNamespace()[1]
self.fetchInternalFunctions = lambda: None
def handleCommand(self, command):
result = lldb.SBCommandReturnObject()
@@ -1360,6 +1287,9 @@ class Dumper(DumperBase):
self.setVariableFetchingOptions(args)
self.qtLoaded = True # FIXME: Do that elsewhere
# Reset certain caches whenever a step over / into / continue
# happens.
# FIXME: Caches are currently also cleared if currently

View File

@@ -1042,6 +1042,8 @@ void CdbEngine::doUpdateLocals(const UpdateParameters &updateParameters)
cmd.arg("partialvar", updateParameters.partialVariable);
cmd.arg("qobjectnames", s.showQObjectNames());
cmd.arg("timestamps", s.logTimeStamps());
cmd.arg("qtversion", runParameters().qtVersion);
cmd.arg("qtnamespace", runParameters().qtNamespace);
StackFrame frame = stackHandler()->currentFrame();
cmd.arg("context", frame.context);
@@ -2809,10 +2811,6 @@ void CdbEngine::setupScripting(const DebuggerResponse &response)
runCommand({command, ScriptCommand});
}
DebuggerCommand cmd0("theDumper.setFallbackQtVersion", ScriptCommand);
cmd0.arg("version", runParameters().fallbackQtVersion);
runCommand(cmd0);
runCommand({"theDumper.loadDumpers(None)", ScriptCommand,
[this](const DebuggerResponse &response) {
watchHandler()->addDumpers(response.data["result"]["dumpers"]);

View File

@@ -188,7 +188,8 @@ public:
QStringList validationErrors;
int fallbackQtVersion = 0x50200;
int qtVersion = 0;
QString qtNamespace;
// Common debugger constants.
Utils::FilePath peripheralDescriptionFile;

View File

@@ -898,7 +898,7 @@ DebuggerRunTool::DebuggerRunTool(RunControl *runControl, AllowTerminal allowTerm
if (QtSupport::QtVersion *baseQtVersion = QtSupport::QtKitAspect::qtVersion(kit)) {
const QVersionNumber qtVersion = baseQtVersion->qtVersion();
m_runParameters.fallbackQtVersion = 0x10000 * qtVersion.majorVersion()
m_runParameters.qtVersion = 0x10000 * qtVersion.majorVersion()
+ 0x100 * qtVersion.minorVersion()
+ qtVersion.microVersion();
}

View File

@@ -4054,10 +4054,6 @@ void GdbEngine::handleGdbStarted()
if (!commands.isEmpty())
runCommand({commands});
DebuggerCommand cmd1("setFallbackQtVersion");
cmd1.arg("version", rp.fallbackQtVersion);
runCommand(cmd1);
runCommand({"loadDumpers", CB(handlePythonSetup)});
// Reload peripheral register description.
@@ -5133,6 +5129,8 @@ void GdbEngine::doUpdateLocals(const UpdateParameters &params)
cmd.arg("dyntype", s.useDynamicType());
cmd.arg("qobjectnames", s.showQObjectNames());
cmd.arg("timestamps", s.logTimeStamps());
cmd.arg("qtversion", runParameters().qtVersion);
cmd.arg("qtnamespace", runParameters().qtNamespace);
StackFrame frame = stackHandler()->currentFrame();
cmd.arg("context", frame.context);

View File

@@ -346,10 +346,6 @@ void LldbEngine::handleLldbStarted()
cmd2.flags = Silent;
runCommand(cmd2);
DebuggerCommand cmd0("setFallbackQtVersion");
cmd0.arg("version", rp.fallbackQtVersion);
runCommand(cmd0);
}
void LldbEngine::runEngine()
@@ -767,6 +763,8 @@ void LldbEngine::doUpdateLocals(const UpdateParameters &params)
cmd.arg("partialvar", params.partialVariable);
cmd.arg("qobjectnames", s.showQObjectNames());
cmd.arg("timestamps", s.logTimeStamps());
cmd.arg("qtversion", runParameters().qtVersion);
cmd.arg("qtnamespace", runParameters().qtNamespace);
StackFrame frame = stackHandler()->currentFrame();
cmd.arg("context", frame.context);

View File

@@ -1200,7 +1200,28 @@ void tst_Dumpers::initTestCase()
if (qEnvironmentVariableIntValue("QTC_USE_CMAKE_FOR_TEST"))
m_buildSystem = BuildSystem::CMake;
QProcess qmake;
qmake.start(m_qmakeBinary, {"--version"});
QVERIFY(qmake.waitForFinished());
QByteArray output = qmake.readAllStandardOutput();
QByteArray error = qmake.readAllStandardError();
int pos0 = output.indexOf("Qt version");
if (pos0 == -1) {
qCDebug(lcDumpers).noquote() << "Output: " << output;
qCDebug(lcDumpers).noquote() << "Error: " << error;
QVERIFY(false);
}
pos0 += 11;
int pos1 = output.indexOf('.', pos0 + 1);
int major = output.mid(pos0, pos1++ - pos0).toInt();
int pos2 = output.indexOf('.', pos1 + 1);
int minor = output.mid(pos1, pos2++ - pos1).toInt();
int pos3 = output.indexOf(' ', pos2 + 1);
int patch = output.mid(pos2, pos3++ - pos2).toInt();
m_qtVersion = 0x10000 * major + 0x100 * minor + patch;
qCDebug(lcDumpers) << "QMake : " << m_qmakeBinary;
qCDebug(lcDumpers) << "Qt Version : " << m_qtVersion;
qCDebug(lcDumpers) << "Use CMake : " << (m_buildSystem == BuildSystem::CMake) << int(m_buildSystem);
m_useGLibCxxDebug = qgetenv("QTC_USE_GLIBCXXDEBUG_FOR_TEST").toInt();
@@ -1381,27 +1402,6 @@ void tst_Dumpers::dumper()
QByteArray error;
if (data.neededQtVersion.isRestricted) {
QProcess qmake;
qmake.setWorkingDirectory(t->buildPath);
qmake.start(m_qmakeBinary, {"--version"});
QVERIFY(qmake.waitForFinished());
output = qmake.readAllStandardOutput();
error = qmake.readAllStandardError();
int pos0 = output.indexOf("Qt version");
if (pos0 == -1) {
qCDebug(lcDumpers).noquote() << "Output: " << output;
qCDebug(lcDumpers).noquote() << "Error: " << error;
QVERIFY(false);
}
pos0 += 11;
int pos1 = output.indexOf('.', pos0 + 1);
int major = output.mid(pos0, pos1++ - pos0).toInt();
int pos2 = output.indexOf('.', pos1 + 1);
int minor = output.mid(pos1, pos2++ - pos1).toInt();
int pos3 = output.indexOf(' ', pos2 + 1);
int patch = output.mid(pos2, pos3++ - pos2).toInt();
m_qtVersion = 0x10000 * major + 0x100 * minor + patch;
if (data.neededQtVersion.min > m_qtVersion)
MSKIP_SINGLE(QByteArray("Need minimum Qt version "
+ QByteArray::number(data.neededQtVersion.min, 16)));
@@ -1793,6 +1793,7 @@ void tst_Dumpers::dumper()
"python theDumper.fetchVariables({" + dumperOptions +
"'token':2,'fancy':1,'forcens':1,"
"'autoderef':1,'dyntype':1,'passexceptions':1,"
"'qtversion':" + QString::number(m_qtVersion) + ",'qtnamespace':'',"
"'testing':1,'qobjectnames':1,"
"'expanded':{" + expandedq + "}})\n";
@@ -1816,6 +1817,7 @@ void tst_Dumpers::dumper()
"'token':2,'fancy':1,'forcens':1,"
"'autoderef':1,'dyntype':1,'passexceptions':0,"
"'testing':1,'qobjectnames':1,"
"'qtversion':" + QString::number(m_qtVersion) + ",'qtnamespace':'',"
"'expanded':{" + expandedq + "}})\n"
"q\n";
} else if (m_debuggerEngine == LldbEngine) {
@@ -1840,6 +1842,7 @@ void tst_Dumpers::dumper()
"'fancy':1,'forcens':1,"
"'autoderef':1,'dyntype':1,'passexceptions':1,"
"'testing':1,'qobjectnames':1,"
"'qtversion':" + QString::number(m_qtVersion) + ",'qtnamespace':'',"
"'expanded':{" + expandedq + "}})\n"
"quit\n";