1# Copyright (C) 2018 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15# usage: python hprofdump.py FILE
16#   Dumps a binary heap dump file to text, to facilitate debugging of heap
17#   dumps and heap dump viewers.
18
19import time
20import struct
21import sys
22
23filename = sys.argv[1]
24hprof = open(filename, "rb")
25
26def readu1(hprof):
27  return struct.unpack('!B', hprof.read(1))[0]
28
29def readu2(hprof):
30  return struct.unpack('!H', hprof.read(2))[0]
31
32def readu4(hprof):
33  return struct.unpack('!I', hprof.read(4))[0]
34
35def readu8(hprof):
36  return struct.unpack('!Q', hprof.read(8))[0]
37
38def readN(n, hprof):
39  if n == 1:
40    return readu1(hprof)
41  if n == 2:
42    return readu2(hprof)
43  if n == 4:
44    return readu4(hprof)
45  if n == 8:
46    return readu8(hprof)
47  raise Exception("Unsupported size of readN: %d" % n)
48
49TY_OBJECT = 2
50TY_BOOLEAN = 4
51TY_CHAR = 5
52TY_FLOAT = 6
53TY_DOUBLE = 7
54TY_BYTE = 8
55TY_SHORT = 9
56TY_INT = 10
57TY_LONG = 11
58
59def showty(ty):
60  if ty == TY_OBJECT:
61    return "Object"
62  if ty == TY_BOOLEAN:
63    return "boolean"
64  if ty == TY_CHAR:
65    return "char"
66  if ty == TY_FLOAT:
67    return "float"
68  if ty == TY_DOUBLE:
69    return "double"
70  if ty == TY_BYTE:
71    return "byte"
72  if ty == TY_SHORT:
73    return "short"
74  if ty == TY_INT:
75    return "int"
76  if ty == TY_LONG:
77    return "long"
78  raise Exception("Unsupported type %d" % ty)
79
80strs = { }
81def showstr(id):
82  if id in strs:
83    return strs[id]
84  return "STR[@%x]" % id
85
86loaded = { }
87def showloaded(serial):
88  if serial in loaded:
89    return showstr(loaded[serial])
90  return "SERIAL[@%x]" % serial
91
92classobjs = { }
93def showclassobj(id):
94  if id in classobjs:
95    return "%s @%x" % (showstr(classobjs[id]), id)
96  return "@%x" % id
97
98
99# [u1]* An initial NULL terminate series of bytes representing the format name
100# and version.
101version = ""
102c = hprof.read(1)
103while (c != '\0'):
104  version += c
105  c = hprof.read(1)
106print "Version: %s" % version
107
108# [u4] size of identifiers.
109idsize = readu4(hprof)
110print "ID Size: %d bytes" % idsize
111def readID(hprof):
112  return readN(idsize, hprof)
113
114def valsize(ty):
115  if ty == TY_OBJECT:
116    return idsize
117  if ty == TY_BOOLEAN:
118    return 1
119  if ty == TY_CHAR:
120    return 2
121  if ty == TY_FLOAT:
122    return 4
123  if ty == TY_DOUBLE:
124    return 8
125  if ty == TY_BYTE:
126    return 1
127  if ty == TY_SHORT:
128    return 2
129  if ty == TY_INT:
130    return 4
131  if ty == TY_LONG:
132    return 8
133  raise Exception("Unsupported type %d" % ty)
134
135def readval(ty, hprof):
136  return readN(valsize(ty), hprof)
137
138# [u4] high word of number of ms since 0:00 GMT, 1/1/70
139# [u4] low word of number of ms since 0:00 GMT, 1/1/70
140timestamp = (readu4(hprof) << 32) | readu4(hprof)
141s, ms = divmod(timestamp, 1000)
142print "Date: %s.%03d" % (time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(s)), ms)
143
144while hprof.read(1):
145  hprof.seek(-1,1)
146  pos = hprof.tell()
147  tag = readu1(hprof)
148  time = readu4(hprof)
149  length = readu4(hprof)
150  if tag == 0x01:
151    id = readID(hprof)
152    string = hprof.read(length - idsize)
153    print "%d: STRING %x %s" % (pos, id, repr(string))
154    strs[id] = string
155  elif tag == 0x02:
156    serial = readu4(hprof)
157    classobj = readID(hprof)
158    stack = readu4(hprof)
159    classname = readID(hprof)
160    loaded[serial] = classname
161    classobjs[classobj] = classname
162    print "LOAD CLASS #%d %s @%x stack=@%x" % (serial, showstr(classname), classobj, stack)
163  elif tag == 0x04:
164    id = readID(hprof)
165    method = readID(hprof)
166    sig = readID(hprof)
167    file = readID(hprof)
168    serial = readu4(hprof)
169    line = readu4(hprof);
170    print "STACK FRAME %d '%s' '%s' '%s' line=%d classserial=%d" % (id, showstr(method), showstr(sig), showstr(file), line, serial)
171  elif tag == 0x05:
172    serial = readu4(hprof)
173    print "STACK TRACE %d" % serial
174    thread = readu4(hprof)
175    frames = readu4(hprof)
176    hprof.read(idsize * frames)
177  elif tag == 0x06:
178    print "ALLOC SITES"
179    flags = readu2(hprof)
180    cutoff_ratio = readu4(hprof)
181    live_bytes = readu4(hprof)
182    live_insts = readu4(hprof)
183    alloc_bytes = readu8(hprof)
184    alloc_insts = readu8(hprof)
185    numsites = readu4(hprof)
186    while numsites > 0:
187      indicator = readu1(hprof)
188      class_serial = readu4(hprof)
189      stack = readu4(hprof)
190      live_bytes = readu4(hprof)
191      live_insts = readu4(hprof)
192      alloc_bytes = readu4(hprof)
193      alloc_insts = readu4(hprof)
194      numsites -= 1
195  elif tag == 0x0A:
196    thread = readu4(hprof)
197    object = readID(hprof)
198    stack = readu4(hprof)
199    name = readID(hprof)
200    group_name = readID(hprof)
201    pgroup_name = readID(hprof)
202    print "START THREAD serial=%d" % thread
203  elif tag == 0x0B:
204    thread = readu4(hprof)
205    print "END THREAD"
206  elif tag == 0x0C or tag == 0x1C:
207    if tag == 0x0C:
208      print "HEAP DUMP"
209    else:
210      print "HEAP DUMP SEGMENT"
211
212    while (length > 0):
213      subtag = readu1(hprof) ; length -= 1
214      if subtag == 0xFF:
215        print " ROOT UNKNOWN"
216        objid = readID(hprof) ; length -= idsize
217      elif subtag == 0x01:
218        print " ROOT JNI GLOBAL"
219        objid = readID(hprof) ; length -= idsize
220        ref = readID(hprof) ; length -= idsize
221      elif subtag == 0x02:
222        print " ROOT JNI LOCAL"
223        objid = readID(hprof) ; length -= idsize
224        thread = readu4(hprof) ; length -= 4
225        frame = readu4(hprof) ; length -= 4
226      elif subtag == 0x03:
227        print " ROOT JAVA FRAME"
228        objid = readID(hprof) ; length -= idsize
229        serial = readu4(hprof) ; length -= 4
230        frame = readu4(hprof) ; length -= 4
231      elif subtag == 0x04:
232        objid = readID(hprof) ; length -= idsize
233        serial = readu4(hprof) ; length -= 4
234        print " ROOT NATIVE STACK serial=%d" % serial
235      elif subtag == 0x05:
236        print " ROOT STICKY CLASS"
237        objid = readID(hprof) ; length -= idsize
238      elif subtag == 0x06:
239        print " ROOT THREAD BLOCK"
240        objid = readID(hprof) ; length -= idsize
241        thread = readu4(hprof) ; length -= 4
242      elif subtag == 0x07:
243        print " ROOT MONITOR USED"
244        objid = readID(hprof) ; length -= idsize
245      elif subtag == 0x08:
246        threadid = readID(hprof) ; length -= idsize
247        serial = readu4(hprof) ; length -= 4
248        stack = readu4(hprof) ; length -= 4
249        print " ROOT THREAD OBJECT threadid=@%x serial=%d" % (threadid, serial)
250      elif subtag == 0x20:
251        print " CLASS DUMP"
252        print "  class class object ID: %s" % showclassobj(readID(hprof)) ; length -= idsize
253        print "  stack trace serial number: #%d" % readu4(hprof) ; length -= 4
254        print "  super class object ID: @%x" % readID(hprof) ; length -= idsize
255        print "  class loader object ID: @%x" % readID(hprof) ; length -= idsize
256        print "  signers object ID: @%x" % readID(hprof) ; length -= idsize
257        print "  protection domain object ID: @%x" % readID(hprof) ; length -= idsize
258        print "  reserved: @%x" % readID(hprof) ; length -= idsize
259        print "  reserved: @%x" % readID(hprof) ; length -= idsize
260        print "  instance size (in bytes): %d" % readu4(hprof) ; length -= 4
261        print "  constant pool:"
262        poolsize = readu2(hprof) ; length -= 2
263        while poolsize > 0:
264          poolsize -= 1
265          idx = readu2(hprof) ; length -= 2
266          ty = readu1(hprof) ; length -= 1
267          val = readval(ty, hprof) ; length -= valsize(ty)
268          print "   %d %s 0x%x" % (idx, showty(ty), val)
269        numstatic = readu2(hprof) ; length -= 2
270        print "  static fields:"
271        while numstatic > 0:
272          numstatic -= 1
273          nameid = readID(hprof) ; length -= idsize
274          ty = readu1(hprof) ; length -= 1
275          val = readval(ty, hprof) ; length -= valsize(ty)
276          print "   %s %s 0x%x" % (showstr(nameid), showty(ty), val)
277        numinst = readu2(hprof) ; length -= 2
278        print "  instance fields:"
279        while numinst > 0:
280          numinst -= 1
281          nameid = readID(hprof) ; length -= idsize
282          ty = readu1(hprof) ; length -= 1
283          print "   %s %s" % (showstr(nameid), showty(ty))
284      elif subtag == 0x21:
285        print " INSTANCE DUMP:"
286        print "  object ID: @%x" % readID(hprof) ; length -= idsize
287        stack = readu4(hprof) ; length -= 4
288        print "  stack: %s" % stack
289        print "  class object ID: %s" % showclassobj(readID(hprof)) ; length -= idsize
290        datalen = readu4(hprof) ; length -= 4
291        print "  %d bytes of instance data" % datalen
292        data = hprof.read(datalen) ; length -= datalen
293      elif subtag == 0x22:
294        print " OBJECT ARRAY DUMP:"
295        print "  array object ID: @%x" % readID(hprof) ; length -= idsize
296        stack = readu4(hprof) ; length -= 4
297        print "  stack: %s" % stack
298        count = readu4(hprof) ; length -= 4
299        print "  array class object ID: %s" % showclassobj(readID(hprof)) ; length -= idsize
300        hprof.read(idsize * count) ; length -= (idsize * count)
301      elif subtag == 0x23:
302        print " PRIMITIVE ARRAY DUMP:"
303        print "  array object ID: @%x" % readID(hprof) ; length -= idsize
304        stack = readu4(hprof) ; length -= 4
305        count = readu4(hprof) ; length -= 4
306        ty = readu1(hprof) ; length -= 1
307        hprof.read(valsize(ty)*count) ; length -= (valsize(ty)*count)
308      elif subtag == 0x89:
309        print " HPROF_ROOT_INTERNED_STRING"
310        objid = readID(hprof) ; length -= idsize
311      elif subtag == 0x8b:
312        objid = readID(hprof) ; length -= idsize
313        print " HPROF ROOT DEBUGGER @%x (at offset %d)" % (objid, hprof.tell() - (idsize + 1))
314      elif subtag == 0x8d:
315        objid = readID(hprof) ; length -= idsize
316        print " HPROF ROOT VM INTERNAL @%x" % objid
317      elif subtag == 0xfe:
318        hty = readu4(hprof) ; length -= 4
319        hnameid = readID(hprof) ; length -= idsize
320        print " HPROF_HEAP_DUMP_INFO %s" % showstr(hnameid)
321      else:
322        raise Exception("TODO: subtag %x" % subtag)
323  elif tag == 0x0E:
324    flags = readu4(hprof)
325    depth = readu2(hprof)
326    print "CONTROL SETTINGS %x %d" % (flags, depth)
327  elif tag == 0x2C:
328    print "HEAP DUMP END"
329  else:
330    raise Exception("TODO: TAG %x" % tag)
331
332