/*
* Copyright (c) 2012, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.vm.heap.gcx;
import static com.sun.max.memory.Memory.*;
import com.sun.max.memory.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.MaxineVM.Phase;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.heap.*;
import com.sun.max.vm.heap.gcx.rset.ctbl.*;
import com.sun.max.vm.layout.*;
import com.sun.max.vm.reference.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.type.*;
/**
* Debugging support for dumping a non-empty range of heap space to the log output stream.
* The dump tolerates zapped areas and zero-filled areas, and indicates the range of addresses occupied by these areas.
* The dump also conservatively prints the content of unparseable location in the range where an object is expected, and report
* reference field that don't comprise valid object addresses.
*
* A heap range dumper may be used with @link {@link FatalError#setOnVMOpError(Runnable)} to dump a range of heap responsible for an
* unexpected memory fault. This is typically used to catch corrupted memory due to GC bugs: before iterating over an assumed iterable heap range,
* a heap dumper for the range may be passed to @link {@link FatalError#setOnVMOpError(Runnable)} so that if a memory error occur the
* range is dumped.
*
* A HeapRangeDump may be specified a {@link DumpRangeRefinement} object that can be used to refined the area to be dumped.
* For instance, heap using a cards may specify a refinement of the dumping range by reseting the bounds of the range to the card holding the corrupted
* cell or reference.
* @see RefineDumpRangeToCard
*/
public final class HeapRangeDumper implements Runnable {
public static boolean DumpOnError = false;
static {
VMOptions.addFieldOption("-XX:", "DumpOnError", HeapRangeDumper.class, "Dump faulty heap range on error (Debug mode only)", Phase.PRISTINE);
}
/**
* Handler to refine the range of heap to dump after a first unparseable location is found.
* A handler may be set for a HeapRangeDumper. If the handler is set, and the dumper is in
* "refinement" mode, it first iterates over the range originally specified
* until a first broken heap location is found (e.g., an incorrectly formatted cell, or a zapped area).
* The provided handler is then called to narrow the range to dump, and restart iterating with this new range,
* with dumping on.
*/
public interface DumpRangeRefinement {
void refineRange(HeapRangeDumper heapDumper, Address unparsable);
}
private final Pointer dynamicHubHubPtr;
private final Pointer staticHubHubPtr;
/**
* Bounds of the heap space. Used to conservatively identify potential heap reference.
*/
private MemoryRegion heapBounds;
/**
* Start of the heap range to dump.
*/
private Address start;
/**
* End of the heap range to dump.
*/
private Address end;
/**
* Pre-allocated signal to interrupt heap range iteration.
*/
private final RuntimeException unparsableAddressException;
/**
* When true, prints out references in dumped cells.
*/
private boolean printReferences = true;
/**
* Create an instance of an HeapRangeDumper.
*
* @param heapBounds bounds of the contiguous virtual space range holding all valid heap references.
*/
public HeapRangeDumper(MemoryRegion heapBounds) {
this.heapBounds = heapBounds;
dynamicHubHubPtr = Reference.fromJava(ClassActor.fromJava(DynamicHub.class).dynamicHub()).toOrigin();
staticHubHubPtr = Reference.fromJava(ClassActor.fromJava(StaticHub.class).dynamicHub()).toOrigin();
unparsableAddressException = new RuntimeException();
}
/**
* Set the range of heap to dump.
* This may be reset by a handler.
* @param start
* @param end
*/
public void setRange(Address start, Address end) {
FatalError.check(start.lessThan(end.minus(1)) && heapBounds.contains(start) && heapBounds.contains(end), "Invalid dumping range");
this.start = start;
this.end = end;
}
private boolean isValidHub(Pointer hubOrigin) {
if (heapBounds.contains(hubOrigin)) {
Pointer hubhubPtr = hubOrigin.getReference(Layout.hubIndex()).toOrigin();
return hubhubPtr.equals(dynamicHubHubPtr) || hubhubPtr.equals(staticHubHubPtr);
}
return false;
}
private Pointer skip(Pointer p, long pattern, String patternName) {
final int maxIndex = end.minus(p).unsignedShiftedRight(Word.widthValue().log2numberOfBytes).toInt();
int i = 1;
while (i < maxIndex) {
if (p.getLong(i++) != pattern) {
break;
}
}
Pointer last = p.plusWords(i);
Log.print(patternName);
Log.print(" [");
Log.print(p);
Log.print(",");
Log.print(last);
Log.println("[");
return last;
}
private DumpRangeRefinement handler;
public void refineOnFirstUnparsableWith(DumpRangeRefinement handler) {
this.handler = handler;
}
public void run() {
if (handler != null) {
findFirstUnparsableInRange();
}
dumpRange();
}
private boolean findFirstUnparsableInRange() {
final Pointer zappedMarker = Pointer.fromLong(ZAPPED_MARKER);
try {
Pointer p = start.asPointer();
while (p.lessThan(end)) {
Pointer origin = Layout.cellToOrigin(p);
Pointer hubOrigin = origin.getReference(Layout.hubIndex()).toOrigin();
if (hubOrigin.isZero() || hubOrigin.equals(zappedMarker)) {
handler.refineRange(this, origin);
return true;
}
if (isValidHub(hubOrigin)) {
final Hub hub = UnsafeCast.asHub(Layout.readHubReference(origin).toJava());
if (hub == HeapFreeChunk.heapFreeChunkHub()) {
Size chunkSize = HeapFreeChunk.getFreechunkSize(p);
p = p.plus(chunkSize);
continue;
}
// Cell start
final SpecificLayout specificLayout = hub.specificLayout;
if (specificLayout.isTupleLayout()) {
TupleReferenceMap.visitReferences(hub, origin, checkUnparsableVisitor, start, end);
if (hub.isJLRReference) {
checkUnparsableVisitor.visit(origin, SpecialReferenceManager.referentIndex());
}
p = p.plus(hub.tupleSize);
continue;
}
Size size = Layout.size(origin);
if (specificLayout.isHybridLayout()) {
TupleReferenceMap.visitReferences(hub, origin, checkUnparsableVisitor, start, end);
} else if (specificLayout.isReferenceArrayLayout()) {
visitReferenceArray(origin, checkUnparsableVisitor);
}
p = p.plus(size);
} else {
// non-zero, non-zapped, invalid hub here. Just print addressed pattern if it looked like a heap reference.
handler.refineRange(this, origin);
return true;
}
}
} catch (RuntimeException e) {
if (e == unparsableAddressException) {
return true;
}
}
return false;
}
private void dumpRange() {
final Pointer zappedMarker = Pointer.fromLong(ZAPPED_MARKER);
Pointer p = start.asPointer();
final boolean lockDisabledSafepoints = Log.lock();
while (p.lessThan(end)) {
Pointer origin = Layout.cellToOrigin(p);
Pointer hubOrigin = origin.getReference(Layout.hubIndex()).toOrigin();
if (hubOrigin.equals(zappedMarker)) {
// Skip zap markers.
p = skip(origin, ZAPPED_MARKER, "ZAPPED AREA");
// Assume the first non-zapped location is an object origin, so loop back
continue;
}
if (hubOrigin.isZero()) {
// Skip zeroed out area.
p = skip(origin, 0L, "Zeroed-out AREA");
// Assume the first non-zapped location is an object origin, so loop back
continue;
}
if (isValidHub(hubOrigin)) {
final Hub hub = UnsafeCast.asHub(Layout.readHubReference(origin).toJava());
if (hub == HeapFreeChunk.heapFreeChunkHub()) {
Size chunkSize = HeapFreeChunk.getFreechunkSize(p);
Log.print("HeapFreeChunk"); Log.print(p); Log.print(" (size=");
Log.printToPowerOfTwoUnits(chunkSize);
Log.println(")");
p = p.plus(chunkSize);
continue;
}
// Cell start
Log.print(origin);
final SpecificLayout specificLayout = hub.specificLayout;
if (specificLayout.isTupleLayout()) {
Size size = hub.tupleSize;
Log.print(" T ("); Log.print(hubOrigin); Log.print(", size = "); Log.print(size.toInt()); Log.println(")");
if (printReferences) {
TupleReferenceMap.visitReferences(hub, origin, dumpVisitor, start, end);
if (hub.isJLRReference) {
Log.print("<s> ");
printRef(origin, SpecialReferenceManager.referentIndex());
}
}
p = p.plus(size);
continue;
}
Size size = Layout.size(origin);
if (specificLayout.isHybridLayout()) {
Log.print(" H ("); Log.print(hubOrigin); Log.print(", size = "); Log.print(size.toInt()); Log.println(")");
if (printReferences) {
TupleReferenceMap.visitReferences(hub, origin, dumpVisitor, start, end);
}
} else if (specificLayout.isReferenceArrayLayout()) {
Log.print("AR ("); Log.print(hubOrigin); Log.print(", size = "); Log.print(size.toInt()); Log.println(")");
if (printReferences) {
visitReferenceArray(origin, dumpVisitor);
}
} else {
Log.print("AS ("); Log.print(hubOrigin); Log.print(", size = "); Log.print(size.toInt()); Log.println(")");
}
p = p.plus(size);
} else {
// non-zero, non-zapped, invalid hub here. Just print addressed pattern if it looked like a heap reference.
Log.print(origin);
Log.print(" ->");
if (heapBounds.contains(hubOrigin)) {
Log.print(" ");
Log.print(hubOrigin);
}
Log.println();
p = p.plusWords(1);
}
}
Log.unlock(lockDisabledSafepoints);
}
private void printRef(Pointer pointer, int wordIndex) {
Log.print(pointer.plusWords(wordIndex)); Log.print(" : ");
final Address referencedCell = pointer.getWord(wordIndex).asAddress();
Log.print(referencedCell);
if (!referencedCell.isZero()) {
Log.print(" R = "); Log.print(RegionTable.theRegionTable().regionID(referencedCell));
}
Log.println();
}
PointerIndexVisitor dumpVisitor = new PointerIndexVisitor() {
@Override
public void visit(Pointer pointer, int wordIndex) {
Log.print(" ");
printRef(pointer, wordIndex);
}
};
PointerIndexVisitor checkUnparsableVisitor = new PointerIndexVisitor() {
@Override
public void visit(Pointer pointer, int wordIndex) {
Pointer referencedOrigin = Layout.cellToOrigin(pointer.getWord(wordIndex).asPointer());
if (referencedOrigin.isZero() || referencedOrigin.equals(Pointer.fromLong(ZAPPED_MARKER)) || !heapBounds.contains(referencedOrigin)) {
handler.refineRange(HeapRangeDumper.this, pointer.plusWords(wordIndex));
throw unparsableAddressException;
}
Pointer hubOrigin = referencedOrigin.getWord(Layout.hubIndex()).asPointer();
if (hubOrigin.isZero() || hubOrigin.equals(Pointer.fromLong(ZAPPED_MARKER)) || !isValidHub(hubOrigin)) {
handler.refineRange(HeapRangeDumper.this, pointer.plusWords(wordIndex));
throw unparsableAddressException;
}
}
};
private void visitReferenceArray(Pointer refArrayOrigin, PointerIndexVisitor visitor) {
final int length = Layout.readArrayLength(refArrayOrigin);
final Address firstElementAddr = refArrayOrigin.plusWords(Layout.firstElementIndex());
final Address endOfArrayAddr = firstElementAddr.plusWords(length);
final int firstIndex = firstElementAddr.lessEqual(start) ? start.minus(firstElementAddr).unsignedShiftedRight(Kind.REFERENCE.width.log2numberOfBytes).toInt() : 0;
final int endIndex = endOfArrayAddr.greaterThan(end) ? end.minus(firstElementAddr).unsignedShiftedRight(Kind.REFERENCE.width.log2numberOfBytes).toInt() : length;
for (int i = firstIndex; i < endIndex; i++) {
visitor.visit(refArrayOrigin, i);
}
}
}