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