# 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


# [u1]* An initial NULL terminate series of bytes representing the format name
# and version.
version = ""
c = hprof.read(1)
while (c != '\0'):
  version += c
  c = hprof.read(1)
print "Version: %s" % version

# [u4] size of identifiers.
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)

# [u4] high word of number of ms since 0:00 GMT, 1/1/70
# [u4] low word of number of ms since 0:00 GMT, 1/1/70
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
  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)

