# 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)