# Lint as: python2, python3
# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# usage: python hprofdump.py FILE
#   Dumps a binary heap dump file to text, to facilitate debugging of heap
#   dumps and heap dump viewers.

import time

import struct
import sys

filename = sys.argv[1]
hprof = open(filename, "rb")

def readu1(hprof):
    return struct.unpack('!B', hprof.read(1))[0]

def readu2(hprof):
    return struct.unpack('!H', hprof.read(2))[0]

def readu4(hprof):
    return struct.unpack('!I', hprof.read(4))[0]

def readu8(hprof):
    return struct.unpack('!Q', hprof.read(8))[0]

def readN(n, hprof):
    if n == 1:
        return readu1(hprof)
    if n == 2:
        return readu2(hprof)
    if n == 4:
        return readu4(hprof)
    if n == 8:
        return readu8(hprof)
    raise Exception("Unsupported size of readN: %d" % n)

TY_OBJECT = 2
TY_BOOLEAN = 4
TY_CHAR = 5
TY_FLOAT = 6
TY_DOUBLE = 7
TY_BYTE = 8
TY_SHORT = 9
TY_INT = 10
TY_LONG = 11

def showty(ty):
    if ty == TY_OBJECT:
        return "Object"
    if ty == TY_BOOLEAN:
        return "boolean"
    if ty == TY_CHAR:
        return "char"
    if ty == TY_FLOAT:
        return "float"
    if ty == TY_DOUBLE:
        return "double"
    if ty == TY_BYTE:
        return "byte"
    if ty == TY_SHORT:
        return "short"
    if ty == TY_INT:
        return "int"
    if ty == TY_LONG:
        return "long"
    raise Exception("Unsupported type %d" % ty)

strs = {}

def showstr(id):
    if id in strs:
        return strs[id]
    return "STR[@%x]" % id

loaded = {}

def showloaded(serial):
    if serial in loaded:
        return showstr(loaded[serial])
    return "SERIAL[@%x]" % serial

classobjs = {}

def showclassobj(id):
    if id in classobjs:
        return "%s @%x" % (showstr(classobjs[id]), id)
    return "@%x" % id

version = ""
c = hprof.read(1)
while (c != b'\0'):
    version += c.decode('ascii')
    c = hprof.read(1)
print("Version: %s" % version)

idsize = readu4(hprof)
print("ID Size: %d bytes" % idsize)

def readID(hprof):
    return readN(idsize, hprof)

def valsize(ty):
    if ty == TY_OBJECT:
        return idsize
    if ty == TY_BOOLEAN:
        return 1
    if ty == TY_CHAR:
        return 2
    if ty == TY_FLOAT:
        return 4
    if ty == TY_DOUBLE:
        return 8
    if ty == TY_BYTE:
        return 1
    if ty == TY_SHORT:
        return 2
    if ty == TY_INT:
        return 4
    if ty == TY_LONG:
        return 8
    raise Exception("Unsupported type %d" % ty)

def readval(ty, hprof):
    return readN(valsize(ty), hprof)

timestamp = (readu4(hprof) << 32) | readu4(hprof)
s, ms = divmod(timestamp, 1000)
print("Date: %s.%03d" % (time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(s)), ms))

while hprof.read(1):
    hprof.seek(-1, 1)
    pos = hprof.tell()
    tag = readu1(hprof)
    time = readu4(hprof)
    length = readu4(hprof)
    if tag == 0x01:
        id = readID(hprof)
        string = hprof.read(length - idsize)
        print("%d: STRING %x %s" % (pos, id, repr(string)))
        strs[id] = string.decode('latin-1')
    elif tag == 0x02:
        serial = readu4(hprof)
        classobj = readID(hprof)
        stack = readu4(hprof)
        classname = readID(hprof)
        loaded[serial] = classname
        classobjs[classobj] = classname
        print("LOAD CLASS #%d %s @%x stack=@%x" % (serial, showstr(classname), classobj, stack))
    elif tag == 0x04:
        id = readID(hprof)
        method = readID(hprof)
        sig = readID(hprof)
        file = readID(hprof)
        serial = readu4(hprof)
        line = readu4(hprof)
        print("STACK FRAME %d '%s' '%s' '%s' line=%d classserial=%d" % (
        id, showstr(method), showstr(sig), showstr(file), line, serial))
    elif tag == 0x05:
        serial = readu4(hprof)
        print("STACK TRACE %d" % serial)
        thread = readu4(hprof)
        frames = readu4(hprof)
        hprof.read(idsize * frames)
    elif tag == 0x06:
        print("ALLOC SITES")
        flags = readu2(hprof)
        cutoff_ratio = readu4(hprof)
        live_bytes = readu4(hprof)
        live_insts = readu4(hprof)
        alloc_bytes = readu8(hprof)
        alloc_insts = readu8(hprof)
        numsites = readu4(hprof)
        while numsites > 0:
            indicator = readu1(hprof)
            class_serial = readu4(hprof)
            stack = readu4(hprof)
            live_bytes = readu4(hprof)
            live_insts = readu4(hprof)
            alloc_bytes = readu4(hprof)
            alloc_insts = readu4(hprof)
            numsites -= 1
    elif tag == 0x0A:
        thread = readu4(hprof)
        object = readID(hprof)
        stack = readu4(hprof)
        name = readID(hprof)
        group_name = readID(hprof)
        pgroup_name = readID(hprof)
        print("START THREAD serial=%d" % thread)
    elif tag == 0x0B:
        thread = readu4(hprof)
        print("END THREAD")
    elif tag == 0x0C or tag == 0x1C:
        if tag == 0x0C:
            print("HEAP DUMP")
        else:
            print("HEAP DUMP SEGMENT")

        while (length > 0):
            subtag = readu1(hprof);
            length -= 1
            if subtag == 0xFF:
                print(" ROOT UNKNOWN")
                objid = readID(hprof);
                length -= idsize
            elif subtag == 0x01:
                print(" ROOT JNI GLOBAL")
                objid = readID(hprof);
                length -= idsize
                ref = readID(hprof);
                length -= idsize
            elif subtag == 0x02:
                print(" ROOT JNI LOCAL")
                objid = readID(hprof);
                length -= idsize
                thread = readu4(hprof);
                length -= 4
                frame = readu4(hprof);
                length -= 4
            elif subtag == 0x03:
                print(" ROOT JAVA FRAME")
                objid = readID(hprof);
                length -= idsize
                serial = readu4(hprof);
                length -= 4
                frame = readu4(hprof);
                length -= 4
            elif subtag == 0x04:
                objid = readID(hprof);
                length -= idsize
                serial = readu4(hprof);
                length -= 4
                print(" ROOT NATIVE STACK serial=%d" % serial)
            elif subtag == 0x05:
                print(" ROOT STICKY CLASS")
                objid = readID(hprof);
                length -= idsize
            elif subtag == 0x06:
                print(" ROOT THREAD BLOCK")
                objid = readID(hprof);
                length -= idsize
                thread = readu4(hprof);
                length -= 4
            elif subtag == 0x07:
                print(" ROOT MONITOR USED")
                objid = readID(hprof);
                length -= idsize
            elif subtag == 0x08:
                threadid = readID(hprof);
                length -= idsize
                serial = readu4(hprof);
                length -= 4
                stack = readu4(hprof);
                length -= 4
                print(" ROOT THREAD OBJECT threadid=@%x serial=%d" % (threadid, serial))
            elif subtag == 0x20:
                print(" CLASS DUMP")
                print("  class class object ID: %s" % showclassobj(readID(hprof)));
                length -= idsize
                print("  stack trace serial number: #%d" % readu4(hprof));
                length -= 4
                print("  super class object ID: @%x" % readID(hprof));
                length -= idsize
                print("  class loader object ID: @%x" % readID(hprof));
                length -= idsize
                print("  signers object ID: @%x" % readID(hprof));
                length -= idsize
                print("  protection domain object ID: @%x" % readID(hprof));
                length -= idsize
                print("  reserved: @%x" % readID(hprof));
                length -= idsize
                print("  reserved: @%x" % readID(hprof));
                length -= idsize
                print("  instance size (in bytes): %d" % readu4(hprof));
                length -= 4
                print("  constant pool:")
                poolsize = readu2(hprof);
                length -= 2
                while poolsize > 0:
                    poolsize -= 1
                    idx = readu2(hprof);
                    length -= 2
                    ty = readu1(hprof);
                    length -= 1
                    val = readval(ty, hprof);
                    length -= valsize(ty)
                    print("   %d %s 0x%x" % (idx, showty(ty), val))
                numstatic = readu2(hprof);
                length -= 2
                print("  static fields:")
                while numstatic > 0:
                    numstatic -= 1
                    nameid = readID(hprof);
                    length -= idsize
                    ty = readu1(hprof);
                    length -= 1
                    val = readval(ty, hprof);
                    length -= valsize(ty)
                    print("   %s %s 0x%x" % (showstr(nameid), showty(ty), val))
                numinst = readu2(hprof);
                length -= 2
                print("  instance fields:")
                while numinst > 0:
                    numinst -= 1
                    nameid = readID(hprof);
                    length -= idsize
                    ty = readu1(hprof);
                    length -= 1
                    print("   %s %s" % (showstr(nameid), showty(ty)))
            elif subtag == 0x21:
                print(" INSTANCE DUMP:")
                print("  object ID: @%x" % readID(hprof));
                length -= idsize
                stack = readu4(hprof);
                length -= 4
                print("  stack: %s" % stack)
                print("  class object ID: %s" % showclassobj(readID(hprof)));
                length -= idsize
                datalen = readu4(hprof);
                length -= 4
                print("  %d bytes of instance data" % datalen)
                data = hprof.read(datalen);
                length -= datalen
            elif subtag == 0x22:
                print(" OBJECT ARRAY DUMP:")
                print("  array object ID: @%x" % readID(hprof));
                length -= idsize
                stack = readu4(hprof);
                length -= 4
                print("  stack: %s" % stack)
                count = readu4(hprof);
                length -= 4
                print("  array class object ID: %s" % showclassobj(readID(hprof)));
                length -= idsize
                hprof.read(idsize * count);
                length -= (idsize * count)
            elif subtag == 0x23:
                print(" PRIMITIVE ARRAY DUMP:")
                print("  array object ID: @%x" % readID(hprof));
                length -= idsize
                stack = readu4(hprof);
                length -= 4
                count = readu4(hprof);
                length -= 4
                ty = readu1(hprof);
                length -= 1
                hprof.read(valsize(ty) * count);
                length -= (valsize(ty) * count)
            elif subtag == 0x89:
                print(" HPROF_ROOT_INTERNED_STRING")
                objid = readID(hprof);
                length -= idsize
            elif subtag == 0x8b:
                objid = readID(hprof);
                length -= idsize
                print(" HPROF ROOT DEBUGGER @%x (at offset %d)" % (objid, hprof.tell() - (idsize + 1)))
            elif subtag == 0x8d:
                objid = readID(hprof);
                length -= idsize
                print(" HPROF ROOT VM INTERNAL @%x" % objid)
            elif subtag == 0xfe:
                hty = readu4(hprof);
                length -= 4
                hnameid = readID(hprof);
                length -= idsize
                print(" HPROF_HEAP_DUMP_INFO %s" % showstr(hnameid))
            else:
                raise Exception("TODO: subtag %x" % subtag)
    elif tag == 0x0E:
        flags = readu4(hprof)
        depth = readu2(hprof)
        print("CONTROL SETTINGS %x %d" % (flags, depth))
    elif tag == 0x2C:
        print("HEAP DUMP END")
    else:
        raise Exception("TODO: TAG %x" % tag)
