< prev index next >

src/share/vm/services/heapDumper.cpp

Print this page
rev 9054 : 8144732: VM_HeapDumper hits assert with bad dump_len
Reviewed-by: dsamersoff

@@ -48,12 +48,11 @@
 /*
  * HPROF binary format - description copied from:
  *   src/share/demo/jvmti/hprof/hprof_io.c
  *
  *
- *  header    "JAVA PROFILE 1.0.1" or "JAVA PROFILE 1.0.2"
- *            (0-terminated)
+ *  header    "JAVA PROFILE 1.0.2" (0-terminated)
  *
  *  u4        size of identifiers. Identifiers are used to represent
  *            UTF8 strings, objects, stack traces, etc. They usually
  *            have the same size as host pointers. For example, on
  *            Solaris and Win32, the size is 4.

@@ -380,10 +379,12 @@
 
   char* _buffer;    // internal buffer
   size_t _size;
   size_t _pos;
 
+  jlong _dump_start;
+
   char* _error;   // error message when I/O fails
 
   void set_file_descriptor(int fd)              { _fd = fd; }
   int file_descriptor() const                   { return _fd; }
 

@@ -403,10 +404,14 @@
 
   void close();
   bool is_open() const                  { return file_descriptor() >= 0; }
   void flush();
 
+  jlong dump_start() const                      { return _dump_start; }
+  void set_dump_start(jlong pos);
+  julong current_record_length();
+
   // total number of bytes written to the disk
   julong bytes_written() const          { return _bytes_written; }
 
   // adjust the number of bytes written to disk (used to keep the count
   // of the number of bytes written in case of rewrites)

@@ -444,10 +449,11 @@
   } while (_buffer == NULL && _size > 0);
   assert((_size > 0 && _buffer != NULL) || (_size == 0 && _buffer == NULL), "sanity check");
   _pos = 0;
   _error = NULL;
   _bytes_written = 0L;
+  _dump_start = (jlong)-1;
   _fd = os::create_binary_file(path, false);    // don't replace existing file
 
   // if the open failed we record the error
   if (_fd < 0) {
     _error = (char*)os::strdup(strerror(errno));

@@ -471,10 +477,26 @@
     ::close(file_descriptor());
     set_file_descriptor(-1);
   }
 }
 
+// sets the dump starting position
+void DumpWriter::set_dump_start(jlong pos) {
+  _dump_start = pos;
+}
+
+julong DumpWriter::current_record_length() {
+  if (is_open()) {
+    // calculate the size of the dump record
+    julong dump_end = bytes_written() + bytes_unwritten();
+    assert(dump_end == (size_t)current_offset(), "checking");
+    julong dump_len = dump_end - dump_start() - 4;
+    return dump_len;
+  }
+  return 0;
+}
+
 // write directly to the file
 void DumpWriter::write_internal(void* s, size_t len) {
   if (is_open()) {
     const char* pos = (char*)s;
     ssize_t n = 0;

@@ -639,10 +661,22 @@
   static void dump_object_array(DumpWriter* writer, objArrayOop array);
   // creates HPROF_GC_PRIM_ARRAY_DUMP record for the given type array
   static void dump_prim_array(DumpWriter* writer, typeArrayOop array);
   // create HPROF_FRAME record for the given method and bci
   static void dump_stack_frame(DumpWriter* writer, int frame_serial_num, int class_serial_num, Method* m, int bci);
+
+  // check if we need to truncate an array
+  static int calculate_array_max_length(DumpWriter* writer, arrayOop array, short header_size);
+
+  // writes a HPROF_HEAP_DUMP_SEGMENT record
+  static void write_dump_header(DumpWriter* writer);
+
+  // fixes up the length of the current dump record
+  static void write_current_dump_record_length(DumpWriter* writer);
+
+  // fixes up the current dump record and writes HPROF_HEAP_DUMP_END record
+  static void end_of_dump(DumpWriter* writer);
 };
 
 // write a header of the given type
 void DumperSupport:: write_header(DumpWriter* writer, hprofTag tag, u4 len) {
   writer->write_u1((u1)tag);

@@ -1045,54 +1079,108 @@
     // get the array class for the next rank
     k = klass->array_klass_or_null();
   }
 }
 
+// Hprof uses an u4 as record length field,
+// which means we need to truncate arrays that are too long.
+int DumperSupport::calculate_array_max_length(DumpWriter* writer, arrayOop array, short header_size) {
+  BasicType type = ArrayKlass::cast(array->klass())->element_type();
+  assert(type >= T_BOOLEAN && type <= T_OBJECT, "invalid array element type");
+
+  int length = array->length();
+
+  int type_size;
+  if (type == T_OBJECT) {
+    type_size = sizeof(address);
+  } else {
+    type_size = type2aelembytes(type);
+  }
+
+  size_t length_in_bytes = (size_t)length * type_size;
+
+  // Create a new record if the current record is non-empty and the array can't fit.
+  julong current_record_length = writer->current_record_length();
+  if (current_record_length > 0 &&
+      (current_record_length + header_size + length_in_bytes) > max_juint) {
+    write_current_dump_record_length(writer);
+    write_dump_header(writer);
+
+    // We now have an empty record.
+    current_record_length = 0;
+  }
+
+  // Calculate max bytes we can use.
+  uint max_bytes = max_juint - (header_size + current_record_length);
+
+  // Array too long for the record?
+  // Calculate max length and return it.
+  if (length_in_bytes > max_bytes) {
+    length = max_bytes / type_size;
+    length_in_bytes = (size_t)length * type_size;
+
+    warning("cannot dump array of type %s[] with length %d; truncating to length %d",
+            type2name_tab[type], array->length(), length);
+  }
+  return length;
+}
+
 // creates HPROF_GC_OBJ_ARRAY_DUMP record for the given object array
 void DumperSupport::dump_object_array(DumpWriter* writer, objArrayOop array) {
+  // sizeof(u1) + 2 * sizeof(u4) + sizeof(objectID) + sizeof(classID)
+  short header_size = 1 + 2 * 4 + 2 * sizeof(address);
+
+  int length = calculate_array_max_length(writer, array, header_size);
 
   writer->write_u1(HPROF_GC_OBJ_ARRAY_DUMP);
   writer->write_objectID(array);
   writer->write_u4(STACK_TRACE_ID);
-  writer->write_u4((u4)array->length());
+  writer->write_u4(length);
+
 
   // array class ID
   writer->write_classID(array->klass());
 
   // [id]* elements
-  for (int index=0; index<array->length(); index++) {
+  for (int index = 0; index < length; index++) {
     oop o = array->obj_at(index);
     writer->write_objectID(o);
   }
 }
 
-#define WRITE_ARRAY(Array, Type, Size) \
-  for (int i=0; i<Array->length(); i++) { writer->write_##Size((Size)array->Type##_at(i)); }
+#define WRITE_ARRAY(Array, Type, Size, Length) \
+  for (int i = 0; i < Length; i++) { writer->write_##Size((Size)array->Type##_at(i)); }
 
 
 // creates HPROF_GC_PRIM_ARRAY_DUMP record for the given type array
 void DumperSupport::dump_prim_array(DumpWriter* writer, typeArrayOop array) {
   BasicType type = TypeArrayKlass::cast(array->klass())->element_type();
 
+  // 2 * sizeof(u1) + 2 * sizeof(u4) + sizeof(objectID)
+  short header_size = 2 * 1 + 2 * 4 + sizeof(address);
+
+  int length = calculate_array_max_length(writer, array, header_size);
+  int type_size = type2aelembytes(type);
+  u4 length_in_bytes = (u4)length * type_size;
+
   writer->write_u1(HPROF_GC_PRIM_ARRAY_DUMP);
   writer->write_objectID(array);
   writer->write_u4(STACK_TRACE_ID);
-  writer->write_u4((u4)array->length());
+  writer->write_u4(length);
   writer->write_u1(type2tag(type));
 
   // nothing to copy
-  if (array->length() == 0) {
+  if (length == 0) {
     return;
   }
 
   // If the byte ordering is big endian then we can copy most types directly
-  u4 length_in_bytes = (u4)array->length() * type2aelembytes(type);
 
   switch (type) {
     case T_INT : {
       if (Bytes::is_Java_byte_ordering_different()) {
-        WRITE_ARRAY(array, int, u4);
+        WRITE_ARRAY(array, int, u4, length);
       } else {
         writer->write_raw((void*)(array->int_at_addr(0)), length_in_bytes);
       }
       break;
     }

@@ -1100,35 +1188,35 @@
       writer->write_raw((void*)(array->byte_at_addr(0)), length_in_bytes);
       break;
     }
     case T_CHAR : {
       if (Bytes::is_Java_byte_ordering_different()) {
-        WRITE_ARRAY(array, char, u2);
+        WRITE_ARRAY(array, char, u2, length);
       } else {
         writer->write_raw((void*)(array->char_at_addr(0)), length_in_bytes);
       }
       break;
     }
     case T_SHORT : {
       if (Bytes::is_Java_byte_ordering_different()) {
-        WRITE_ARRAY(array, short, u2);
+        WRITE_ARRAY(array, short, u2, length);
       } else {
         writer->write_raw((void*)(array->short_at_addr(0)), length_in_bytes);
       }
       break;
     }
     case T_BOOLEAN : {
       if (Bytes::is_Java_byte_ordering_different()) {
-        WRITE_ARRAY(array, bool, u1);
+        WRITE_ARRAY(array, bool, u1, length);
       } else {
         writer->write_raw((void*)(array->bool_at_addr(0)), length_in_bytes);
       }
       break;
     }
     case T_LONG : {
       if (Bytes::is_Java_byte_ordering_different()) {
-        WRITE_ARRAY(array, long, u8);
+        WRITE_ARRAY(array, long, u8, length);
       } else {
         writer->write_raw((void*)(array->long_at_addr(0)), length_in_bytes);
       }
       break;
     }

@@ -1136,18 +1224,18 @@
     // handle float/doubles in a special value to ensure than NaNs are
     // written correctly. TO DO: Check if we can avoid this on processors that
     // use IEEE 754.
 
     case T_FLOAT : {
-      for (int i=0; i<array->length(); i++) {
-        dump_float( writer, array->float_at(i) );
+      for (int i = 0; i < length; i++) {
+        dump_float(writer, array->float_at(i));
       }
       break;
     }
     case T_DOUBLE : {
-      for (int i=0; i<array->length(); i++) {
-        dump_double( writer, array->double_at(i) );
+      for (int i = 0; i < length; i++) {
+        dump_double(writer, array->double_at(i));
       }
       break;
     }
     default : ShouldNotReachHere();
   }

@@ -1360,12 +1448,10 @@
   static DumpWriter*    _global_writer;
   DumpWriter*           _local_writer;
   JavaThread*           _oome_thread;
   Method*               _oome_constructor;
   bool _gc_before_heap_dump;
-  bool _is_segmented_dump;
-  jlong _dump_start;
   GrowableArray<Klass*>* _klass_map;
   ThreadStackTrace** _stack_traces;
   int _num_threads;
 
   // accessors and setters

@@ -1380,15 +1466,10 @@
     _global_writer = _local_writer;
   }
   void clear_global_dumper() { _global_dumper = NULL; }
   void clear_global_writer() { _global_writer = NULL; }
 
-  bool is_segmented_dump() const                { return _is_segmented_dump; }
-  void set_segmented_dump()                     { _is_segmented_dump = true; }
-  jlong dump_start() const                      { return _dump_start; }
-  void set_dump_start(jlong pos);
-
   bool skip_operation() const;
 
   // writes a HPROF_LOAD_CLASS record
   static void do_load_class(Klass* k);
 

@@ -1409,30 +1490,18 @@
   }
 
   // HPROF_TRACE and HPROF_FRAME records
   void dump_stack_traces();
 
-  // writes a HPROF_HEAP_DUMP or HPROF_HEAP_DUMP_SEGMENT record
-  void write_dump_header();
-
-  // fixes up the length of the current dump record
-  void write_current_dump_record_length();
-
-  // fixes up the current dump record )and writes HPROF_HEAP_DUMP_END
-  // record in the case of a segmented heap dump)
-  void end_of_dump();
-
  public:
   VM_HeapDumper(DumpWriter* writer, bool gc_before_heap_dump, bool oome) :
     VM_GC_Operation(0 /* total collections,      dummy, ignored */,
                     GCCause::_heap_dump /* GC Cause */,
                     0 /* total full collections, dummy, ignored */,
                     gc_before_heap_dump) {
     _local_writer = writer;
     _gc_before_heap_dump = gc_before_heap_dump;
-    _is_segmented_dump = false;
-    _dump_start = (jlong)-1;
     _klass_map = new (ResourceObj::C_HEAP, mtInternal) GrowableArray<Klass*>(INITIAL_CLASS_COUNT, true);
     _stack_traces = NULL;
     _num_threads = 0;
     if (oome) {
       assert(!Thread::current()->is_VM_thread(), "Dump from OutOfMemoryError cannot be called by the VMThread");

@@ -1468,91 +1537,70 @@
 
 bool VM_HeapDumper::skip_operation() const {
   return false;
 }
 
-// sets the dump starting position
-void VM_HeapDumper::set_dump_start(jlong pos) {
-  _dump_start = pos;
-}
-
- // writes a HPROF_HEAP_DUMP or HPROF_HEAP_DUMP_SEGMENT record
-void VM_HeapDumper::write_dump_header() {
-  if (writer()->is_open()) {
-    if (is_segmented_dump()) {
-      writer()->write_u1(HPROF_HEAP_DUMP_SEGMENT);
-    } else {
-      writer()->write_u1(HPROF_HEAP_DUMP);
-    }
-    writer()->write_u4(0); // current ticks
+ // writes a HPROF_HEAP_DUMP_SEGMENT record
+void DumperSupport::write_dump_header(DumpWriter* writer) {
+  if (writer->is_open()) {
+    writer->write_u1(HPROF_HEAP_DUMP_SEGMENT);
+    writer->write_u4(0); // current ticks
 
     // record the starting position for the dump (its length will be fixed up later)
-    set_dump_start(writer()->current_offset());
-    writer()->write_u4(0);
+    writer->set_dump_start(writer->current_offset());
+    writer->write_u4(0);
   }
 }
 
 // fixes up the length of the current dump record
-void VM_HeapDumper::write_current_dump_record_length() {
-  if (writer()->is_open()) {
-    assert(dump_start() >= 0, "no dump start recorded");
-
-    // calculate the size of the dump record
-    julong dump_end = writer()->current_offset();
-    julong dump_len = (dump_end - dump_start() - 4);
+void DumperSupport::write_current_dump_record_length(DumpWriter* writer) {
+  if (writer->is_open()) {
+    julong dump_end = writer->bytes_written() + writer->bytes_unwritten();
+    julong dump_len = writer->current_record_length();
 
     // record length must fit in a u4
     if (dump_len > max_juint) {
       warning("record is too large");
     }
 
     // seek to the dump start and fix-up the length
-    writer()->seek_to_offset(dump_start());
-    writer()->write_u4((u4)dump_len);
+    assert(writer->dump_start() >= 0, "no dump start recorded");
+    writer->seek_to_offset(writer->dump_start());
+    writer->write_u4((u4)dump_len);
 
     // adjust the total size written to keep the bytes written correct.
-    writer()->adjust_bytes_written(-((jlong) sizeof(u4)));
+    writer->adjust_bytes_written(-((jlong) sizeof(u4)));
 
     // seek to dump end so we can continue
-    writer()->seek_to_offset(dump_end);
+    writer->seek_to_offset(dump_end);
 
     // no current dump record
-    set_dump_start((jlong)-1);
+    writer->set_dump_start((jlong)-1);
   }
 }
 
 // used on a sub-record boundary to check if we need to start a
 // new segment.
 void VM_HeapDumper::check_segment_length() {
   if (writer()->is_open()) {
-    if (is_segmented_dump()) {
-      // don't use current_offset that would be too expensive on a per record basis
-      julong dump_end = writer()->bytes_written() + writer()->bytes_unwritten();
-      assert(dump_end == (julong)writer()->current_offset(), "checking");
-      julong dump_len = (dump_end - dump_start() - 4);
-      assert(dump_len <= max_juint, "bad dump length");
+    julong dump_len = writer()->current_record_length();
 
-      if (dump_len > HeapDumpSegmentSize) {
-        write_current_dump_record_length();
-        write_dump_header();
-      }
+    if (dump_len > 2UL*G) {
+      DumperSupport::write_current_dump_record_length(writer());
+      DumperSupport::write_dump_header(writer());
     }
   }
 }
 
-// fixes up the current dump record )and writes HPROF_HEAP_DUMP_END
-// record in the case of a segmented heap dump)
-void VM_HeapDumper::end_of_dump() {
-  if (writer()->is_open()) {
-    write_current_dump_record_length();
+// fixes up the current dump record and writes HPROF_HEAP_DUMP_END record
+void DumperSupport::end_of_dump(DumpWriter* writer) {
+  if (writer->is_open()) {
+    write_current_dump_record_length(writer);
 
-    // for segmented dump we write the end record
-    if (is_segmented_dump()) {
-      writer()->write_u1(HPROF_HEAP_DUMP_END);
-      writer()->write_u4(0);
-      writer()->write_u4(0);
-    }
+    writer->write_u1(HPROF_HEAP_DUMP_END);
+    writer->write_u4(0);
+    writer->write_u4(0);
   }
 }
 
 // marks sub-record boundary
 void HeapObjectDumper::mark_end_of_record() {

@@ -1714,20 +1762,21 @@
 //  HPROF_HEADER
 //  [HPROF_UTF8]*
 //  [HPROF_LOAD_CLASS]*
 //  [[HPROF_FRAME]*|HPROF_TRACE]*
 //  [HPROF_GC_CLASS_DUMP]*
-//  HPROF_HEAP_DUMP
+//  [HPROF_HEAP_DUMP_SEGMENT]*
+//  HPROF_HEAP_DUMP_END
 //
 // The HPROF_TRACE records represent the stack traces where the heap dump
 // is generated and a "dummy trace" record which does not include
 // any frames. The dummy trace record is used to be referenced as the
 // unknown object alloc site.
 //
-// The HPROF_HEAP_DUMP record has a length following by sub-records. To allow
-// the heap dump be generated in a single pass we remember the position of
-// the dump length and fix it up after all sub-records have been written.
+// Each HPROF_HEAP_DUMP_SEGMENT record has a length followed by sub-records.
+// To allow the heap dump be generated in a single pass we remember the position
+// of the dump length and fix it up after all sub-records have been written.
 // To generate the sub-records we iterate over the heap, writing
 // HPROF_GC_INSTANCE_DUMP, HPROF_GC_OBJ_ARRAY_DUMP, and HPROF_GC_PRIM_ARRAY_DUMP
 // records as we go. Once that is done we write records for some of the GC
 // roots.
 

@@ -1750,19 +1799,13 @@
   // At this point we should be the only dumper active, so
   // the following should be safe.
   set_global_dumper();
   set_global_writer();
 
-  // Write the file header - use 1.0.2 for large heaps, otherwise 1.0.1
+  // Write the file header - we always use 1.0.2
   size_t used = ch->used();
-  const char* header;
-  if (used > (size_t)SegmentedHeapDumpThreshold) {
-    set_segmented_dump();
-    header = "JAVA PROFILE 1.0.2";
-  } else {
-    header = "JAVA PROFILE 1.0.1";
-  }
+  const char* header = "JAVA PROFILE 1.0.2";
 
   // header is few bytes long - no chance to overflow int
   writer()->write_raw((void*)header, (int)strlen(header));
   writer()->write_u1(0); // terminator
   writer()->write_u4(oopSize);

@@ -1778,22 +1821,22 @@
 
   // write HPROF_FRAME and HPROF_TRACE records
   // this must be called after _klass_map is built when iterating the classes above.
   dump_stack_traces();
 
-  // write HPROF_HEAP_DUMP or HPROF_HEAP_DUMP_SEGMENT
-  write_dump_header();
+  // write HPROF_HEAP_DUMP_SEGMENT
+  DumperSupport::write_dump_header(writer());
 
   // Writes HPROF_GC_CLASS_DUMP records
   ClassLoaderDataGraph::classes_do(&do_class_dump);
   Universe::basic_type_classes_do(&do_basic_type_array_class_dump);
   check_segment_length();
 
   // writes HPROF_GC_INSTANCE_DUMP records.
-  // After each sub-record is written check_segment_length will be invoked. When
-  // generated a segmented heap dump this allows us to check if the current
-  // segment exceeds a threshold and if so, then a new segment is started.
+  // After each sub-record is written check_segment_length will be invoked
+  // to check if the current segment exceeds a threshold. If so, a new
+  // segment is started.
   // The HPROF_GC_CLASS_DUMP and HPROF_GC_INSTANCE_DUMP are the vast bulk
   // of the heap dump.
   HeapObjectDumper obj_dumper(this, writer());
   Universe::heap()->safe_object_iterate(&obj_dumper);
 

@@ -1815,13 +1858,12 @@
 
   // HPROF_GC_ROOT_STICKY_CLASS
   StickyClassDumper class_dumper(writer());
   SystemDictionary::always_strong_classes_do(&class_dumper);
 
-  // fixes up the length of the dump record. In the case of a segmented
-  // heap then the HPROF_HEAP_DUMP_END record is also written.
-  end_of_dump();
+  // fixes up the length of the dump record and writes the HPROF_HEAP_DUMP_END record.
+  DumperSupport::end_of_dump(writer());
 
   // Now we clear the global variables, so that a future dumper might run.
   clear_global_dumper();
   clear_global_writer();
 }
< prev index next >