1 /*
  2  * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 
 25 /*
 26  * The Original Code is HAT. The Initial Developer of the
 27  * Original Code is Bill Foote, with contributions from others
 28  * at JavaSoft/Sun.
 29  */
 30 
 31 package jdk.test.lib.hprof.parser;
 32 
 33 import java.io.*;
 34 import java.util.Date;
 35 import java.util.Hashtable;
 36 import java.util.Map;
 37 import jdk.test.lib.hprof.model.ArrayTypeCodes;
 38 import jdk.test.lib.hprof.model.*;
 39 
 40 /**
 41  * Object that's used to read a hprof file.
 42  *
 43  * @author      Bill Foote
 44  */
 45 
 46 public class HprofReader extends Reader /* imports */ implements ArrayTypeCodes {
 47 
 48     final static int MAGIC_NUMBER = 0x4a415641;
 49     // That's "JAVA", the first part of "JAVA PROFILE ..."
 50     private final static String[] VERSIONS = {
 51             " PROFILE 1.0\0",
 52             " PROFILE 1.0.1\0",
 53             " PROFILE 1.0.2\0",
 54     };
 55 
 56     private final static int VERSION_JDK12BETA3 = 0;
 57     private final static int VERSION_JDK12BETA4 = 1;
 58     private final static int VERSION_JDK6       = 2;
 59     // These version numbers are indices into VERSIONS.  The instance data
 60     // member version is set to one of these, and it drives decisions when
 61     // reading the file.
 62     //
 63     // Version 1.0.1 added HPROF_GC_PRIM_ARRAY_DUMP, which requires no
 64     // version-sensitive parsing.
 65     //
 66     // Version 1.0.1 changed the type of a constant pool entry from a signature
 67     // to a typecode.
 68     //
 69     // Version 1.0.2 added HPROF_HEAP_DUMP_SEGMENT and HPROF_HEAP_DUMP_END
 70     // to allow a large heap to be dumped as a sequence of heap dump segments.
 71     //
 72     // The HPROF agent in J2SE 1.2 through to 5.0 generate a version 1.0.1
 73     // file. In Java SE 6.0 the version is either 1.0.1 or 1.0.2 depending on
 74     // the size of the heap (normally it will be 1.0.1 but for multi-GB
 75     // heaps the heap dump will not fit in a HPROF_HEAP_DUMP record so the
 76     // dump is generated as version 1.0.2).
 77 
 78     //
 79     // Record types:
 80     //
 81     static final int HPROF_UTF8          = 0x01;
 82     static final int HPROF_LOAD_CLASS    = 0x02;
 83     static final int HPROF_UNLOAD_CLASS  = 0x03;
 84     static final int HPROF_FRAME         = 0x04;
 85     static final int HPROF_TRACE         = 0x05;
 86     static final int HPROF_ALLOC_SITES   = 0x06;
 87     static final int HPROF_HEAP_SUMMARY  = 0x07;
 88 
 89     static final int HPROF_START_THREAD  = 0x0a;
 90     static final int HPROF_END_THREAD    = 0x0b;
 91 
 92     static final int HPROF_HEAP_DUMP     = 0x0c;
 93 
 94     static final int HPROF_CPU_SAMPLES   = 0x0d;
 95     static final int HPROF_CONTROL_SETTINGS = 0x0e;
 96     static final int HPROF_LOCKSTATS_WAIT_TIME = 0x10;
 97     static final int HPROF_LOCKSTATS_HOLD_TIME = 0x11;
 98 
 99     static final int HPROF_FLAT_ARRAYS          = 0x12;
100     static final int HPROF_INLINED_FIELDS       = 0x13;
101 
102     static final int HPROF_FLAT_ARRAY           = 0x01;
103     static final int HPROF_CLASS_WITH_INLINED_FIELDS = 0x01;
104 
105     static final int HPROF_GC_ROOT_UNKNOWN       = 0xff;
106     static final int HPROF_GC_ROOT_JNI_GLOBAL    = 0x01;
107     static final int HPROF_GC_ROOT_JNI_LOCAL     = 0x02;
108     static final int HPROF_GC_ROOT_JAVA_FRAME    = 0x03;
109     static final int HPROF_GC_ROOT_NATIVE_STACK  = 0x04;
110     static final int HPROF_GC_ROOT_STICKY_CLASS  = 0x05;
111     static final int HPROF_GC_ROOT_THREAD_BLOCK  = 0x06;
112     static final int HPROF_GC_ROOT_MONITOR_USED  = 0x07;
113     static final int HPROF_GC_ROOT_THREAD_OBJ    = 0x08;
114 
115     static final int HPROF_GC_CLASS_DUMP         = 0x20;
116     static final int HPROF_GC_INSTANCE_DUMP      = 0x21;
117     static final int HPROF_GC_OBJ_ARRAY_DUMP         = 0x22;
118     static final int HPROF_GC_PRIM_ARRAY_DUMP         = 0x23;
119 
120     static final int HPROF_HEAP_DUMP_SEGMENT     = 0x1c;
121     static final int HPROF_HEAP_DUMP_END         = 0x2c;
122 
123     private final static int T_CLASS = 2;
124 
125     private int version;        // The version of .hprof being read
126 
127     private int debugLevel;
128     private long currPos;        // Current position in the file
129 
130     private int dumpsToSkip;
131     private boolean callStack;  // If true, read the call stack of objects
132 
133     private int identifierSize;         // Size, in bytes, of identifiers.
134     private Hashtable<Long, String> names;
135 
136     // Hashtable<Integer, ThreadObject>, used to map the thread sequence number
137     // (aka "serial number") to the thread object ID for
138     // HPROF_GC_ROOT_THREAD_OBJ.  ThreadObject is a trivial inner class,
139     // at the end of this file.
140     private Hashtable<Integer, ThreadObject> threadObjects;
141 
142     // Hashtable<Long, String>, maps class object ID to class name
143     // (with / converted to .)
144     private Hashtable<Long, String> classNameFromObjectID;
145 
146     // Hashtable<Integer, Integer>, maps class serial # to class object ID
147     private Hashtable<Integer, String> classNameFromSerialNo;
148 
149     // Hashtable<Long, StackFrame> maps stack frame ID to StackFrame.
150     // Null if we're not tracking them.
151     private Hashtable<Long, StackFrame> stackFrames;
152 
153     // Hashtable<Integer, StackTrace> maps stack frame ID to StackTrace
154     // Null if we're not tracking them.
155     private Hashtable<Integer, StackTrace> stackTraces;
156 
157     private Snapshot snapshot;
158 
159     public static boolean verifyMagicNumber(int numberRead) {
160         return (numberRead == MAGIC_NUMBER);
161     }
162 
163     public HprofReader(ReadBuffer readBuffer, PositionDataInputStream in,
164                        int dumpNumber, boolean callStack, int debugLevel)
165                        throws IOException {
166         super(in);
167         this.snapshot = new Snapshot(readBuffer);
168         this.dumpsToSkip = dumpNumber - 1;
169         this.callStack = callStack;
170         this.debugLevel = debugLevel;
171         names = new Hashtable<Long, String>();
172         threadObjects = new Hashtable<Integer, ThreadObject>(43);
173         classNameFromObjectID = new Hashtable<Long, String>();
174         if (callStack) {
175             stackFrames = new Hashtable<Long, StackFrame>(43);
176             stackTraces = new Hashtable<Integer, StackTrace>(43);
177             classNameFromSerialNo = new Hashtable<Integer, String>();
178         }
179     }
180 
181     public HprofReader(String fileName, PositionDataInputStream in,
182                        int dumpNumber, boolean callStack, int debugLevel)
183                        throws IOException {
184         this(MappedReadBuffer.create(new RandomAccessFile(fileName, "r")),
185             in, dumpNumber, callStack, debugLevel);
186     }
187 
188     public Snapshot read() throws IOException {
189         currPos = 4;    // 4 because of the magic number
190         version = readVersionHeader();
191         identifierSize = in.readInt();
192         snapshot.setIdentifierSize(identifierSize);
193         if (version >= VERSION_JDK12BETA4) {
194             snapshot.setNewStyleArrayClass(true);
195         } else {
196             snapshot.setNewStyleArrayClass(false);
197         }
198 
199         currPos += 4;
200         if (identifierSize != 4 && identifierSize != 8) {
201             throw new IOException("I'm sorry, but I can't deal with an identifier size of " + identifierSize + ".  I can only deal with 4 or 8.");
202         }
203         System.out.println("Dump file created " + (new Date(in.readLong())));
204         currPos += 8;
205 
206         for (;;) {
207             int type;
208             try {
209                 type = in.readUnsignedByte();
210             } catch (EOFException ignored) {
211                 break;
212             }
213             in.readInt();       // Timestamp of this record
214             // Length of record: readInt() will return negative value for record
215             // length >2GB.  so store 32bit value in long to keep it unsigned.
216             long length = in.readInt() & 0xffffffffL;
217             if (debugLevel > 0) {
218                 System.out.println("Read record type " + type
219                                    + ", length " + length
220                                    + " at position " + toHex(currPos));
221             }
222             if (length < 0) {
223                 throw new IOException("Bad record length of " + length
224                                       + " at byte " + toHex(currPos+5)
225                                       + " of file.");
226             }
227             currPos += 9 + length;
228             switch (type) {
229                 case HPROF_UTF8: {
230                     long id = readID();
231                     byte[] chars = new byte[(int)length - identifierSize];
232                     in.readFully(chars);
233                     names.put(id, new String(chars));
234                     break;
235                 }
236                 case HPROF_LOAD_CLASS: {
237                     int serialNo = in.readInt();        // Not used
238                     long classID = readID();
239                     int stackTraceSerialNo = in.readInt();
240                     long classNameID = readID();
241                     Long classIdI = classID;
242                     String nm = getNameFromID(classNameID).replace('/', '.');
243                     classNameFromObjectID.put(classIdI, nm);
244                     if (classNameFromSerialNo != null) {
245                         classNameFromSerialNo.put(serialNo, nm);
246                     }
247                     break;
248                 }
249 
250                 case HPROF_HEAP_DUMP: {
251                     if (dumpsToSkip == 0) {
252                         try {
253                             readHeapDump(length, currPos);
254                         } catch (EOFException exp) {
255                             handleEOF(exp, snapshot);
256                         }
257                         if (debugLevel > 0) {
258                             System.out.println("    Finished processing instances in heap dump.");
259                         }
260                     } else {
261                         dumpsToSkip--;
262                         skipBytes(length);
263                     }
264                     break;
265                 }
266 
267                 case HPROF_HEAP_DUMP_END: {
268                     if (version >= VERSION_JDK6) {
269                         if (dumpsToSkip == 0) {
270                             // update dumpsToSkip to skip other dumps
271                             dumpsToSkip--;
272                         } else {
273                             // skip this dump (of the end record for a sequence of dump segments)
274                             dumpsToSkip--;
275                         }
276                     } else {
277                         // HPROF_HEAP_DUMP_END only recognized in >= 1.0.2
278                         warn("Ignoring unrecognized record type " + type);
279                     }
280                     skipBytes(length);  // should be no-op
281                     break;
282                 }
283 
284                 case HPROF_HEAP_DUMP_SEGMENT: {
285                     if (version >= VERSION_JDK6) {
286                         if (dumpsToSkip == 0) {
287                             try {
288                                 // read the dump segment
289                                 readHeapDump(length, currPos);
290                             } catch (EOFException exp) {
291                                 handleEOF(exp, snapshot);
292                             }
293                         } else {
294                             // all segments comprising the heap dump will be skipped
295                             skipBytes(length);
296                         }
297                     } else {
298                         // HPROF_HEAP_DUMP_SEGMENT only recognized in >= 1.0.2
299                         warn("Ignoring unrecognized record type " + type);
300                         skipBytes(length);
301                     }
302                     break;
303                 }
304 
305                 case HPROF_FRAME: {
306                     if (stackFrames == null) {
307                         skipBytes(length);
308                     } else {
309                         long id = readID();
310                         String methodName = getNameFromID(readID());
311                         String methodSig = getNameFromID(readID());
312                         String sourceFile = getNameFromID(readID());
313                         int classSer = in.readInt();
314                         String className = classNameFromSerialNo.get(classSer);
315                         int lineNumber = in.readInt();
316                         if (lineNumber < StackFrame.LINE_NUMBER_NATIVE) {
317                             warn("Weird stack frame line number:  " + lineNumber);
318                             lineNumber = StackFrame.LINE_NUMBER_UNKNOWN;
319                         }
320                         stackFrames.put(id,
321                                         new StackFrame(methodName, methodSig,
322                                                        className, sourceFile,
323                                                        lineNumber));
324                     }
325                     break;
326                 }
327                 case HPROF_TRACE: {
328                     if (stackTraces == null) {
329                         skipBytes(length);
330                     } else {
331                         int serialNo = in.readInt();
332                         int threadSeq = in.readInt();   // Not used
333                         StackFrame[] frames = new StackFrame[in.readInt()];
334                         for (int i = 0; i < frames.length; i++) {
335                             long fid = readID();
336                             frames[i] = stackFrames.get(fid);
337                             if (frames[i] == null) {
338                                 throw new IOException("Stack frame " + toHex(fid) + " not found");
339                             }
340                         }
341                         stackTraces.put(serialNo,
342                                         new StackTrace(frames));
343                     }
344                     break;
345                 }
346                 case HPROF_UNLOAD_CLASS:
347                 case HPROF_ALLOC_SITES:
348                 case HPROF_START_THREAD:
349                 case HPROF_END_THREAD:
350                 case HPROF_HEAP_SUMMARY:
351                 case HPROF_CPU_SAMPLES:
352                 case HPROF_CONTROL_SETTINGS:
353                 case HPROF_LOCKSTATS_WAIT_TIME:
354                 case HPROF_LOCKSTATS_HOLD_TIME:
355                 {
356                     // Ignore these record types
357                     skipBytes(length);
358                     break;
359                 }
360                 case HPROF_FLAT_ARRAYS: {
361                     readFlatArrays(length);
362                     break;
363                 }
364                 case HPROF_INLINED_FIELDS: {
365                     readInlinedFields(length);
366                     break;
367                 }
368 
369                 default: {
370                     skipBytes(length);
371                     warn("Ignoring unrecognized record type " + type);
372                 }
373             }
374         }
375 
376         return snapshot;
377     }
378 
379     public String printStackTraces() {
380         StringBuffer output = new StringBuffer();
381         for (Map.Entry<Integer, StackTrace> entry : stackTraces.entrySet()) {
382             StackFrame[] frames = entry.getValue().getFrames();
383             output.append("SerialNo " + entry.getKey() + "\n");
384             for (int i = 0; i < frames.length; i++) {
385                 output.append("  " + frames[i].getClassName() + "." + frames[i].getMethodName()
386                         + frames[i].getMethodSignature() + " (" + frames[i].getSourceFileName()
387                         + ":" + frames[i].getLineNumber() + ")" + "\n");
388             }
389         }
390 
391         System.out.println(output);
392         return output.toString();
393     }
394 
395     private void skipBytes(long length) throws IOException {
396         while (length > 0) {
397             long skipped = in.skip(length);
398             if (skipped == 0) {
399                 // EOF or other problem, throw exception
400                 throw new EOFException("Couldn't skip enough bytes");
401             }
402             length -= skipped;
403         }
404     }
405 
406     private int readVersionHeader() throws IOException {
407         int candidatesLeft = VERSIONS.length;
408         boolean[] matched = new boolean[VERSIONS.length];
409         for (int i = 0; i < candidatesLeft; i++) {
410             matched[i] = true;
411         }
412 
413         int pos = 0;
414         while (candidatesLeft > 0) {
415             char c = (char) in.readByte();
416             currPos++;
417             for (int i = 0; i < VERSIONS.length; i++) {
418                 if (matched[i]) {
419                     if (c != VERSIONS[i].charAt(pos)) {   // Not matched
420                         matched[i] = false;
421                         --candidatesLeft;
422                     } else if (pos == VERSIONS[i].length() - 1) {  // Full match
423                         return i;
424                     }
425                 }
426             }
427             ++pos;
428         }
429         throw new IOException("Version string not recognized at byte " + (pos+3));
430     }
431 
432     private void readHeapDump(long bytesLeft, long posAtEnd) throws IOException {
433         while (bytesLeft > 0) {
434             int type = in.readUnsignedByte();
435             if (debugLevel > 0) {
436                 System.out.println("    Read heap sub-record type " + type
437                                    + " at position "
438                                    + toHex(posAtEnd - bytesLeft));
439             }
440             bytesLeft--;
441             switch(type) {
442                 case HPROF_GC_ROOT_UNKNOWN: {
443                     long id = readID();
444                     bytesLeft -= identifierSize;
445                     snapshot.addRoot(new Root(id, 0, Root.UNKNOWN, ""));
446                     break;
447                 }
448                 case HPROF_GC_ROOT_THREAD_OBJ: {
449                     long id = readID();
450                     int threadSeq = in.readInt();
451                     int stackSeq = in.readInt();
452                     bytesLeft -= identifierSize + 8;
453                     StackTrace st = getStackTraceFromSerial(stackSeq);
454                     ThreadObject threadObj = new ThreadObject(id, st);
455                     threadObjects.put(threadSeq, threadObj);
456                     snapshot.addThreadObject(threadObj);
457                     break;
458                 }
459                 case HPROF_GC_ROOT_JNI_GLOBAL: {
460                     long id = readID();
461                     long globalRefId = readID();        // Ignored, for now
462                     bytesLeft -= 2*identifierSize;
463                     snapshot.addRoot(new Root(id, 0, Root.NATIVE_STATIC, ""));
464                     break;
465                 }
466                 case HPROF_GC_ROOT_JNI_LOCAL: {
467                     long id = readID();
468                     int threadSeq = in.readInt();
469                     int depth = in.readInt();
470                     bytesLeft -= identifierSize + 8;
471                     ThreadObject to = getThreadObjectFromSequence(threadSeq);
472                     StackTrace st = to.getStackTrace();
473                     if (st != null) {
474                         st = st.traceForDepth(depth+1);
475                     }
476                     snapshot.addRoot(new Root(id, to.getId(),
477                                               Root.NATIVE_LOCAL, "", st));
478                     break;
479                 }
480                 case HPROF_GC_ROOT_JAVA_FRAME: {
481                     long id = readID();
482                     int threadSeq = in.readInt();
483                     int depth = in.readInt();
484                     bytesLeft -= identifierSize + 8;
485                     ThreadObject to = getThreadObjectFromSequence(threadSeq);
486                     StackTrace st = to.getStackTrace();;
487                     if (st != null) {
488                         st = st.traceForDepth(depth+1);
489                     }
490                     snapshot.addRoot(new Root(id, to.getId(),
491                                               Root.JAVA_LOCAL, "", st));
492                     break;
493                 }
494                 case HPROF_GC_ROOT_NATIVE_STACK: {
495                     long id = readID();
496                     int threadSeq = in.readInt();
497                     bytesLeft -= identifierSize + 4;
498                     ThreadObject to = getThreadObjectFromSequence(threadSeq);
499                     StackTrace st = to.getStackTrace();;
500                     snapshot.addRoot(new Root(id, to.getId(),
501                                               Root.NATIVE_STACK, "", st));
502                     break;
503                 }
504                 case HPROF_GC_ROOT_STICKY_CLASS: {
505                     long id = readID();
506                     bytesLeft -= identifierSize;
507                     snapshot.addRoot(new Root(id, 0, Root.SYSTEM_CLASS, ""));
508                     break;
509                 }
510                 case HPROF_GC_ROOT_THREAD_BLOCK: {
511                     long id = readID();
512                     int threadSeq = in.readInt();
513                     bytesLeft -= identifierSize + 4;
514                     ThreadObject to = getThreadObjectFromSequence(threadSeq);
515                     StackTrace st = to.getStackTrace();
516                     snapshot.addRoot(new Root(id, to.getId(),
517                                      Root.THREAD_BLOCK, "", st));
518                     break;
519                 }
520                 case HPROF_GC_ROOT_MONITOR_USED: {
521                     long id = readID();
522                     bytesLeft -= identifierSize;
523                     snapshot.addRoot(new Root(id, 0, Root.BUSY_MONITOR, ""));
524                     break;
525                 }
526                 case HPROF_GC_CLASS_DUMP: {
527                     int bytesRead = readClass();
528                     bytesLeft -= bytesRead;
529                     break;
530                 }
531                 case HPROF_GC_INSTANCE_DUMP: {
532                     int bytesRead = readInstance();
533                     bytesLeft -= bytesRead;
534                     break;
535                 }
536                 case HPROF_GC_OBJ_ARRAY_DUMP: {
537                     long bytesRead = readArray(false);
538                     bytesLeft -= bytesRead;
539                     break;
540                 }
541                 case HPROF_GC_PRIM_ARRAY_DUMP: {
542                     long bytesRead = readArray(true);
543                     bytesLeft -= bytesRead;
544                     break;
545                 }
546                 default: {
547                     throw new IOException("Unrecognized heap dump sub-record type:  " + type);
548                 }
549             }
550         }
551         if (bytesLeft != 0) {
552             warn("Error reading heap dump or heap dump segment:  Byte count is " + bytesLeft + " instead of 0");
553             skipBytes(bytesLeft);
554         }
555         if (debugLevel > 0) {
556             System.out.println("    Finished heap sub-records.");
557         }
558     }
559 
560     private long readID() throws IOException {
561         return (identifierSize == 4)?
562             (Snapshot.SMALL_ID_MASK & (long)in.readInt()) : in.readLong();
563     }
564 
565     //
566     // Read a java value.  If result is non-null, it's expected to be an
567     // array of one element.  We use it to fake multiple return values.
568     // @returns the number of bytes read
569     //
570     private int readValue(JavaThing[] resultArr) throws IOException {
571         byte type = in.readByte();
572         return 1 + readValueForType(type, resultArr);
573     }
574 
575     private int readValueForType(byte type, JavaThing[] resultArr)
576             throws IOException {
577         if (version >= VERSION_JDK12BETA4) {
578             type = signatureFromTypeId(type);
579         }
580         return readValueForTypeSignature(type, resultArr);
581     }
582 
583     private int readValueForTypeSignature(byte type, JavaThing[] resultArr)
584             throws IOException {
585         switch (type) {
586             case '[':
587             case 'L': {
588                 long id = readID();
589                 if (resultArr != null) {
590                     resultArr[0] = new JavaObjectRef(id);
591                 }
592                 return identifierSize;
593             }
594             case 'Z': {
595                 int b = in.readByte();
596                 if (b != 0 && b != 1) {
597                     warn("Illegal boolean value read");
598                 }
599                 if (resultArr != null) {
600                     resultArr[0] = new JavaBoolean(b != 0);
601                 }
602                 return 1;
603             }
604             case 'B': {
605                 byte b = in.readByte();
606                 if (resultArr != null) {
607                     resultArr[0] = new JavaByte(b);
608                 }
609                 return 1;
610             }
611             case 'S': {
612                 short s = in.readShort();
613                 if (resultArr != null) {
614                     resultArr[0] = new JavaShort(s);
615                 }
616                 return 2;
617             }
618             case 'C': {
619                 char ch = in.readChar();
620                 if (resultArr != null) {
621                     resultArr[0] = new JavaChar(ch);
622                 }
623                 return 2;
624             }
625             case 'I': {
626                 int val = in.readInt();
627                 if (resultArr != null) {
628                     resultArr[0] = new JavaInt(val);
629                 }
630                 return 4;
631             }
632             case 'J': {
633                 long val = in.readLong();
634                 if (resultArr != null) {
635                     resultArr[0] = new JavaLong(val);
636                 }
637                 return 8;
638             }
639             case 'F': {
640                 float val = in.readFloat();
641                 if (resultArr != null) {
642                     resultArr[0] = new JavaFloat(val);
643                 }
644                 return 4;
645             }
646             case 'D': {
647                 double val = in.readDouble();
648                 if (resultArr != null) {
649                     resultArr[0] = new JavaDouble(val);
650                 }
651                 return 8;
652             }
653             default: {
654                 throw new IOException("Bad value signature:  " + type);
655             }
656         }
657     }
658 
659     private ThreadObject getThreadObjectFromSequence(int threadSeq)
660             throws IOException {
661         ThreadObject to = threadObjects.get(threadSeq);
662         if (to == null) {
663             throw new IOException("Thread " + threadSeq +
664                                   " not found for JNI local ref");
665         }
666         return to;
667     }
668 
669     private String getNameFromID(long id) throws IOException {
670         return getNameFromID(Long.valueOf(id));
671     }
672 
673     private String getNameFromID(Long id) throws IOException {
674         if (id.longValue() == 0L) {
675             return "";
676         }
677         String result = names.get(id);
678         if (result == null) {
679             warn("Name not found at " + toHex(id.longValue()));
680             return "unresolved name " + toHex(id.longValue());
681         }
682         return result;
683     }
684 
685     private StackTrace getStackTraceFromSerial(int ser) throws IOException {
686         if (stackTraces == null) {
687             return null;
688         }
689         StackTrace result = stackTraces.get(ser);
690         if (result == null) {
691             warn("Stack trace not found for serial # " + ser);
692         }
693         return result;
694     }
695 
696     //
697     // Handle a HPROF_GC_CLASS_DUMP
698     // Return number of bytes read
699     //
700     private int readClass() throws IOException {
701         long id = readID();
702         StackTrace stackTrace = getStackTraceFromSerial(in.readInt());
703         long superId = readID();
704         long classLoaderId = readID();
705         long signersId = readID();
706         long protDomainId = readID();
707         long reserved1 = readID();
708         long reserved2 = readID();
709         int instanceSize = in.readInt();
710         int bytesRead = 7 * identifierSize + 8;
711 
712         int numConstPoolEntries = in.readUnsignedShort();
713         bytesRead += 2;
714         for (int i = 0; i < numConstPoolEntries; i++) {
715             int index = in.readUnsignedShort(); // unused
716             bytesRead += 2;
717             bytesRead += readValue(null);       // We ignore the values
718         }
719 
720         int numStatics = in.readUnsignedShort();
721         bytesRead += 2;
722         JavaThing[] valueBin = new JavaThing[1];
723         JavaStatic[] statics = new JavaStatic[numStatics];
724         for (int i = 0; i < numStatics; i++) {
725             long nameId = readID();
726             bytesRead += identifierSize;
727             byte type = in.readByte();
728             bytesRead++;
729             bytesRead += readValueForType(type, valueBin);
730             String fieldName = getNameFromID(nameId);
731             if (version >= VERSION_JDK12BETA4) {
732                 type = signatureFromTypeId(type);
733             }
734             String signature = "" + ((char) type);
735             JavaField f = new JavaField(fieldName, signature);
736             statics[i] = new JavaStatic(f, valueBin[0]);
737         }
738 
739         int numFields = in.readUnsignedShort();
740         bytesRead += 2;
741         JavaField[] fields = new JavaField[numFields];
742         for (int i = 0; i < numFields; i++) {
743             long nameId = readID();
744             bytesRead += identifierSize;
745             byte type = in.readByte();
746             bytesRead++;
747             String fieldName = getNameFromID(nameId);
748             if (version >= VERSION_JDK12BETA4) {
749                 type = signatureFromTypeId(type);
750             }
751             String signature = "" + ((char) type);
752             fields[i] = new JavaField(fieldName, signature);
753         }
754         String name = classNameFromObjectID.get(id);
755         if (name == null) {
756             warn("Class name not found for " + toHex(id));
757             name = "unknown-name@" + toHex(id);
758         }
759         JavaClass c = new JavaClass(id, name, superId, classLoaderId, signersId,
760                                     protDomainId, fields, statics,
761                                     instanceSize);
762         snapshot.addClass(id, c);
763         snapshot.setSiteTrace(c, stackTrace);
764 
765         return bytesRead;
766     }
767 
768     private String toHex(long addr) {
769         return jdk.test.lib.hprof.util.Misc.toHex(addr);
770     }
771 
772     //
773     // Handle a HPROF_GC_INSTANCE_DUMP
774     // Return number of bytes read
775     //
776     private int readInstance() throws IOException {
777         long start = in.position();
778         long id = readID();
779         StackTrace stackTrace = getStackTraceFromSerial(in.readInt());
780         long classID = readID();
781         JavaClass searchedClass = snapshot.findClass(
782                                   "0x" + Long.toHexString(classID));
783         if (searchedClass == null) {
784             throw new IOException(
785                 "Class Record for 0x" + Long.toHexString(classID) + " not found");
786         }
787         int bytesFollowing = in.readInt();
788         int bytesRead = (2 * identifierSize) + 8 + bytesFollowing;
789         JavaObject jobj = new JavaObject(classID, start);
790         skipBytes(bytesFollowing);
791         snapshot.addHeapObject(id, jobj);
792         snapshot.setSiteTrace(jobj, stackTrace);
793         return bytesRead;
794     }
795 
796     //
797     // Handle a HPROF_GC_OBJ_ARRAY_DUMP or HPROF_GC_PRIM_ARRAY_DUMP
798     // Return number of bytes read
799     //
800     private long readArray(boolean isPrimitive) throws IOException {
801         long start = in.position();
802         long id = readID();
803         StackTrace stackTrace = getStackTraceFromSerial(in.readInt());
804         int num = in.readInt();
805         long bytesRead = identifierSize + 8;
806         long elementClassID;
807         if (isPrimitive) {
808             elementClassID = in.readByte();
809             bytesRead++;
810         } else {
811             elementClassID = readID();
812             bytesRead += identifierSize;
813         }
814 
815         // Check for primitive arrays:
816         byte primitiveSignature = 0x00;
817         int elSize = 0;
818         if (isPrimitive || version < VERSION_JDK12BETA4) {
819             switch ((int)elementClassID) {
820                 case T_BOOLEAN: {
821                     primitiveSignature = (byte) 'Z';
822                     elSize = 1;
823                     break;
824                 }
825                 case T_CHAR: {
826                     primitiveSignature = (byte) 'C';
827                     elSize = 2;
828                     break;
829                 }
830                 case T_FLOAT: {
831                     primitiveSignature = (byte) 'F';
832                     elSize = 4;
833                     break;
834                 }
835                 case T_DOUBLE: {
836                     primitiveSignature = (byte) 'D';
837                     elSize = 8;
838                     break;
839                 }
840                 case T_BYTE: {
841                     primitiveSignature = (byte) 'B';
842                     elSize = 1;
843                     break;
844                 }
845                 case T_SHORT: {
846                     primitiveSignature = (byte) 'S';
847                     elSize = 2;
848                     break;
849                 }
850                 case T_INT: {
851                     primitiveSignature = (byte) 'I';
852                     elSize = 4;
853                     break;
854                 }
855                 case T_LONG: {
856                     primitiveSignature = (byte) 'J';
857                     elSize = 8;
858                     break;
859                 }
860             }
861             if (version >= VERSION_JDK12BETA4 && primitiveSignature == 0x00) {
862                 throw new IOException("Unrecognized typecode:  "
863                                         + elementClassID);
864             }
865         }
866         if (primitiveSignature != 0x00) {
867             long size = elSize * (long)num;
868             bytesRead += size;
869             JavaValueArray va = new JavaValueArray(id, primitiveSignature, start);
870             skipBytes(size);
871             snapshot.addHeapObject(id, va);
872             snapshot.setSiteTrace(va, stackTrace);
873         } else {
874             long sz = (long)num * identifierSize;
875             bytesRead += sz;
876             JavaObjectArray arr = new JavaObjectArray(elementClassID, start);
877             skipBytes(sz);
878             snapshot.addHeapObject(id, arr);
879             snapshot.setSiteTrace(arr, stackTrace);
880         }
881         return bytesRead;
882     }
883 
884     private byte signatureFromTypeId(byte typeId) throws IOException {
885         switch (typeId) {
886             case T_CLASS: {
887                 return (byte) 'L';
888             }
889             case T_BOOLEAN: {
890                 return (byte) 'Z';
891             }
892             case T_CHAR: {
893                 return (byte) 'C';
894             }
895             case T_FLOAT: {
896                 return (byte) 'F';
897             }
898             case T_DOUBLE: {
899                 return (byte) 'D';
900             }
901             case T_BYTE: {
902                 return (byte) 'B';
903             }
904             case T_SHORT: {
905                 return (byte) 'S';
906             }
907             case T_INT: {
908                 return (byte) 'I';
909             }
910             case T_LONG: {
911                 return (byte) 'J';
912             }
913             default: {
914                 throw new IOException("Invalid type id of " + typeId);
915             }
916         }
917     }
918 
919     private void readFlatArrays(long length) throws IOException {
920         while (length > 0) {
921             byte tag = in.readByte();
922             length--;
923             switch (tag) {
924                 case HPROF_FLAT_ARRAY: {
925                     long objId = readID();
926                     length -= identifierSize;
927                     long elementClassId = readID();
928                     length -= identifierSize;
929                     snapshot.addFlatArray(objId, elementClassId);
930                     break;
931                 }
932                 default: {
933                     throw new IOException("Invalid tag " + tag);
934                 }
935             }
936         }
937     }
938 
939     private void readInlinedFields(long length) throws IOException {
940         while (length > 0) {
941             byte tag = in.readByte();
942             length--;
943             switch (tag) {
944                 case HPROF_CLASS_WITH_INLINED_FIELDS: {
945                     long classID = readID();
946                     length -= identifierSize;
947                     int fieldNum = in.readUnsignedShort();
948                     length -= 2;
949                     Snapshot.ClassInlinedFields[] fields = new Snapshot.ClassInlinedFields[fieldNum];
950                     for (int i = 0; i < fieldNum; i++) {
951                         int fieldIndex = in.readUnsignedShort();
952                         length -= 2;
953                         int synthFieldCount = in.readUnsignedShort();
954                         length -= 2;
955                         String fieldName = getNameFromID(readID());
956                         length -= identifierSize;
957                         long fieldClassId = readID();
958                         length -= identifierSize;
959                         fields[i] = new Snapshot.ClassInlinedFields(fieldIndex, synthFieldCount, fieldName, fieldClassId);
960                     }
961                     snapshot.addClassInlinedFields(classID, fields);
962                     break;
963                 }
964                 default: {
965                     throw new IOException("Invalid tag " + tag);
966                 }
967             }
968         }
969     }
970 
971     private void handleEOF(EOFException exp, Snapshot snapshot) {
972         if (debugLevel > 0) {
973             exp.printStackTrace();
974         }
975         warn("Unexpected EOF. Will miss information...");
976         // we have EOF, we have to tolerate missing references
977         snapshot.setUnresolvedObjectsOK(true);
978     }
979 
980     private void warn(String msg) {
981         System.out.println("WARNING: " + msg);
982     }
983 
984 }