forked from qt-creator/qt-creator
Debugger: Display QStrings with LLDB
Change-Id: I238f0203b618adb117f336dfc9f1465606f26ced Reviewed-by: hjk <hjk121@nokiamail.com>
This commit is contained in:
@@ -20,14 +20,110 @@ cdbLoaded = False
|
||||
lldbLoaded = False
|
||||
gdbLoaded = False
|
||||
|
||||
# Encodings. Keep that synchronized with DebuggerEncoding in watchutils.h
|
||||
Unencoded8Bit, \
|
||||
Base64Encoded8BitWithQuotes, \
|
||||
Base64Encoded16BitWithQuotes, \
|
||||
Base64Encoded32BitWithQuotes, \
|
||||
Base64Encoded16Bit, \
|
||||
Base64Encoded8Bit, \
|
||||
Hex2EncodedLatin1, \
|
||||
Hex4EncodedLittleEndian, \
|
||||
Hex8EncodedLittleEndian, \
|
||||
Hex2EncodedUtf8, \
|
||||
Hex8EncodedBigEndian, \
|
||||
Hex4EncodedBigEndian, \
|
||||
Hex4EncodedLittleEndianWithoutQuotes, \
|
||||
Hex2EncodedLocal8Bit, \
|
||||
JulianDate, \
|
||||
MillisecondsSinceMidnight, \
|
||||
JulianDateAndMillisecondsSinceMidnight, \
|
||||
Hex2EncodedInt1, \
|
||||
Hex2EncodedInt2, \
|
||||
Hex2EncodedInt4, \
|
||||
Hex2EncodedInt8, \
|
||||
Hex2EncodedUInt1, \
|
||||
Hex2EncodedUInt2, \
|
||||
Hex2EncodedUInt4, \
|
||||
Hex2EncodedUInt8, \
|
||||
Hex2EncodedFloat4, \
|
||||
Hex2EncodedFloat8 \
|
||||
= range(27)
|
||||
|
||||
# Display modes. Keep that synchronized with DebuggerDisplay in watchutils.h
|
||||
StopDisplay, \
|
||||
DisplayImageData, \
|
||||
DisplayUtf16String, \
|
||||
DisplayImageFile, \
|
||||
DisplayProcess, \
|
||||
DisplayLatin1String, \
|
||||
DisplayUtf8String \
|
||||
= range(7)
|
||||
|
||||
#######################################################################
|
||||
#
|
||||
# Helpers
|
||||
#
|
||||
#######################################################################
|
||||
|
||||
currentDir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||
#print "DIR: %s " % currentDir
|
||||
# This is a cache mapping from 'type name' to 'display alternatives'.
|
||||
qqFormats = {}
|
||||
|
||||
# This is a cache of all known dumpers.
|
||||
qqDumpers = {}
|
||||
|
||||
# This is a cache of all dumpers that support writing.
|
||||
qqEditable = {}
|
||||
|
||||
# This keeps canonical forms of the typenames, without array indices etc.
|
||||
qqStripForFormat = {}
|
||||
|
||||
def stripForFormat(typeName):
|
||||
global qqStripForFormat
|
||||
if typeName in qqStripForFormat:
|
||||
return qqStripForFormat[typeName]
|
||||
stripped = ""
|
||||
inArray = 0
|
||||
for c in stripClassTag(typeName):
|
||||
if c == '<':
|
||||
break
|
||||
if c == ' ':
|
||||
continue
|
||||
if c == '[':
|
||||
inArray += 1
|
||||
elif c == ']':
|
||||
inArray -= 1
|
||||
if inArray and ord(c) >= 48 and ord(c) <= 57:
|
||||
continue
|
||||
stripped += c
|
||||
qqStripForFormat[typeName] = stripped
|
||||
return stripped
|
||||
|
||||
|
||||
def registerDumper(function):
|
||||
global qqDumpers, qqFormats, qqEditable
|
||||
try:
|
||||
funcname = function.func_name
|
||||
if funcname.startswith("qdump__"):
|
||||
type = funcname[7:]
|
||||
qqDumpers[type] = function
|
||||
qqFormats[type] = qqFormats.get(type, "")
|
||||
elif funcname.startswith("qform__"):
|
||||
type = funcname[7:]
|
||||
formats = ""
|
||||
try:
|
||||
formats = function()
|
||||
except:
|
||||
pass
|
||||
qqFormats[type] = formats
|
||||
elif funcname.startswith("qedit__"):
|
||||
type = funcname[7:]
|
||||
try:
|
||||
qqEditable[type] = function
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
def warn(message):
|
||||
print 'XXX={"%s"}@\n' % message.encode("latin1").replace('"', "'")
|
||||
@@ -68,22 +164,6 @@ class Type:
|
||||
def strip_typedefs(self):
|
||||
return self
|
||||
|
||||
class Value:
|
||||
def __init__(self, var):
|
||||
self.raw = var
|
||||
self.is_optimized_out = False
|
||||
self.address = var.addr
|
||||
self.type = Type(var)
|
||||
self.name = var.name
|
||||
|
||||
def __str__(self):
|
||||
return str(self.raw.value)
|
||||
|
||||
def __getitem__(self, name):
|
||||
return None if self.raw is None else self.raw.GetChildMemberWithName(name)
|
||||
|
||||
def fields(self):
|
||||
return [Value(self.raw.GetChildAtIndex(i)) for i in xrange(self.raw.num_children)]
|
||||
|
||||
def currentFrame():
|
||||
currentThread = self.process.GetThreadAtIndex(0)
|
||||
@@ -167,6 +247,33 @@ def loggingCallback(args):
|
||||
s = s.replace('"', "'")
|
||||
sys.stdout.write('log="%s"@\n' % s)
|
||||
|
||||
def check(exp):
|
||||
if not exp:
|
||||
raise RuntimeError("Check failed")
|
||||
|
||||
def checkSimpleRef(ref):
|
||||
count = ref["_q_value"]
|
||||
check(count > 0)
|
||||
check(count < 1000000)
|
||||
|
||||
def checkRef(ref):
|
||||
return True
|
||||
try:
|
||||
count = ref["atomic"]["_q_value"] # Qt 5.
|
||||
minimum = -1
|
||||
except:
|
||||
count = ref["_q_value"] # Qt 4.
|
||||
minimum = 0
|
||||
# Assume there aren't a million references to any object.
|
||||
check(count >= minimum)
|
||||
check(count < 1000000)
|
||||
|
||||
|
||||
def valueChildAccess(self, name):
|
||||
return self.GetChildMemberWithName(name)
|
||||
|
||||
lldb.SBValue.__getitem__ = valueChildAccess
|
||||
|
||||
class Children:
|
||||
def __init__(self, d, numChild = 1, childType = None, childNumChild = None,
|
||||
maxNumChild = None, addrBase = None, addrStep = None):
|
||||
@@ -272,10 +379,17 @@ class Debugger:
|
||||
def put(self, stuff):
|
||||
sys.stdout.write(stuff)
|
||||
|
||||
def currentItemFormat(self):
|
||||
#format = self.formats.get(self.currentIName)
|
||||
#if format is None:
|
||||
# format = self.typeformats.get(stripForFormat(str(self.currentType)))
|
||||
#return format
|
||||
return 0
|
||||
|
||||
def putNumChild(self, numchild):
|
||||
#warn("NUM CHILD: '%s' '%s'" % (numchild, self.currentChildNumChild))
|
||||
if numchild != self.currentChildNumChild:
|
||||
self.put('numchild="%s",' % numchild)
|
||||
#if numchild != self.currentChildNumChild:
|
||||
self.put('numchild="%s",' % numchild)
|
||||
|
||||
def putValue(self, value, encoding = None, priority = 0):
|
||||
# Higher priority values override lower ones.
|
||||
@@ -283,6 +397,7 @@ class Debugger:
|
||||
self.currentValue = value
|
||||
self.currentValuePriority = priority
|
||||
self.currentValueEncoding = encoding
|
||||
#self.put('value="%s",' % value)
|
||||
|
||||
def setupInferior(self, args):
|
||||
fileName = args['executable']
|
||||
@@ -293,6 +408,7 @@ class Debugger:
|
||||
self.report('state="inferiorsetupok",msg="%s",exe="%s"' % (error, fileName))
|
||||
else:
|
||||
self.report('state="inferiorsetupfailed",msg="%s",exe="%s"' % (error, fileName))
|
||||
self.importDumpers()
|
||||
|
||||
def runEngine(self, _):
|
||||
error = lldb.SBError()
|
||||
@@ -364,7 +480,7 @@ class Debugger:
|
||||
result += '],current-thread-id="%s"},' % self.currentThread().id
|
||||
self.report(result)
|
||||
|
||||
def reportStack(self):
|
||||
def reportStack(self, _ = None):
|
||||
if self.process is None:
|
||||
self.report('msg="No process"')
|
||||
else:
|
||||
@@ -386,32 +502,89 @@ class Debugger:
|
||||
result += '],hasmore="%s"},' % hasmore
|
||||
self.report(result)
|
||||
|
||||
def putType(self, typeName):
|
||||
self.put('type="%s",' % typeName)
|
||||
|
||||
def putStringValue(self, value, priority = 0):
|
||||
if not value is None:
|
||||
str = self.encodeString(value)
|
||||
self.putValue(str, Hex4EncodedLittleEndian, priority)
|
||||
|
||||
def readRawMemory(self, base, size):
|
||||
error = lldb.SBError()
|
||||
contents = self.process.ReadMemory(base, size, error)
|
||||
return binascii.hexlify(contents)
|
||||
|
||||
def computeLimit(self, size, limit):
|
||||
if limit is None:
|
||||
return size
|
||||
if limit == 0:
|
||||
#return min(size, qqStringCutOff)
|
||||
return min(size, 100)
|
||||
return min(size, limit)
|
||||
|
||||
def qStringData(self, value):
|
||||
private = value['d']
|
||||
checkRef(private['ref'])
|
||||
try:
|
||||
# Qt 5. Will fail on Qt 4 due to the missing 'offset' member.
|
||||
offset = private['offset']
|
||||
data = int(private.GetValue(), 0) + int(offset.GetValue())
|
||||
return data, int(private['size'].GetValue()), int(private['alloc'].GetValue())
|
||||
except:
|
||||
# Qt 4.
|
||||
return private['data'], int(private['size']), int(private['alloc'])
|
||||
|
||||
def encodeString(self, value, limit = 0):
|
||||
data, size, alloc = self.qStringData(value)
|
||||
if alloc != 0:
|
||||
check(0 <= size and size <= alloc and alloc <= 100*1000*1000)
|
||||
limit = self.computeLimit(size, limit)
|
||||
s = self.readRawMemory(data, 2 * limit)
|
||||
if limit < size:
|
||||
s += "2e002e002e00"
|
||||
return s
|
||||
|
||||
def putValue(self, value, encoding = None, priority = 0):
|
||||
# Higher priority values override lower ones.
|
||||
#if priority >= self.currentValuePriority:
|
||||
# self.currentValue = value
|
||||
# self.currentValuePriority = priority
|
||||
# self.currentValueEncoding = encoding
|
||||
if not encoding is None:
|
||||
self.put('valueencoded="%d",' % encoding)
|
||||
self.put('value="%s",' % value)
|
||||
|
||||
def putItem(self, value, tryDynamic=True):
|
||||
#val = value.GetDynamicValue(lldb.eDynamicCanRunTarget)
|
||||
val = value
|
||||
v = val.GetValue()
|
||||
#numchild = 1 if val.MightHaveChildren() else 0
|
||||
numchild = val.GetNumChildren()
|
||||
self.put('iname="%s",' % self.currentIName)
|
||||
self.put('type="%s",' % val.GetTypeName())
|
||||
#self.put('vtype="%s",' % val.GetValueType())
|
||||
self.put('value="%s",' % ("" if v is None else v))
|
||||
self.put('numchild="%s",' % numchild)
|
||||
self.put('addr="0x%x",' % val.GetLoadAddress())
|
||||
if self.currentIName in self.expandedINames:
|
||||
with Children(self):
|
||||
self.putFields(value)
|
||||
global qqDumpers
|
||||
#value = value.GetDynamicValue(lldb.eDynamicCanRunTarget)
|
||||
typeName = value.GetTypeName()
|
||||
if typeName in qqDumpers:
|
||||
self.putType(typeName)
|
||||
qqDumpers[typeName](self, value)
|
||||
else:
|
||||
v = value.GetValue()
|
||||
#numchild = 1 if value.MightHaveChildren() else 0
|
||||
numchild = value.GetNumChildren()
|
||||
self.put('iname="%s",' % self.currentIName)
|
||||
self.put('type="%s",' % typeName)
|
||||
self.put('value="%s",' % ("" if v is None else v))
|
||||
self.put('numchild="%s",' % numchild)
|
||||
self.put('addr="0x%x",' % value.GetLoadAddress())
|
||||
if self.currentIName in self.expandedINames:
|
||||
with Children(self):
|
||||
self.putFields(value)
|
||||
|
||||
def putFields(self, value):
|
||||
n = value.GetNumChildren()
|
||||
if n > 10000:
|
||||
n = 10000
|
||||
for i in xrange(n):
|
||||
field = value.GetChildAtIndex(i)
|
||||
with SubItem(self, field.GetName()):
|
||||
self.putItem(field)
|
||||
child = value.GetChildAtIndex(i)
|
||||
with SubItem(self, child.GetName()):
|
||||
self.putItem(child)
|
||||
|
||||
def reportVariables(self):
|
||||
def reportVariables(self, _ = None):
|
||||
frame = self.currentThread().GetSelectedFrame()
|
||||
self.currentIName = "local"
|
||||
self.put('data=[')
|
||||
@@ -421,7 +594,7 @@ class Debugger:
|
||||
self.put(']')
|
||||
self.report('')
|
||||
|
||||
def reportData(self):
|
||||
def reportData(self, _ = None):
|
||||
self.reportRegisters()
|
||||
if self.process is None:
|
||||
self.report('process="none"')
|
||||
@@ -451,7 +624,7 @@ class Debugger:
|
||||
sys.stdout.write(stuff)
|
||||
sys.stdout.write("@\n")
|
||||
|
||||
def interruptInferior(self):
|
||||
def interruptInferior(self, _ = None):
|
||||
if self.process is None:
|
||||
self.report('msg="No process"')
|
||||
else:
|
||||
@@ -459,7 +632,7 @@ class Debugger:
|
||||
error = self.process.Stop()
|
||||
self.reportError(error)
|
||||
|
||||
def detachInferior(self):
|
||||
def detachInferior(self, _ = None):
|
||||
if self.process is None:
|
||||
self.report('msg="No process"')
|
||||
else:
|
||||
@@ -467,7 +640,7 @@ class Debugger:
|
||||
self.reportError(error)
|
||||
self.reportData()
|
||||
|
||||
def continueInferior(self):
|
||||
def continueInferior(self, _ = None):
|
||||
if self.process is None:
|
||||
self.report('msg="No process"')
|
||||
else:
|
||||
@@ -585,7 +758,7 @@ class Debugger:
|
||||
result += "]"
|
||||
self.report(result)
|
||||
|
||||
def do_listModules(self, args):
|
||||
def listModules(self, args):
|
||||
result = 'modules=['
|
||||
for module in self.target.modules:
|
||||
result += '{file="%s"' % module.file.fullpath
|
||||
@@ -667,8 +840,9 @@ class Debugger:
|
||||
def requestModuleSymbols(self, frame):
|
||||
self.handleCommand("target module list " + frame)
|
||||
|
||||
def executeDebuggerCommand(self, command):
|
||||
def executeDebuggerCommand(self, args):
|
||||
result = lldb.SBCommandReturnObject()
|
||||
command = args['command']
|
||||
self.debugger.GetCommandInterpreter().HandleCommand(command, result)
|
||||
success = result.Succeeded()
|
||||
if success:
|
||||
@@ -715,21 +889,19 @@ class Debugger:
|
||||
result += ',contents="%s"}' % binascii.hexlify(contents)
|
||||
self.report(result)
|
||||
|
||||
def importDumpers(self, _ = None):
|
||||
result = lldb.SBCommandReturnObject()
|
||||
interpreter = self.debugger.GetCommandInterpreter()
|
||||
global qqDumpers, qqFormats, qqEditable
|
||||
items = globals()
|
||||
for key in items:
|
||||
registerDumper(items[key])
|
||||
|
||||
|
||||
currentDir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||
#warn("currentdir: %s" % currentDir)
|
||||
#execfile(os.path.join(currentDir, "dumper.py"))
|
||||
#execfile(os.path.join(currentDir, "qttypes.py"))
|
||||
|
||||
#def importPlainDumpers(args):
|
||||
# pass
|
||||
|
||||
#def bbsetup(args = ''):
|
||||
# global qqDumpers, qqFormats, qqEditable
|
||||
# typeCache = {}
|
||||
#
|
||||
# items = globals()
|
||||
# for key in items:
|
||||
# registerDumper(items[key])
|
||||
|
||||
#bbsetup()
|
||||
execfile(os.path.join(currentDir, "qttypes.py"))
|
||||
|
||||
import sys
|
||||
import select
|
||||
@@ -739,6 +911,7 @@ lldbLoaded = True
|
||||
if __name__ == '__main__':
|
||||
db = Debugger()
|
||||
db.report('state="enginesetupok"')
|
||||
#importPlainDumpers()
|
||||
while True:
|
||||
rlist, _, _ = select.select([sys.stdin], [], [], 0.1)
|
||||
if rlist:
|
||||
|
||||
@@ -82,34 +82,26 @@ LldbEngine::LldbEngine(const DebuggerStartParameters &startParameters)
|
||||
: DebuggerEngine(startParameters)
|
||||
{
|
||||
m_lastAgentId = 0;
|
||||
m_lastToken = 0;
|
||||
setObjectName(QLatin1String("LldbEngine"));
|
||||
}
|
||||
|
||||
LldbEngine::~LldbEngine()
|
||||
{}
|
||||
|
||||
void LldbEngine::executeDebuggerCommand(const QString &command, DebuggerLanguages languages)
|
||||
void LldbEngine::executeDebuggerCommand(const QString &command, DebuggerLanguages)
|
||||
{
|
||||
if (!(languages & CppLanguage))
|
||||
return;
|
||||
QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
|
||||
if (state() == DebuggerNotReady) {
|
||||
showMessage(_("LLDB PROCESS NOT RUNNING, PLAIN CMD IGNORED: ") + command);
|
||||
return;
|
||||
}
|
||||
//runCommand(command.toUtf8());
|
||||
m_lldbProc.write(command.toUtf8() + '\n');
|
||||
runCommand(Command("executeDebuggerCommand").arg("command", command));
|
||||
}
|
||||
|
||||
static int token = 1;
|
||||
|
||||
void LldbEngine::runCommand(const Command &command)
|
||||
{
|
||||
QTC_ASSERT(m_lldbProc.state() == QProcess::Running, notifyEngineIll());
|
||||
++token;
|
||||
++m_lastToken;
|
||||
QByteArray token = QByteArray::number(m_lastToken);
|
||||
QByteArray cmd = "db {'cmd':'" + command.function + "',"
|
||||
+ command.args + "'token':" + QByteArray::number(token) + "}\n";
|
||||
showMessage(QString::number(token) + _(cmd), LogInput);
|
||||
+ command.args + "'token':" + token + "}\n";
|
||||
showMessage(_(token + cmd), LogInput);
|
||||
m_lldbProc.write(cmd);
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +185,7 @@ private:
|
||||
|
||||
// FIXME: Make generic.
|
||||
int m_lastAgentId;
|
||||
int m_lastToken;
|
||||
QMap<QPointer<DisassemblerAgent>, int> m_disassemblerAgents;
|
||||
QMap<QPointer<MemoryAgent>, int> m_memoryAgents;
|
||||
QHash<int, QPointer<QObject> > m_memoryAgentTokens;
|
||||
|
||||
Reference in New Issue
Block a user