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