dumper.py 62.4 KB
Newer Older
hjk's avatar
hjk committed
1
import sys
2
import base64
3
import __builtin__
4
import os
hjk's avatar
hjk committed
5
import tempfile
6

7 8 9 10
# Fails on Windows.
try:
    import curses.ascii
    def printableChar(ucs):
11 12 13
        if curses.ascii.isprint(ucs):
            return ucs
        return '?'
14
except:
15 16 17 18
    def printableChar(ucs):
        if ucs >= 32 and ucs <= 126:
            return ucs
        return '?'
hjk's avatar
hjk committed
19

20
# Fails on SimulatorQt.
21
tempFileCounter = 0
22
try:
23 24 25
    # Test if 2.6 is used (Windows), trigger exception and default
    # to 2nd version.
    tempfile.NamedTemporaryFile(prefix="py_",delete=True)
26
    def createTempFile():
27
        file = tempfile.NamedTemporaryFile(prefix="py_",delete=False)
28
        file.close()
29
        return file.name, file
30

31 32
except:
    def createTempFile():
33 34
        global tempFileCounter
        tempFileCounter += 1
35
        fileName = "%s/py_tmp_%d_%d" \
hjk's avatar
hjk committed
36
            % (tempfile.gettempdir(), os.getpid(), tempFileCounter)
37
        return fileName, None
38

39 40 41 42
def removeTempFile(name, file):
    try:
        os.remove(name)
    except:
43
        pass
44

45 46 47 48 49
try:
    import binascii
except:
    pass

hjk's avatar
hjk committed
50 51 52
verbosity = 0
verbosity = 1

53 54 55 56
# Some "Enums"

# Encodings
Unencoded8Bit, \
57 58 59 60 61 62 63 64 65 66
Base64Encoded8BitWithQuotes, \
Base64Encoded16BitWithQuotes, \
Base64Encoded32BitWithQuotes, \
Base64Encoded16Bit, \
Base64Encoded8Bit, \
Hex2EncodedLatin1, \
Hex4EncodedLittleEndian, \
Hex8EncodedLittleEndian, \
Hex2EncodedUtf8, \
Hex8EncodedBigEndian, \
Arvid Ephraim Picciani's avatar
Arvid Ephraim Picciani committed
67
Hex4EncodedBigEndian, \
68
Hex4EncodedLittleEndianWithoutQuotes, \
69 70 71 72 73
Hex2EncodedLocal8Bit, \
JulianDate, \
MillisecondsSinceMidnight, \
JulianDateAndMillisecondsSinceMidnight \
    = range(17)
74 75

# Display modes
76 77 78 79 80 81
StopDisplay, \
DisplayImage1, \
DisplayString, \
DisplayImage, \
DisplayProcess \
    = range(5)
82 83


84
def hasInferiorThreadList():
85
    #return False
86
    try:
87
        a = gdb.inferiors()[0].threads()
88 89 90 91
        return True
    except:
        return False

92 93
def dynamicTypeName(value):
    #vtbl = str(parseAndEvaluate("{int(*)(int)}%s" % long(value.address)))
hjk's avatar
hjk committed
94 95 96 97 98 99 100 101 102 103 104 105
    try:
        # Fails on 7.1 due to the missing to_string.
        vtbl = gdb.execute("info symbol {int*}%s" % long(value.address),
            to_string = True)
        pos1 = vtbl.find("vtable ")
        if pos1 != -1:
            pos1 += 11
            pos2 = vtbl.find(" +", pos1)
            if pos2 != -1:
                return vtbl[pos1 : pos2]
    except:
        pass
106 107
    return str(value.type)

108 109
def upcast(value):
    try:
110 111 112 113 114 115 116 117 118 119 120 121
        return value.cast(value.dynamic_type)
    except:
        pass
    #try:
    #    return value.cast(lookupType(dynamicTypeName(value)))
    #except:
    #    pass
    return value

def expensiveUpcast(value):
    try:
        return value.cast(value.dynamic_type)
122
    except:
123 124 125 126 127 128
        pass
    try:
        return value.cast(lookupType(dynamicTypeName(value)))
    except:
        pass
    return value
129

130 131
typeCache = {}

132 133 134 135 136 137 138
class TypeInfo:
    def __init__(self, type):
        self.size = type.sizeof
        self.reported = False

typeInfoCache = {}

139 140
def lookupType(typestring):
    type = typeCache.get(typestring)
hjk's avatar
hjk committed
141
    #warn("LOOKUP 1: %s -> %s" % (typestring, type))
142 143 144
    if not type is None:
        return type

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
    if typestring == "void":
        type = gdb.lookup_type(typestring)
        typeCache[typestring] = type
        return type

    if typestring.find("(anon") != -1:
        # gdb doesn't like
        # '(anonymous namespace)::AddAnalysisMessageSuppressionComment'
        typeCache[typestring] = None
        return None

    try:
        type = gdb.parse_and_eval("{%s}&main" % typestring).type
        typeCache[typestring] = type
        return type
    except:
        pass

hjk's avatar
hjk committed
163 164 165
    #warn(" RESULT FOR 7.2: '%s': %s" % (typestring, type))
    #typeCache[typestring] = type
    #return None
166

hjk's avatar
hjk committed
167 168
    # This part should only trigger for
    # gdb 7.1 for types with namespace separators.
169

170 171
    ts = typestring
    while True:
hjk's avatar
hjk committed
172
        #warn("TS: '%s'" % ts)
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
        if ts.startswith("class "):
            ts = ts[6:]
        elif ts.startswith("struct "):
            ts = ts[7:]
        elif ts.startswith("const "):
            ts = ts[6:]
        elif ts.startswith("volatile "):
            ts = ts[9:]
        elif ts.startswith("enum "):
            ts = ts[5:]
        elif ts.endswith(" const"):
            ts = ts[:-6]
        elif ts.endswith(" volatile"):
            ts = ts[:-9]
        elif ts.endswith("*const"):
            ts = ts[:-5]
        elif ts.endswith("*volatile"):
            ts = ts[:-8]
        else:
            break

    if ts.endswith('*'):
        type = lookupType(ts[0:-1])
hjk's avatar
hjk committed
196 197 198
        if not type is None:
            type = type.pointer()
            typeCache[typestring] = type
199 200 201 202 203 204 205
            return type

    try:
        #warn("LOOKING UP '%s'" % ts)
        type = gdb.lookup_type(ts)
    except RuntimeError, error:
        #warn("LOOKING UP '%s': %s" % (ts, error))
206 207 208 209 210
        if type is None:
            pos = typestring.find("<unnamed>")
            if pos != -1:
                # See http://sourceware.org/bugzilla/show_bug.cgi?id=13269
                return lookupType(typestring.replace("<unnamed>", "(anonymous namespace)"))
211 212 213 214 215 216 217 218 219
        # See http://sourceware.org/bugzilla/show_bug.cgi?id=11912
        exp = "(class '%s'*)0" % ts
        try:
            type = parseAndEvaluate(exp).type.target()
        except:
            # Can throw "RuntimeError: No type named class Foo."
            pass
    except:
        #warn("LOOKING UP '%s' FAILED" % ts)
220
        pass
221 222 223

    # This could still be None as gdb.lookup_type("char[3]") generates
    # "RuntimeError: No type named char[3]"
224 225
    return type

226
def cleanAddress(addr):
227 228
    if addr is None:
        return "<no address>"
229 230
    # We cannot use str(addr) as it yields rubbish for char pointers
    # that might trigger Unicode encoding errors.
231
    #return addr.cast(lookupType("void").pointer())
232 233
    # We do not use "hex(...)" as it (sometimes?) adds a "L" suffix.
    return "0x%x" % long(addr)
234

235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
def extractTemplateArgument(type, position):
    level = 0
    skipSpace = False
    inner = ""
    type = str(type)
    for c in type[type.find('<') + 1 : -1]:
        if c == '<':
            inner += c
            level += 1
        elif c == '>':
            level -= 1
            inner += c
        elif c == ',':
            if level == 0:
                if position == 0:
                    return inner
                position -= 1
                inner = ""
            else:
                inner += c
                skipSpace = True
        else:
            if skipSpace and c == ' ':
                pass
            else:
                inner += c
                skipSpace = False
    return inner

def templateArgument(type, position):
265 266 267 268 269 270
    try:
        # This fails on stock 7.2 with
        # "RuntimeError: No type named myns::QObject.\n"
        return type.template_argument(position)
    except:
        # That's something like "myns::QList<...>"
271
        return lookupType(extractTemplateArgument(type.strip_typedefs(), position))
272

273

hjk's avatar
hjk committed
274 275 276 277
# Workaround for gdb < 7.1
def numericTemplateArgument(type, position):
    try:
        return int(type.template_argument(position))
278
    except RuntimeError, error:
hjk's avatar
hjk committed
279 280 281 282
        # ": No type named 30."
        msg = str(error)
        return int(msg[14:-1])

283

284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
def showException(msg, exType, exValue, exTraceback):
    warn("**** CAUGHT EXCEPTION: %s ****" % msg)
    try:
        import traceback
        for line in traceback.format_exception(exType, exValue, exTraceback):
            warn("%s" % line)
    except:
        pass


class OutputSafer:
    def __init__(self, d, pre = "", post = ""):
        self.d = d
        self.pre = pre
        self.post = post

    def __enter__(self):
        self.d.put(self.pre)
        self.savedOutput = self.d.output
hjk's avatar
hjk committed
303
        self.d.output = []
304 305 306 307 308 309 310

    def __exit__(self, exType, exValue, exTraceBack):
        self.d.put(self.post)
        if self.d.passExceptions and not exType is None:
            showException("OUTPUTSAFER", exType, exValue, exTraceBack)
            self.d.output = self.savedOutput
        else:
hjk's avatar
hjk committed
311 312
            self.savedOutput.extend(self.d.output)
            self.d.output = self.savedOutput
313 314 315
        return False


316 317 318 319 320
class NoAddress:
    def __init__(self, d):
        self.d = d

    def __enter__(self):
hjk's avatar
hjk committed
321 322
        self.savedPrintsAddress = self.d.currentPrintsAddress
        self.d.currentPrintsAddress = False
323 324

    def __exit__(self, exType, exValue, exTraceBack):
hjk's avatar
hjk committed
325
        self.d.currentPrintsAddress = self.savedPrintsAddress
326 327


328 329 330 331 332 333 334 335 336 337 338 339 340
class NoDynamicType:
    def __init__(self, d):
        self.d = d

    def __enter__(self):
        self.savedUseDynamicType = self.d.useDynamicType
        self.d.useDynamicType = False

    def __exit__(self, exType, exValue, exTraceBack):
        self.d.useDynamicType = self.savedUseDynamicType



341
class SubItem:
hjk's avatar
hjk committed
342
    def __init__(self, d, component):
343
        self.d = d
hjk's avatar
hjk committed
344 345
        self.iname = "%s.%s" % (d.currentIName, component)
        self.name = component
346 347 348

    def __enter__(self):
        self.d.put('{')
hjk's avatar
hjk committed
349 350 351 352
        #if not self.name is None:
        if isinstance(self.name, str):
            self.d.put('name="%s",' % self.name)
        self.savedIName = self.d.currentIName
353 354 355 356 357
        self.savedValue = self.d.currentValue
        self.savedValuePriority = self.d.currentValuePriority
        self.savedValueEncoding = self.d.currentValueEncoding
        self.savedType = self.d.currentType
        self.savedTypePriority = self.d.currentTypePriority
hjk's avatar
hjk committed
358
        self.d.currentIName = self.iname
359
        self.d.currentValuePriority = -100
360 361
        self.d.currentValueEncoding = None
        self.d.currentType = ""
362
        self.d.currentTypePriority = -100
363 364 365 366 367 368

    def __exit__(self, exType, exValue, exTraceBack):
        #warn(" CURRENT VALUE: %s %s %s" % (self.d.currentValue,
        #    self.d.currentValueEncoding, self.d.currentValuePriority))
        if self.d.passExceptions and not exType is None:
            showException("SUBITEM", exType, exValue, exTraceBack)
369
        try:
370
            #warn("TYPE VALUE: %s" % self.d.currentValue)
hjk's avatar
hjk committed
371 372 373 374 375 376 377 378
            typeName = stripClassTag(self.d.currentType)
            #warn("TYPE: '%s'  DEFAULT: '%s' % (typeName, self.d.currentChildType))

            if len(typeName) > 0 and typeName != self.d.currentChildType:
                self.d.put('type="%s",' % typeName) # str(type.unqualified()) ?
                if not typeName in typeInfoCache \
                        and typeName != " ": # FIXME: Move to lookupType
                    typeObj = lookupType(typeName)
379
                    if not typeObj is None:
hjk's avatar
hjk committed
380 381 382 383 384 385
                        typeInfoCache[typeName] = TypeInfo(typeObj)
            if  self.d.currentValue is None:
                self.d.put('value="<not accessible>",numchild="0",')
            else:
                if not self.d.currentValueEncoding is None:
                    self.d.put('valueencoded="%d",' % self.d.currentValueEncoding)
386
                self.d.put('value="%s",' % self.d.currentValue)
387 388
        except:
            pass
389
        self.d.put('},')
hjk's avatar
hjk committed
390
        self.d.currentIName = self.savedIName
391 392 393 394 395 396 397
        self.d.currentValue = self.savedValue
        self.d.currentValuePriority = self.savedValuePriority
        self.d.currentValueEncoding = self.savedValueEncoding
        self.d.currentType = self.savedType
        self.d.currentTypePriority = self.savedTypePriority
        return True

hjk's avatar
hjk committed
398 399 400 401 402 403 404 405 406 407 408
class TopLevelItem(SubItem):
    def __init__(self, d, iname):
        self.d = d
        self.iname = iname
        self.name = None

class UnnamedSubItem(SubItem):
    def __init__(self, d, component):
        self.d = d
        self.iname = "%s.%s" % (self.d.currentIName, component)
        self.name = None
409 410

class Children:
hjk's avatar
hjk committed
411 412
    def __init__(self, d, numChild = 1, childType = None, childNumChild = None,
            maxNumChild = None, addrBase = None, addrStep = None):
413 414 415
        self.d = d
        self.numChild = numChild
        self.childNumChild = childNumChild
hjk's avatar
hjk committed
416 417 418 419
        self.maxNumChild = maxNumChild
        self.addrBase = addrBase
        self.addrStep = addrStep
        self.printsAddress = True
420 421 422
        if childType is None:
            self.childType = None
        else:
hjk's avatar
hjk committed
423 424
            self.childType = stripClassTag(str(childType))
            self.d.put('childtype="%s",' % self.childType)
425 426 427 428 429 430 431 432 433 434
            if childNumChild is None:
                if isSimpleType(childType):
                    self.d.put('childnumchild="0",')
                    self.childNumChild = 0
                elif childType.code == PointerCode:
                    self.d.put('childnumchild="1",')
                    self.childNumChild = 1
            else:
                self.d.put('childnumchild="%s",' % childNumChild)
                self.childNumChild = childNumChild
hjk's avatar
hjk committed
435 436 437 438 439 440 441
        try:
            if not addrBase is None and not addrStep is None:
                self.d.put('addrbase="0x%x",' % long(addrBase))
                self.d.put('addrstep="0x%x",' % long(addrStep))
                self.printsAddress = False
        except:
            warn("ADDRBASE: %s" % addrBase)
442 443 444 445 446
        #warn("CHILDREN: %s %s %s" % (numChild, childType, childNumChild))

    def __enter__(self):
        self.savedChildType = self.d.currentChildType
        self.savedChildNumChild = self.d.currentChildNumChild
hjk's avatar
hjk committed
447 448 449 450 451 452 453 454
        self.savedNumChild = self.d.currentNumChild
        self.savedMaxNumChild = self.d.currentMaxNumChild
        self.savedPrintsAddress = self.d.currentPrintsAddress
        self.d.currentChildType = self.childType
        self.d.currentChildNumChild = self.childNumChild
        self.d.currentNumChild = self.numChild
        self.d.currentMaxNumChild = self.maxNumChild
        self.d.currentPrintsAddress = self.printsAddress
455 456 457 458 459
        self.d.put("children=[")

    def __exit__(self, exType, exValue, exTraceBack):
        if self.d.passExceptions and not exType is None:
            showException("CHILDREN", exType, exValue, exTraceBack)
hjk's avatar
hjk committed
460 461 462
        if not self.d.currentMaxNumChild is None:
            if self.d.currentMaxNumChild < self.d.currentNumChild:
                self.d.put('{name="<incomplete>",value="",type="",numchild="0"},')
463 464
        self.d.currentChildType = self.savedChildType
        self.d.currentChildNumChild = self.savedChildNumChild
hjk's avatar
hjk committed
465 466 467
        self.d.currentNumChild = self.savedNumChild
        self.d.currentMaxNumChild = self.savedMaxNumChild
        self.d.currentPrintsAddress = self.savedPrintsAddress
468 469 470 471
        self.d.put('],')
        return True


472
def value(expr):
473
    value = parseAndEvaluate(expr)
474 475 476 477 478
    try:
        return int(value)
    except:
        return str(value)

hjk's avatar
hjk committed
479
def isSimpleType(typeobj):
480
    code = typeobj.code
481 482 483 484 485
    return code == BoolCode \
        or code == CharCode \
        or code == IntCode \
        or code == FloatCode \
        or code == EnumCode
hjk's avatar
hjk committed
486 487

def warn(message):
488 489
    if True or verbosity > 0:
        print "XXX: %s\n" % message.encode("latin1")
hjk's avatar
hjk committed
490 491 492 493 494 495
    pass

def check(exp):
    if not exp:
        raise RuntimeError("Check failed")

496
def checkRef(ref):
497 498 499 500 501
    count = 0
    if qtMajorVersion() >= 5:
        count = ref["atomic"]["_q_value"]
    else:
        count = ref["_q_value"]
502 503 504
    check(count > 0)
    check(count < 1000000) # assume there aren't a million references to any object

hjk's avatar
hjk committed
505
#def couldBePointer(p, align):
506
#    type = lookupType("unsigned int")
hjk's avatar
hjk committed
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
#    ptr = gdb.Value(p).cast(type)
#    d = int(str(ptr))
#    warn("CHECKING : %s %d " % (p, ((d & 3) == 0 and (d > 1000 or d == 0))))
#    return (d & (align - 1)) and (d > 1000 or d == 0)


def checkAccess(p, align = 1):
    return p.dereference()

def checkContents(p, expected, align = 1):
    if int(p.dereference()) != expected:
        raise RuntimeError("Contents check failed")

def checkPointer(p, align = 1):
    if not isNull(p):
        p.dereference()

524 525 526 527 528 529
def isAccessible(p):
    try:
        long(p)
        return True
    except:
        return False
hjk's avatar
hjk committed
530 531

def isNull(p):
532 533 534 535
    # The following can cause evaluation to abort with "UnicodeEncodeError"
    # for invalid char *, as their "contents" is being examined
    #s = str(p)
    #return s == "0x0" or s.startswith("0x0 ")
536 537 538 539 540
    #try:
    #    # Can fail with: "RuntimeError: Cannot access memory at address 0x5"
    #    return p.cast(lookupType("void").pointer()) == 0
    #except:
    #    return False
541 542 543 544 545
    try:
        # Can fail with: "RuntimeError: Cannot access memory at address 0x5"
        return long(p) == 0
    except:
        return False
hjk's avatar
hjk committed
546 547

movableTypes = set([
hjk's avatar
hjk committed
548 549 550 551 552 553
    "QBrush", "QBitArray", "QByteArray", "QCustomTypeInfo", "QChar", "QDate",
    "QDateTime", "QFileInfo", "QFixed", "QFixedPoint", "QFixedSize",
    "QHashDummyValue", "QIcon", "QImage", "QLine", "QLineF", "QLatin1Char",
    "QLocale", "QMatrix", "QModelIndex", "QPoint", "QPointF", "QPen",
    "QPersistentModelIndex", "QResourceRoot", "QRect", "QRectF", "QRegExp",
    "QSize", "QSizeF", "QString", "QTime", "QTextBlock", "QUrl", "QVariant",
hjk's avatar
hjk committed
554
    "QXmlStreamAttribute", "QXmlStreamNamespaceDeclaration",
hjk's avatar
hjk committed
555 556 557 558 559 560 561 562 563 564 565 566 567
    "QXmlStreamNotationDeclaration", "QXmlStreamEntityDeclaration"
])

def stripClassTag(typeName):
    if typeName.startswith("class "):
        return typeName[6:]
    if typeName.startswith("struct "):
        return typeName[7:]
    if typeName.startswith("const "):
        return typeName[6:]
    if typeName.startswith("volatile "):
        return typeName[9:]
    return typeName
hjk's avatar
hjk committed
568 569

def checkPointerRange(p, n):
570
    for i in xrange(n):
hjk's avatar
hjk committed
571 572 573
        checkPointer(p)
        ++p

hjk's avatar
hjk committed
574 575 576 577 578 579 580 581 582 583 584 585 586
def call2(value, func, args):
    # args is a tuple.
    arg = ""
    for i in range(len(args)):
        if i:
            arg += ','
        a = args[i]
        if (':' in a) and not ("'" in a):
            arg = "'%s'" % a
        else:
            arg += a

    #warn("CALL: %s -> %s(%s)" % (value, func, arg))
hjk's avatar
hjk committed
587
    type = stripClassTag(str(value.type))
588
    if type.find(":") >= 0:
hjk's avatar
hjk committed
589
        type = "'" + type + "'"
590
    # 'class' is needed, see http://sourceware.org/bugzilla/show_bug.cgi?id=11912
hjk's avatar
hjk committed
591
    exp = "((class %s*)%s)->%s(%s)" % (type, value.address, func, arg)
hjk's avatar
hjk committed
592
    #warn("CALL: %s" % exp)
593 594 595 596 597
    result = None
    try:
        result = parseAndEvaluate(exp)
    except:
        pass
hjk's avatar
hjk committed
598 599 600
    #warn("  -> %s" % result)
    return result

hjk's avatar
hjk committed
601 602 603
def call(value, func, *args):
    return call2(value, func, args)

604 605 606 607
def makeValue(type, init):
    type = stripClassTag(type)
    if type.find(":") >= 0:
        type = "'" + type + "'"
hjk's avatar
hjk committed
608
    # Avoid malloc symbol clash with QVector.
609
    gdb.execute("set $d = (%s*)calloc(sizeof(%s), 1)" % (type, type))
610 611 612 613 614 615 616
    gdb.execute("set *$d = {%s}" % init)
    value = parseAndEvaluate("$d").dereference()
    #warn("  TYPE: %s" % value.type)
    #warn("  ADDR: %s" % value.address)
    #warn("  VALUE: %s" % value)
    return value

617 618 619 620 621 622 623 624 625 626 627 628
def makeStdString(init):
    # Works only for small allocators, but they are usually empty.
    gdb.execute("set $d=(std::string*)calloc(sizeof(std::string), 2)");
    gdb.execute("call($d->basic_string(\"" + init +
        "\",*(std::allocator<char>*)(1+$d)))")
    value = parseAndEvaluate("$d").dereference()
    #warn("  TYPE: %s" % value.type)
    #warn("  ADDR: %s" % value.address)
    #warn("  VALUE: %s" % value)
    return value


629 630 631 632 633 634 635 636 637 638
def makeExpression(value):
    type = stripClassTag(str(value.type))
    if type.find(":") >= 0:
        type = "'" + type + "'"
    #warn("  TYPE: %s" % type)
    #exp = "(*(%s*)(&%s))" % (type, value.address)
    exp = "(*(%s*)(%s))" % (type, value.address)
    #warn("  EXP: %s" % exp)
    return exp

639 640
qqNs = None

641
def qtNamespace():
642
    # FIXME: This only works when call from inside a Qt function frame.
643
    global qqNs
644 645
    if not qqNs is None:
        return qqNs
646
    try:
647
        str = catchCliOutput("ptype QString::Null")[0]
hjk's avatar
hjk committed
648
        # The result looks like:
649 650 651 652 653
        # "type = const struct myns::QString::Null {"
        # "    <no data fields>"
        # "}"
        pos1 = str.find("struct") + 7
        pos2 = str.find("QString::Null")
654 655 656 657
        if pos1 > -1 and pos2 > -1:
            qqNs = str[pos1:pos2]
            return qqNs
        return ""
658
    except:
659
        return ""
hjk's avatar
hjk committed
660

661 662 663 664 665 666 667 668 669 670
# --  Determine major Qt version by calling qVersion (cached)

qqMajorVersion = None

def qtMajorVersion():
    global qqMajorVersion
    if not qqMajorVersion is None:
        return qqMajorVersion
    try:
        # -- Result is returned as character, need to subtract '0'
671 672 673 674 675
        v = int(parseAndEvaluate("*(char*)qVersion()"))
        if v >= 51:
            qqMajorVersion = v - 48
            return qqMajorVersion
        return 0
676 677 678
    except:
        return 0

679 680
def findFirstZero(p, maximum):
    for i in xrange(maximum):
681 682 683
        if p.dereference() == 0:
            return i
        p = p + 1
684
    return maximum + 1
685

686
def extractCharArray(p, maxsize):
687
    p = p.cast(lookupType("unsigned char").pointer())
688
    s = ""
689 690 691 692 693 694
    i = 0
    while i < maxsize:
        c = int(p.dereference())
        if c == 0:
            return s
        s += "%c" % c
695
        p += 1
696
        i += 1
697 698 699
    if p.dereference() != 0:
        s += "..."
    return s
700 701 702 703 704 705 706 707 708 709 710

def extractByteArray(value):
    d_ptr = value['d'].dereference()
    data = d_ptr['data']
    size = d_ptr['size']
    alloc = d_ptr['alloc']
    check(0 <= size and size <= alloc and alloc <= 100*1000*1000)
    checkRef(d_ptr["ref"])
    if size > 0:
        checkAccess(data, 4)
        checkAccess(data + size) == 0
711
    return extractCharArray(data, min(100, size))
712

713
def encodeCharArray(p, maxsize, limit = -1):
714
    t = lookupType("unsigned char").pointer()
715
    p = p.cast(t)
716 717
    if limit == -1:
        limit = findFirstZero(p, maxsize)
718
    s = ""
719 720 721 722 723 724 725 726
    try:
        # gdb.Inferior is new in gdb 7.2
        inferior = gdb.inferiors()[0]
        s = binascii.hexlify(inferior.read_memory(p, limit))
    except:
        for i in xrange(limit):
            s += "%02x" % int(p.dereference())
            p += 1
727
    if limit > maxsize:
728 729 730 731
        s += "2e2e2e"
    return s

def encodeChar2Array(p, maxsize):
732
    t = lookupType("unsigned short").pointer()
733
    p = p.cast(t)
734
    limit = findFirstZero(p, maxsize)
735 736 737 738 739 740 741 742 743
    s = ""
    for i in xrange(limit):
        s += "%04x" % int(p.dereference())
        p += 1
    if i == maxsize:
        s += "2e002e002e00"
    return s

def encodeChar4Array(p, maxsize):
744
    t = lookupType("unsigned int").pointer()
745
    p = p.cast(t)
746
    limit = findFirstZero(p, maxsize)
747 748 749 750
    s = ""
    for i in xrange(limit):
        s += "%08x" % int(p.dereference())
        p += 1
751
    if i > maxsize:
752
        s += "2e0000002e0000002e000000"
753 754
    return s

755 756 757 758 759 760 761 762 763 764 765 766
def qByteArrayData(value):
    if qtMajorVersion() < 5:
        d_ptr = value['d'].dereference()
        checkRef(d_ptr["ref"])
        data = d_ptr['data']
        size = d_ptr['size']
        alloc = d_ptr['alloc']
        return data, size, alloc
    else: # Qt5: Implement the QByteArrayData::data() accessor.
        qByteArrayData = value['d'].dereference()
        size = qByteArrayData['size']
        alloc = qByteArrayData['alloc']
767
        charPointerType = lookupType('char *')
hjk's avatar
hjk committed
768 769
        data = qByteArrayData['d'].cast(charPointerType) \
             + qByteArrayData['offset'] + charPointerType.sizeof
770 771
        return data, size, alloc

772
def encodeByteArray(value):
773
    data, size, alloc = qByteArrayData(value)
774 775 776 777
    check(0 <= size and size <= alloc and alloc <= 100*1000*1000)
    if size > 0:
        checkAccess(data, 4)
        checkAccess(data + size) == 0
778
    return encodeCharArray(data, 100, size)
779

780 781 782 783
def qQStringData(value):
    if qtMajorVersion() < 5:
        d_ptr = value['d'].dereference()
        checkRef(d_ptr['ref'])
hjk's avatar
hjk committed
784
        return d_ptr['data'], int(d_ptr['size']), int(d_ptr['alloc'])
785 786
    else: # Qt5: Implement the QStringArrayData::data() accessor.
        qStringData = value['d'].dereference()
787
        ushortPointerType = lookupType('ushort *')
hjk's avatar
hjk committed
788 789 790
        data = qStringData['d'].cast(ushortPointerType) \
            + ushortPointerType.sizeof / 2 + qStringData['offset']
        return data, int(qStringData['size']), int(qStringData['alloc'])
791

792
def encodeString(value):
793 794
    data, size, alloc = qQStringData(value)

795 796 797
    check(0 <= size and size <= alloc and alloc <= 100*1000*1000)
    if size > 0:
        checkAccess(data, 4)
hjk's avatar
hjk committed
798
        checkAccess(data + size) == 0
799
    s = ""
800
    limit = min(size, 1000)
801 802 803
    try:
        # gdb.Inferior is new in gdb 7.2
        inferior = gdb.inferiors()[0]
804
        s = binascii.hexlify(inferior.read_memory(data, 2 * limit))
805
    except:
806
        p = data
807
        for i in xrange(limit):
808 809 810 811
            val = int(p.dereference())
            s += "%02x" % (val % 256)
            s += "%02x" % (val / 256)
            p += 1
812 813
    if limit < size:
        s += "2e002e002e00"
814 815
    return s

hjk's avatar
hjk committed
816 817
def stripTypedefs(type):
    type = type.unqualified()
hjk's avatar
hjk committed
818
    while type.code == TypedefCode:
819 820 821
        type = type.strip_typedefs().unqualified()
    return type

hjk's avatar
hjk committed
822 823
def extractFields(type):
    # Insufficient, see http://sourceware.org/bugzilla/show_bug.cgi?id=10953:
hjk's avatar
hjk committed
824
    #fields = type.fields()
hjk's avatar
hjk committed
825
    # Insufficient, see http://sourceware.org/bugzilla/show_bug.cgi?id=11777:
hjk's avatar
hjk committed
826
    #fields = defsype).fields()
hjk's avatar
hjk committed
827
    # This seems to work.
hjk's avatar
hjk committed
828
    #warn("TYPE 0: %s" % type)
hjk's avatar
hjk committed
829
    type = stripTypedefs(type)
830 831 832
    fields = type.fields()
    if len(fields):
        return fields
hjk's avatar
hjk committed
833
    #warn("TYPE 1: %s" % type)
834
    # This fails for arrays. See comment in lookupType.
835 836 837
    type0 = lookupType(str(type))
    if not type0 is None:
        type = type0
hjk's avatar
hjk committed
838
    if type.code == FunctionCode:
839
        return []
hjk's avatar
hjk committed
840 841
    #warn("TYPE 2: %s" % type)
    fields = type.fields()
hjk's avatar
hjk committed
842
    #warn("FIELDS: %s" % fields)
hjk's avatar
hjk committed
843 844
    return fields

hjk's avatar
hjk committed
845 846
#######################################################################
#
847
# LocalItem
hjk's avatar
hjk committed
848 849 850
#
#######################################################################

851
# Contains iname, name, and value.
hjk's avatar
hjk committed
852
class LocalItem:
853
    pass
hjk's avatar
hjk committed
854

855 856 857 858 859 860
#######################################################################
#
# SetupCommand
#
#######################################################################

861
# This is a cache mapping from 'type name' to 'display alternatives'.
862
qqFormats = {}
863 864

# This is a cache of all known dumpers.
hjk's avatar
hjk committed
865
qqDumpers = {}
866

867 868 869
# This is a cache of all dumpers that support writing.
qqEditable = {}

870 871 872 873 874
# This is a cache of the namespace of the currently used Qt version.
# FIXME: This is not available on 'bbsetup' time, only at 'bb' time.

# This is a cache of typenames->bool saying whether we are QObject
# derived.
hjk's avatar
hjk committed
875
qqQObjectCache = {}
876

877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899
# This keeps canonical forms of the typenames, without array indices etc.
qqStripForFormat = {}

def stripForFormat(typeName):
    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

900
def bbsetup(args):
hjk's avatar
hjk committed
901 902
    typeInfoCache = {}
    typeCache = {}
903 904 905 906 907
    module = sys.modules[__name__]
    for key, value in module.__dict__.items():
        if key.startswith("qdump__"):
            name = key[7:]
            qqDumpers[name] = value
hjk's avatar
hjk committed
908
            qqFormats[name] = qqFormats.get(name, "")
909 910 911 912 913 914 915 916
        elif key.startswith("qform__"):
            name = key[7:]
            formats = ""
            try:
                formats = value()
            except:
                pass
            qqFormats[name] = formats
917 918 919 920 921 922
        elif key.startswith("qedit__"):
            name = key[7:]
            try:
                qqEditable[name] = value
            except:
                pass
923
    result = "dumpers=["
924
    #qqNs = qtNamespace() # This is too early
925
    for key, value in qqFormats.items():
926 927 928 929
        if qqEditable.has_key(key):
            result += '{type="%s",formats="%s",editable="true"},' % (key, value)
        else:
            result += '{type="%s",formats="%s"},' % (key, value)
930 931
    result += ']'
    #result += ',namespace="%s"' % qqNs
932
    result += ',hasInferiorThreadList="%s"' % int(hasInferiorThreadList())
933 934
    return result

935 936
registerCommand("bbsetup", bbsetup)

937

938 939 940 941 942 943 944
#######################################################################
#
# Edit Command
#
#######################################################################

def bbedit(args):
945 946
    (type, expr, value) = args.split(",")
    type = base64.b16decode(type, True)
947
    ns = qtNamespace()
948 949
    if type.startswith(ns):
        type = type[len(ns):]
950
    type = type.replace("::", "__")
951 952 953 954 955 956
    pos = type.find('<')
    if pos != -1:
        type = type[0:pos]
    expr = base64.b16decode(expr, True)
    value = base64.b16decode(value, True)
    #warn("EDIT: %s %s %s %s: " % (pos, type, expr, value))
957 958
    if qqEditable.has_key(type):
        qqEditable[type](expr, value)
959 960
    else:
        gdb.execute("set (%s)=%s" % (expr, value))
961

962
registerCommand("bbedit", bbedit)
963 964


hjk's avatar
hjk committed
965 966
#######################################################################
#
967
# Frame Command
hjk's avatar
hjk committed
968 969 970
#
#######################################################################

971
def bb(args):
hjk's avatar
hjk committed
972
    output = 'data=[' + "".join(Dumper(args).output) + '],typeinfo=['
973 974 975 976 977 978 979
    for typeName, typeInfo in typeInfoCache.iteritems():
        if not typeInfo.reported:
            output += '{name="' + base64.b64encode(typeName)
            output += '",size="' + str(typeInfo.size) + '"},'
            typeInfo.reported = True
    output += ']';
    return output
hjk's avatar
hjk committed
980

981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998
registerCommand("bb", bb)

def p1(args):
    import cProfile
    cProfile.run('bb("%s")' % args, "/tmp/bbprof")
    import pstats
    pstats.Stats('/tmp/bbprof').sort_stats('time').print_stats()
    return ""

registerCommand("p1", p1)

def p2(args):
    import timeit
    return timeit.repeat('bb("%s")' % args,
        'from __main__ import bb', number=10)

registerCommand("p2", p2)

hjk's avatar
hjk committed
999 1000 1001 1002 1003 1004 1005

#######################################################################
#
# The Dumper Class
#
#######################################################################

1006

hjk's avatar
hjk committed
1007
class Dumper:
1008
    def __init__(self, args):
hjk's avatar
hjk committed
1009
        self.output = []
hjk's avatar
hjk committed
1010 1011
        self.currentIName = ""
        self.currentPrintsAddress = True
1012 1013
        self.currentChildType = ""
        self.currentChildNumChild = -1
hjk's avatar
hjk committed
1014 1015
        self.currentMaxNumChild = -1
        self.currentNumChild = -1
1016 1017 1018 1019 1020
        self.currentValue = None
        self.currentValuePriority = -100
        self.currentValueEncoding = None
        self.currentType = None
        self.currentTypePriority = -100
1021 1022 1023 1024 1025 1026 1027 1028
        self.typeformats = {}
        self.formats = {}
        self.expandedINames = ""

        options = []
        varList = []
        watchers = ""

1029
        resultVarName = ""
1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054
        for arg in args.split(' '):
            pos = arg.find(":") + 1
            if arg.startswith("options:"):
                options = arg[pos:].split(",")
            elif arg.startswith("vars:"):
                if len(arg[pos:]) > 0:
                    varList = arg[pos:].split(",")
            elif arg.startswith("resultvarname:"):
                resultVarName = arg[pos:]
            elif arg.startswith("expanded:"):
                self.expandedINames = set(arg[pos:].split(","))
            elif arg.startswith("typeformats:"):
                for f in arg[pos:].split(","):
                    pos = f.find("=")
                    if pos != -1:
                        type = base64.b16decode(f[0:pos], True)
                        self.typeformats[type] = int(f[pos+1:])
            elif arg.startswith("formats:"):
                for f in arg[pos:].split(","):
                    pos = f.find("=")
                    if pos != -1:
                        self.formats[f[0:pos]] = int(f[pos+1:])
            elif arg.startswith("watchers:"):
                watchers = base64.b16decode(arg[pos:], True)

1055
        self.useDynamicType = "dyntype" in options
1056 1057 1058
        self.useFancy = "fancy" in options
        self.passExceptions = "pe" in options
        self.autoDerefPointers = "autoderef" in options
1059
        self.partialUpdate = "partial" <