dumper.py 61.9 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
class SubItem:
hjk's avatar
hjk committed
329
    def __init__(self, d, component):
330
        self.d = d
hjk's avatar
hjk committed
331 332
        self.iname = "%s.%s" % (d.currentIName, component)
        self.name = component
333 334 335

    def __enter__(self):
        self.d.put('{')
hjk's avatar
hjk committed
336 337 338 339
        #if not self.name is None:
        if isinstance(self.name, str):
            self.d.put('name="%s",' % self.name)
        self.savedIName = self.d.currentIName
340 341 342 343 344
        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
345
        self.d.currentIName = self.iname
346
        self.d.currentValuePriority = -100
347 348
        self.d.currentValueEncoding = None
        self.d.currentType = ""
349
        self.d.currentTypePriority = -100
350 351 352 353 354 355

    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)
356
        try:
357
            #warn("TYPE VALUE: %s" % self.d.currentValue)
hjk's avatar
hjk committed
358 359 360 361 362 363 364 365
            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)
366
                    if not typeObj is None:
hjk's avatar
hjk committed
367 368 369 370 371 372
                        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)
373
                self.d.put('value="%s",' % self.d.currentValue)
374 375
        except:
            pass
376
        self.d.put('},')
hjk's avatar
hjk committed
377
        self.d.currentIName = self.savedIName
378 379 380 381 382 383 384
        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
385 386 387 388 389 390 391 392 393 394 395
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
396 397

class Children:
hjk's avatar
hjk committed
398 399
    def __init__(self, d, numChild = 1, childType = None, childNumChild = None,
            maxNumChild = None, addrBase = None, addrStep = None):
400 401 402
        self.d = d
        self.numChild = numChild
        self.childNumChild = childNumChild
hjk's avatar
hjk committed
403 404 405 406
        self.maxNumChild = maxNumChild
        self.addrBase = addrBase
        self.addrStep = addrStep
        self.printsAddress = True
407 408 409
        if childType is None:
            self.childType = None
        else:
hjk's avatar
hjk committed
410 411
            self.childType = stripClassTag(str(childType))
            self.d.put('childtype="%s",' % self.childType)
412 413 414 415 416 417 418 419 420 421
            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
422 423 424 425 426 427 428
        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)
429 430 431 432 433
        #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
434 435 436 437 438 439 440 441
        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
442 443 444 445 446
        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
447 448 449
        if not self.d.currentMaxNumChild is None:
            if self.d.currentMaxNumChild < self.d.currentNumChild:
                self.d.put('{name="<incomplete>",value="",type="",numchild="0"},')
450 451
        self.d.currentChildType = self.savedChildType
        self.d.currentChildNumChild = self.savedChildNumChild
hjk's avatar
hjk committed
452 453 454
        self.d.currentNumChild = self.savedNumChild
        self.d.currentMaxNumChild = self.savedMaxNumChild
        self.d.currentPrintsAddress = self.savedPrintsAddress
455 456 457 458
        self.d.put('],')
        return True


459
def value(expr):
460
    value = parseAndEvaluate(expr)
461 462 463 464 465
    try:
        return int(value)
    except:
        return str(value)

hjk's avatar
hjk committed
466
def isSimpleType(typeobj):
467
    code = typeobj.code
468 469 470 471 472
    return code == BoolCode \
        or code == CharCode \
        or code == IntCode \
        or code == FloatCode \
        or code == EnumCode
hjk's avatar
hjk committed
473 474

def warn(message):
475 476
    if True or verbosity > 0:
        print "XXX: %s\n" % message.encode("latin1")
hjk's avatar
hjk committed
477 478 479 480 481 482
    pass

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

483
def checkRef(ref):
484 485 486 487 488
    count = 0
    if qtMajorVersion() >= 5:
        count = ref["atomic"]["_q_value"]
    else:
        count = ref["_q_value"]
489 490 491
    check(count > 0)
    check(count < 1000000) # assume there aren't a million references to any object

hjk's avatar
hjk committed
492
#def couldBePointer(p, align):
493
#    type = lookupType("unsigned int")
hjk's avatar
hjk committed
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
#    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()

511 512 513 514 515 516
def isAccessible(p):
    try:
        long(p)
        return True
    except:
        return False
hjk's avatar
hjk committed
517 518

def isNull(p):
519 520 521 522
    # 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 ")
523 524 525 526 527
    #try:
    #    # Can fail with: "RuntimeError: Cannot access memory at address 0x5"
    #    return p.cast(lookupType("void").pointer()) == 0
    #except:
    #    return False
528 529 530 531 532
    try:
        # Can fail with: "RuntimeError: Cannot access memory at address 0x5"
        return long(p) == 0
    except:
        return False
hjk's avatar
hjk committed
533 534

movableTypes = set([
hjk's avatar
hjk committed
535 536 537 538 539 540
    "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
541
    "QXmlStreamAttribute", "QXmlStreamNamespaceDeclaration",
hjk's avatar
hjk committed
542 543 544 545 546 547 548 549 550 551 552 553 554
    "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
555 556

def checkPointerRange(p, n):
557
    for i in xrange(n):
hjk's avatar
hjk committed
558 559 560
        checkPointer(p)
        ++p

hjk's avatar
hjk committed
561 562 563 564 565 566 567 568 569 570 571 572 573
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
574
    type = stripClassTag(str(value.type))
575
    if type.find(":") >= 0:
hjk's avatar
hjk committed
576
        type = "'" + type + "'"
577
    # 'class' is needed, see http://sourceware.org/bugzilla/show_bug.cgi?id=11912
hjk's avatar
hjk committed
578
    exp = "((class %s*)%s)->%s(%s)" % (type, value.address, func, arg)
hjk's avatar
hjk committed
579
    #warn("CALL: %s" % exp)
580 581 582 583 584
    result = None
    try:
        result = parseAndEvaluate(exp)
    except:
        pass
hjk's avatar
hjk committed
585 586 587
    #warn("  -> %s" % result)
    return result

hjk's avatar
hjk committed
588 589 590
def call(value, func, *args):
    return call2(value, func, args)

591 592 593 594
def makeValue(type, init):
    type = stripClassTag(type)
    if type.find(":") >= 0:
        type = "'" + type + "'"
hjk's avatar
hjk committed
595
    # Avoid malloc symbol clash with QVector.
596
    gdb.execute("set $d = (%s*)calloc(sizeof(%s), 1)" % (type, type))
597 598 599 600 601 602 603
    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

604 605 606 607 608 609 610 611 612 613 614 615
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


616 617 618 619 620 621 622 623 624 625
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

626 627
qqNs = None

628
def qtNamespace():
629
    # FIXME: This only works when call from inside a Qt function frame.
630
    global qqNs
631 632
    if not qqNs is None:
        return qqNs
633
    try:
634
        str = catchCliOutput("ptype QString::Null")[0]
hjk's avatar
hjk committed
635
        # The result looks like:
636 637 638 639 640
        # "type = const struct myns::QString::Null {"
        # "    <no data fields>"
        # "}"
        pos1 = str.find("struct") + 7
        pos2 = str.find("QString::Null")
641 642 643 644
        if pos1 > -1 and pos2 > -1:
            qqNs = str[pos1:pos2]
            return qqNs
        return ""
645
    except:
646
        return ""
hjk's avatar
hjk committed
647

648 649 650 651 652 653 654 655 656 657
# --  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'
658 659 660 661 662
        v = int(parseAndEvaluate("*(char*)qVersion()"))
        if v >= 51:
            qqMajorVersion = v - 48
            return qqMajorVersion
        return 0
663 664 665
    except:
        return 0

666 667
def findFirstZero(p, maximum):
    for i in xrange(maximum):
668 669 670
        if p.dereference() == 0:
            return i
        p = p + 1
671
    return maximum + 1
672

673
def extractCharArray(p, maxsize):
674
    p = p.cast(lookupType("unsigned char").pointer())
675
    s = ""
676 677 678 679 680 681
    i = 0
    while i < maxsize:
        c = int(p.dereference())
        if c == 0:
            return s
        s += "%c" % c
682
        p += 1
683
        i += 1
684 685 686
    if p.dereference() != 0:
        s += "..."
    return s
687 688 689 690 691 692 693 694 695 696 697

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
698
    return extractCharArray(data, min(100, size))
699

700
def encodeCharArray(p, maxsize, limit = -1):
701
    t = lookupType("unsigned char").pointer()
702
    p = p.cast(t)
703 704
    if limit == -1:
        limit = findFirstZero(p, maxsize)
705
    s = ""
706 707 708 709 710 711 712 713
    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
714
    if limit > maxsize:
715 716 717 718
        s += "2e2e2e"
    return s

def encodeChar2Array(p, maxsize):
719
    t = lookupType("unsigned short").pointer()
720
    p = p.cast(t)
721
    limit = findFirstZero(p, maxsize)
722 723 724 725 726 727 728 729 730
    s = ""
    for i in xrange(limit):
        s += "%04x" % int(p.dereference())
        p += 1
    if i == maxsize:
        s += "2e002e002e00"
    return s

def encodeChar4Array(p, maxsize):
731
    t = lookupType("unsigned int").pointer()
732
    p = p.cast(t)
733
    limit = findFirstZero(p, maxsize)
734 735 736 737
    s = ""
    for i in xrange(limit):
        s += "%08x" % int(p.dereference())
        p += 1
738
    if i > maxsize:
739
        s += "2e0000002e0000002e000000"
740 741
    return s

742 743 744 745 746 747 748 749 750 751 752 753
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']
754
        charPointerType = lookupType('char *')
hjk's avatar
hjk committed
755 756
        data = qByteArrayData['d'].cast(charPointerType) \
             + qByteArrayData['offset'] + charPointerType.sizeof
757 758
        return data, size, alloc

759
def encodeByteArray(value):
760
    data, size, alloc = qByteArrayData(value)
761 762 763 764
    check(0 <= size and size <= alloc and alloc <= 100*1000*1000)
    if size > 0:
        checkAccess(data, 4)
        checkAccess(data + size) == 0
765
    return encodeCharArray(data, 100, size)
766

767 768 769 770
def qQStringData(value):
    if qtMajorVersion() < 5:
        d_ptr = value['d'].dereference()
        checkRef(d_ptr['ref'])
hjk's avatar
hjk committed
771
        return d_ptr['data'], int(d_ptr['size']), int(d_ptr['alloc'])
772 773
    else: # Qt5: Implement the QStringArrayData::data() accessor.
        qStringData = value['d'].dereference()
774
        ushortPointerType = lookupType('ushort *')
hjk's avatar
hjk committed
775 776 777
        data = qStringData['d'].cast(ushortPointerType) \
            + ushortPointerType.sizeof / 2 + qStringData['offset']
        return data, int(qStringData['size']), int(qStringData['alloc'])
778

779
def encodeString(value):
780 781
    data, size, alloc = qQStringData(value)

782 783 784
    check(0 <= size and size <= alloc and alloc <= 100*1000*1000)
    if size > 0:
        checkAccess(data, 4)
hjk's avatar
hjk committed
785
        checkAccess(data + size) == 0
786
    s = ""
787
    limit = min(size, 1000)
788 789 790
    try:
        # gdb.Inferior is new in gdb 7.2
        inferior = gdb.inferiors()[0]
791
        s = binascii.hexlify(inferior.read_memory(data, 2 * limit))
792
    except:
793
        p = data
794
        for i in xrange(limit):
795 796 797 798
            val = int(p.dereference())
            s += "%02x" % (val % 256)
            s += "%02x" % (val / 256)
            p += 1
799 800
    if limit < size:
        s += "2e002e002e00"
801 802
    return s

hjk's avatar
hjk committed
803 804
def stripTypedefs(type):
    type = type.unqualified()
hjk's avatar
hjk committed
805
    while type.code == TypedefCode:
806 807 808
        type = type.strip_typedefs().unqualified()
    return type

hjk's avatar
hjk committed
809 810
def extractFields(type):
    # Insufficient, see http://sourceware.org/bugzilla/show_bug.cgi?id=10953:
hjk's avatar
hjk committed
811
    #fields = type.fields()
hjk's avatar
hjk committed
812
    # Insufficient, see http://sourceware.org/bugzilla/show_bug.cgi?id=11777:
hjk's avatar
hjk committed
813
    #fields = defsype).fields()
hjk's avatar
hjk committed
814
    # This seems to work.
hjk's avatar
hjk committed
815
    #warn("TYPE 0: %s" % type)
hjk's avatar
hjk committed
816
    type = stripTypedefs(type)
817 818 819
    fields = type.fields()
    if len(fields):
        return fields
hjk's avatar
hjk committed
820
    #warn("TYPE 1: %s" % type)
821
    # This fails for arrays. See comment in lookupType.
822 823 824
    type0 = lookupType(str(type))
    if not type0 is None:
        type = type0
hjk's avatar
hjk committed
825
    if type.code == FunctionCode:
826
        return []
hjk's avatar
hjk committed
827 828
    #warn("TYPE 2: %s" % type)
    fields = type.fields()
hjk's avatar
hjk committed
829
    #warn("FIELDS: %s" % fields)
hjk's avatar
hjk committed
830 831
    return fields

hjk's avatar
hjk committed
832 833
#######################################################################
#
834
# LocalItem
hjk's avatar
hjk committed
835 836 837
#
#######################################################################

838
# Contains iname, name, and value.
hjk's avatar
hjk committed
839
class LocalItem:
840
    pass
hjk's avatar
hjk committed
841

842 843 844 845 846 847
#######################################################################
#
# SetupCommand
#
#######################################################################

848
# This is a cache mapping from 'type name' to 'display alternatives'.
849
qqFormats = {}
850 851

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

854 855 856
# This is a cache of all dumpers that support writing.
qqEditable = {}

857 858 859 860 861
# 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
862
qqQObjectCache = {}
863

864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
# 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

887
def bbsetup(args):
hjk's avatar
hjk committed
888 889
    typeInfoCache = {}
    typeCache = {}
890 891 892 893 894
    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
895
            qqFormats[name] = qqFormats.get(name, "")
896 897 898 899 900 901 902 903
        elif key.startswith("qform__"):
            name = key[7:]
            formats = ""
            try:
                formats = value()
            except:
                pass
            qqFormats[name] = formats
904 905 906 907 908 909
        elif key.startswith("qedit__"):
            name = key[7:]
            try:
                qqEditable[name] = value
            except:
                pass
910
    result = "dumpers=["
911
    #qqNs = qtNamespace() # This is too early
912
    for key, value in qqFormats.items():
913 914 915 916
        if qqEditable.has_key(key):
            result += '{type="%s",formats="%s",editable="true"},' % (key, value)
        else:
            result += '{type="%s",formats="%s"},' % (key, value)
917 918
    result += ']'
    #result += ',namespace="%s"' % qqNs
919
    result += ',hasInferiorThreadList="%s"' % int(hasInferiorThreadList())
920 921
    return result

922 923
registerCommand("bbsetup", bbsetup)

924

925 926 927 928 929 930 931
#######################################################################
#
# Edit Command
#
#######################################################################

def bbedit(args):
932 933
    (type, expr, value) = args.split(",")
    type = base64.b16decode(type, True)
934
    ns = qtNamespace()
935 936
    if type.startswith(ns):
        type = type[len(ns):]
937
    type = type.replace("::", "__")
938 939 940 941 942 943
    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))
944 945
    if qqEditable.has_key(type):
        qqEditable[type](expr, value)
946 947
    else:
        gdb.execute("set (%s)=%s" % (expr, value))
948

949
registerCommand("bbedit", bbedit)
950 951


hjk's avatar
hjk committed
952 953
#######################################################################
#
954
# Frame Command
hjk's avatar
hjk committed
955 956 957
#
#######################################################################

958
def bb(args):
hjk's avatar
hjk committed
959
    output = 'data=[' + "".join(Dumper(args).output) + '],typeinfo=['
960 961 962 963 964 965 966
    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
967

968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985
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
986 987 988 989 990 991 992

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

993

hjk's avatar
hjk committed
994
class Dumper:
995
    def __init__(self, args):
hjk's avatar
hjk committed
996
        self.output = []
hjk's avatar
hjk committed
997 998
        self.currentIName = ""
        self.currentPrintsAddress = True
999 1000
        self.currentChildType = ""
        self.currentChildNumChild = -1
hjk's avatar
hjk committed
1001 1002
        self.currentMaxNumChild = -1
        self.currentNumChild = -1
1003 1004 1005 1006 1007
        self.currentValue = None
        self.currentValuePriority = -100
        self.currentValueEncoding = None
        self.currentType = None
        self.currentTypePriority = -100
1008 1009 1010 1011 1012 1013 1014 1015
        self.typeformats = {}
        self.formats = {}
        self.expandedINames = ""

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

1016
        resultVarName = ""
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041
        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)

1042
        self.useDynamicType = "dyntype" in options
1043 1044 1045
        self.useFancy = "fancy" in options
        self.passExceptions = "pe" in options
        self.autoDerefPointers = "autoderef" in options
1046
        self.partialUpdate = "partial" in options
1047 1048
        self.tooltipOnly = "tooltiponly" in options
        self.noLocals = "nolocals" in options
1049
        self.ns = qtNamespace()
1050 1051 1052