dumper.py 60.8 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 163 164 165 166 167
    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

    #warn(" RESULT '%s': %s" % (typestring, type))
    typeCache[typestring] = type
    return None


168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
    ts = typestring
    while True:
        #WARN("ts: '%s'" % ts)
        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
194 195 196
        if not type is None:
            type = type.pointer()
            typeCache[typestring] = type
197 198 199 200 201 202 203
            return type

    try:
        #warn("LOOKING UP '%s'" % ts)
        type = gdb.lookup_type(ts)
    except RuntimeError, error:
        #warn("LOOKING UP '%s': %s" % (ts, error))
204 205 206 207 208
        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)"))
209 210 211 212 213 214 215 216 217
        # 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)
218
        pass
219 220 221

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

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

233 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
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):
263 264 265 266 267 268
    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<...>"
269
        return lookupType(extractTemplateArgument(type.strip_typedefs(), position))
270

271

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

281

282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
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
301
        self.d.output = []
302 303 304 305 306 307 308

    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
309 310
            self.savedOutput.extend(self.d.output)
            self.d.output = self.savedOutput
311 312 313
        return False


314 315 316 317 318
class NoAddress:
    def __init__(self, d):
        self.d = d

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

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


326
class SubItem:
hjk's avatar
hjk committed
327
    def __init__(self, d, component):
328
        self.d = d
hjk's avatar
hjk committed
329 330
        self.iname = "%s.%s" % (d.currentIName, component)
        self.name = component
331 332 333

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

624 625
qqNs = None

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

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

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

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

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

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

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

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

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

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

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

777
def encodeString(value):
778 779
    data, size, alloc = qQStringData(value)

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

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

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

hjk's avatar
hjk committed
830 831
#######################################################################
#
832
# LocalItem
hjk's avatar
hjk committed
833 834 835
#
#######################################################################

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

840 841 842 843 844 845
#######################################################################
#
# SetupCommand
#
#######################################################################

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

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

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

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

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

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

918 919
registerCommand("bbsetup", bbsetup)

920

921 922 923 924 925 926 927
#######################################################################
#
# Edit Command
#
#######################################################################

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

945
registerCommand("bbedit", bbedit)
946 947


hjk's avatar
hjk committed
948 949
#######################################################################
#
950
# Frame Command
hjk's avatar
hjk committed
951 952 953
#
#######################################################################

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

964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981
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
982 983 984 985 986 987 988

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

989

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

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

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

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

        #warn("NAMESPACE: '%s'" % self.ns)
        #warn("VARIABLES: %s" % varList)
        #warn("EXPANDED INAMES: %s" % self.expandedINames)
1050
        #warn("WATCHERS: %s" % watchers)
1051 1052
        #warn("PARTIAL: %s" % self.partialUpdate)
        #warn("NO LOCALS: %s" % self.noLocals)
1053 1054 1055 1056 1057
        module = sys.modules[__name__]

        #
        # Locals
        #
1058
        locals = []
1059
        fullUpdateNeeded = True