/*
* Copyright (c) 2007, 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.debug;
import static com.sun.max.vm.VMConfiguration.*;
import com.sun.max.annotate.*;
import com.sun.max.lang.*;
import com.sun.max.memory.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.code.*;
import com.sun.max.vm.heap.*;
import com.sun.max.vm.heap.SpecialReferenceManager.JLRRAlias;
import com.sun.max.vm.layout.*;
import com.sun.max.vm.layout.Layout.HeaderField;
import com.sun.max.vm.log.VMLog.Record;
import com.sun.max.vm.log.hosted.*;
import com.sun.max.vm.object.*;
import com.sun.max.vm.reference.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.type.*;
/**
* A collection of routines useful for placing and operating on special
* tags in an object heap in aid of debugging a garbage collector in
* a {@linkplain MaxineVM#isDebug() debug} build of the VM.
*
* These methods are common to all schemes.
* TODO: check above assumption.
*/
public class DebugHeap {
@FOLD
public static boolean isTagging() {
return MaxineVM.isDebug() && vmConfig().heapScheme().supportsTagging();
}
@FOLD
public static boolean isPadding() {
return MaxineVM.isDebug() && vmConfig().heapScheme().supportsPadding();
}
@FOLD
private static int hubIndex() {
return Layout.generalLayout().getOffsetFromOrigin(HeaderField.HUB).dividedBy(Word.size()).toInt();
}
/**
* Increments a given allocation mark to reserve space for a {@linkplain #writeCellTag(Pointer) debug tag} if
* this is a {@linkplain MaxineVM#isDebug() debug} VM.
*
* @param mark an address at which a cell will be allocated
* @return the given allocation address increment by 1 word if necessary
*/
@INLINE
public static Pointer adjustForDebugTag(Pointer mark) {
if (isTagging()) {
return mark.plusWords(1);
}
return mark;
}
/**
* Checking the tag a cell.
* A region start address may be supplied for tracing purposes if the cell reside in a specific region of memory.
*
* @param regionStart Address to the first byte of the memory region where the cell is allocated
* @param cell cell whose tag is checked
* @return Address to beginning of the cell stripped off its tag.
*/
@INLINE
public static Pointer checkDebugCellTag(Address regionStart, Pointer cell) {
if (isTagging()) {
if (!isValidCellTag(cell.getWord(0))) {
Log.print("Invalid object tag @ ");
Log.print(cell);
if (!regionStart.isZero()) {
Log.print(" (start + ");
Log.print(cell.minus(regionStart).asOffset().toInt());
Log.print(")");
}
Log.println();
FatalError.unexpected("INVALID CELL TAG");
}
return cell.plusWords(1);
}
return cell;
}
public static boolean isValidCellTag(Word word) {
return word.equals(tagWord());
}
private static final long LONG_OBJECT_TAG = 0xccccddddddddeeeeL;
private static final long LONG_OBJECT_PAD = 0xeeeeddddddddccccL;
private static final int INT_OBJECT_TAG = 0xcccceeee;
private static final int INT_OBJECT_PAD = 0xeeeecccc;
public static byte[] tagBytes(DataModel dataModel) {
return dataModel.wordWidth == WordWidth.BITS_64 ? dataModel.toBytes(LONG_OBJECT_TAG) : dataModel.toBytes(INT_OBJECT_TAG);
}
@INLINE
private static Word tagWord() {
if (Word.width() == 64) {
return Address.fromLong(LONG_OBJECT_TAG);
}
return Address.fromInt(INT_OBJECT_TAG);
}
@INLINE
protected
static Word padWord() {
if (Word.width() == 64) {
return Address.fromLong(LONG_OBJECT_PAD);
}
return Address.fromInt(INT_OBJECT_PAD);
}
public static void writeCellPadding(Pointer start, int words) {
FatalError.check(isPadding(), "Can only be called in debug VM");
Memory.setWords(start, words, padWord());
}
public static int writeCellPadding(Pointer start, Address end) {
FatalError.check(isPadding(), "Can only be called in debug VM");
final int words = end.minus(start).dividedBy(Word.size()).toInt();
Memory.setWords(start, words, padWord());
return words;
}
@INLINE
public static void writeCellTag(Pointer cell) {
if (isTagging()) {
cell.setWord(-1, tagWord());
}
}
private static void checkCellTag(Pointer cell, Word tag) {
if (!isValidCellTag(tag)) {
Log.print("cell: ");
Log.print(cell);
Log.print(" origin: ");
Log.print(Layout.cellToOrigin(cell));
Log.println();
FatalError.unexpected("missing object tag");
}
}
private static void checkRefTag(Reference ref) {
if (isTagging()) {
if (!ref.isZero()) {
final Pointer origin = ref.toOrigin();
final Pointer cell = Layout.originToCell(origin);
checkCellTag(cell, cell.minusWords(1).getWord(0));
}
}
}
public static void checkNonNullRefTag(Reference ref) {
if (isTagging()) {
final Pointer origin = ref.toOrigin();
final Pointer cell = Layout.originToCell(origin);
checkCellTag(cell, cell.minusWords(1).getWord(0));
}
}
public static final class ReferenceFinder extends PointerIndexVisitor implements CellVisitor {
private boolean fatalErrorIfFound = false;
private Address searched;
private long visitedRefsCount;
private long visitedCellsCount;
public ReferenceFinder(boolean fatalErrorIfFound) {
this.fatalErrorIfFound = fatalErrorIfFound;
}
public void setSearchedReference(Address origin) {
this.searched = origin;
this.visitedRefsCount = 0L;
this.visitedCellsCount = 0L;
}
public void setSearchedReference(Address origin, boolean fatalErrorIfFound) {
this.searched = origin;
this.visitedRefsCount = 0L;
this.visitedCellsCount = 0L;
this.fatalErrorIfFound = fatalErrorIfFound;
}
public long visitedRefsCount() {
return visitedRefsCount;
}
public long visitedCellsCount() {
return visitedCellsCount;
}
private void reportFoundRef(Pointer pointer, int wordIndex) {
Log.print("found reference ");
Log.print(searched);
Log.print(" @ ");
Log.print(pointer);
Log.print(" + ");
Log.println(wordIndex);
FatalError.breakpoint();
if (fatalErrorIfFound) {
FatalError.unexpected("unexpected reference");
}
}
@INLINE
private void checkRef(Pointer pointer, int wordIndex) {
visitedRefsCount++;
if (pointer.getWord(wordIndex).asAddress().equals(searched)) {
reportFoundRef(pointer, wordIndex);
}
}
@Override
public void visit(Pointer pointer, int wordIndex) {
checkRef(pointer, wordIndex);
}
@Override
public Pointer visitCell(Pointer cell) {
final Pointer origin = Layout.cellToOrigin(cell);
visitedCellsCount++;
checkRef(origin, Layout.hubIndex());
final Hub hub = Layout.getHub(origin);
final SpecificLayout specificLayout = hub.specificLayout;
if (specificLayout == Layout.tupleLayout()) {
//TupleReferenceMap.visitReferences(hub, origin, this);
hub.visitMappedReferences(origin, this);
if (hub.isJLRReference) {
checkRef(origin, SpecialReferenceManager.referentIndex());
}
return cell.plus(hub.tupleSize);
}
final int length = Layout.readArrayLength(origin);
if (specificLayout == Layout.hybridLayout()) {
hub.visitMappedReferences(origin, this);
//TupleReferenceMap.visitReferences(hub, origin, this);
return cell.plus(Layout.hybridLayout().getArraySize(length));
} else if (specificLayout == Layout.referenceArrayLayout()) {
int wordIndex = Layout.firstElementIndex();
final int endIndex = wordIndex + length;
while (wordIndex < endIndex) {
checkRef(origin, wordIndex++);
}
return cell.plus(Layout.referenceArrayLayout().getArraySize(Kind.REFERENCE, length));
}
return cell.plus(Layout.size(origin));
}
public void visitCells(Address start, Address end) {
Pointer cell = start.asPointer();
do {
cell = visitCell(cell);
} while (cell.lessThan(end));
}
}
/**
* Verifies that a reference points into the bounds of valid heap space.
* This is a gross verification, i.e., it doesn't verify if the reference is actual to a valid object in a live part of the heap.
*/
public static final class RefVerifier extends PointerIndexVisitor {
/**
* An address space in which valid objects can be found apart from the boot {@linkplain Heap#bootHeapRegion heap} and
* {@linkplain Code#bootCodeRegion code} regions. This value is ignored if {@code null}.
*/
MemoryRegion space1;
/**
* A second address space in which valid objects can be found apart from the boot {@linkplain Heap#bootHeapRegion heap} and
* {@linkplain Code#bootCodeRegion code} regions. This value is ignored if {@code null}.
*/
MemoryRegion space2;
/**
* Array holding references values (other than {@code null} that must be excluded from verification.
*/
long [] exclusions;
public RefVerifier() {
}
public RefVerifier(MemoryRegion space) {
this.space1 = space;
}
public RefVerifier(MemoryRegion space1, MemoryRegion space2) {
this.space1 = space1;
this.space2 = space2;
}
/**
* Verifies that a reference value denoted by a given base pointer and index points into a known object address space.
*
* @param address the base pointer of a reference. If this value is {@link Address#zero()}, then both it and {@code
* index} are ignored.
* @param index the offset in words from {@code address} of the reference to be verified
* @param ref the reference to be verified
* @param space1 an address space in which valid objects can be found apart from the boot
* {@linkplain Heap#bootHeapRegion heap} and {@linkplain Code#bootCodeRegion code} regions. This value is
* ignored if null.
* @param space2 another address space in which valid objects can be found apart from the boot
* {@linkplain Heap#bootHeapRegion heap} and {@linkplain Code#bootCodeRegion code} regions. This value is
* ignored if null.
*/
public void verifyRefAtIndex(Address address, int index, Reference ref) {
if (ref.isZero()) {
return;
}
if (isTagging()) {
checkNonNullRefTag(ref);
}
if (CodePointer.isCodePointer(ref)) {
return;
}
final Pointer origin = ref.toOrigin();
if (Heap.bootHeapRegion.contains(origin) || Code.contains(origin) || ImmortalHeap.contains(origin)) {
return;
}
if (space1 != null && space1.contains(origin)) {
return;
}
if (space2 != null && space2.contains(origin)) {
return;
}
if (exclusions != null) {
for (int i = 0; i < exclusions.length; i++) {
if (origin.equals(Pointer.fromLong(exclusions[i]))) {
return;
}
}
}
Log.print("invalid ref: ");
Log.print(origin.asAddress());
if (!address.isZero()) {
Log.print(" @ ");
Log.print(address);
Log.print("+");
Log.print(index * Word.size());
}
Log.println();
FatalError.unexpected("invalid ref");
}
@Override
public void visit(Pointer pointer, int index) {
verifyRefAtIndex(pointer, index, pointer.getReference(index));
}
/**
* Set the verifier to include one extra valid memory region in addition to the boot, code and immortal heap regions.
* This replaces all previous heap space (except for the default one).
* @param space a memory region
*/
public void setValidHeapSpace(MemoryRegion space) {
this.space1 = space;
this.space2 = null;
}
/**
* Set the verifier to include two extra valid memory region in addition to the boot, code and immortal heap regions.
* This replaces all previous heap space (except for the default ones).
* @param space1 a memory region
* @param space2 a memory region
*/
public void setValidSpaces(MemoryRegion space1, MemoryRegion space2) {
this.space1 = space1;
this.space2 = space2;
}
public void setExclusions(long [] exclusions) {
if (exclusions != null) {
for (int i = 0; i < exclusions.length; i++) {
if (exclusions[i] == 0) {
FatalError.unexpected("Can't set null as a special address");
}
}
}
this.exclusions = exclusions;
}
}
protected static Hub checkHub(Pointer origin, RefVerifier refVerifier) {
final Reference hubRef = Layout.readHubReference(origin);
FatalError.check(!hubRef.isZero(), "null hub");
refVerifier.visit(origin, hubIndex());
final Hub hub = UnsafeCast.asHub(hubRef.toJava());
Hub h = hub;
if (h instanceof StaticHub) {
final ClassActor classActor = hub.classActor;
FatalError.check(classActor.staticHub() == h, "lost static hub");
h = ObjectAccess.readHub(h);
}
for (int i = 0; i < 2; i++) {
h = ObjectAccess.readHub(h);
}
FatalError.check(ObjectAccess.readHub(h) == h, "lost hub hub");
return hub;
}
public static boolean isValidNonnullRef(Reference ref) {
if (isTagging()) {
final Pointer origin = ref.toOrigin();
final Pointer cell = Layout.originToCell(origin);
return isValidCellTag(cell.minusWords(1).getWord(0));
}
return true;
}
/**
* Verifies that a given memory region consisting of contiguous objects is well formed.
* The memory region is well formed if each reference embedded in an object points to an address in the given memory region, the boot
* {@linkplain Heap#bootHeapRegion heap} or {@linkplain Code#bootCodeRegion code} region.
* In addition if the heap scheme supports tagging, the memory region is well formed if
* <ul>
* <li>It starts with an object preceded by a debug tag word.</li>
* <li>Each object in the region is immediately succeeded by another object with the preceding debug tag.</li>
* </ul>
* This method doesn't do anything if not called in a {@linkplain MaxineVM#isDebug() debug} VM.
*
* @param region the region being verified
* @param start the start of the memory region to verify
* @param end the end of memory region
* @param verifier a {@link PointerIndexVisitor} instance that will call
* {@link #verifyRefAtIndex(Address, int, Reference, MemoryRegion, MemoryRegion)} for a reference value denoted by a base
* pointer and offset
*/
public static void verifyRegion(MemoryRegion region, Address start, final Address end, RefVerifier verifier, DetailLogger detailLogger) {
if (!MaxineVM.isDebug()) {
return;
}
Pointer cell = start.asPointer();
while (cell.lessThan(end)) {
if (isPadding()) {
cell = skipCellPadding(cell, detailLogger);
}
if (cell.greaterEqual(end)) {
break;
}
cell = checkDebugCellTag(start, cell);
final Pointer origin = Layout.cellToOrigin(cell);
final Hub hub = checkHub(origin, verifier);
if (hub.isJLRReference) {
JLRRAlias refAlias = SpecialReferenceManager.asJLRRAlias(Reference.fromOrigin(origin).toJava());
if (refAlias.discovered != null) {
Log.print("Special reference of type ");
Log.print(hub.classActor.name.string);
Log.print(" at ");
Log.print(cell);
Log.print(" has non-null value for 'discovered' field: ");
Log.println(Reference.fromJava(refAlias.discovered).toOrigin());
FatalError.unexpected("invalid special ref");
}
}
if (detailLogger.enabled()) {
detailLogger.logVerifyObject(hub.classActor, cell, Layout.size(origin).toInt());
}
final SpecificLayout specificLayout = hub.specificLayout;
if (specificLayout.isTupleLayout()) {
TupleReferenceMap.visitReferences(hub, origin, verifier);
cell = cell.plus(hub.tupleSize);
} else {
if (specificLayout.isHybridLayout()) {
TupleReferenceMap.visitReferences(hub, origin, verifier);
} else if (specificLayout.isReferenceArrayLayout()) {
final int length = Layout.readArrayLength(origin);
for (int index = 0; index < length; index++) {
verifier.visit(origin, Layout.firstElementIndex());
}
}
cell = cell.plus(Layout.size(origin));
}
}
}
private static Pointer skipCellPadding(Pointer cell, DetailLogger detailLogger) {
if (isPadding()) {
Pointer cellStart = cell;
while (cell.getWord().equals(DebugHeap.padWord())) {
cell = cell.plusWords(1);
}
if (!cell.equals(cellStart)) {
if (detailLogger.enabled()) {
detailLogger.logSkipped(cell.minus(cellStart).toInt());
}
}
}
return cell;
}
@HOSTED_ONLY
@VMLoggerInterface
private interface DetailLoggerInterface {
void visitCell(
@VMLogParam(name = "cell") Pointer cell);
// Use class ID and not ClassActor since the logger boiler plate will automatically log a class id that it converts back into a class actor name when tracing.
// The latter involves a ClassID.toClassActor method call, a fairly complicated code with virtual call etc., which might come across forwarding reference and crash as a result.
void forward(
@VMLogParam(name = "classID") int classID,
@VMLogParam(name = "at") Pointer at,
@VMLogParam(name = "fromCell") Pointer fromCell,
@VMLogParam(name = "toCell") Pointer toCell,
@VMLogParam(name = "size") int size);
void skipped(
@VMLogParam(name = "padBytes") int padBytes);
void verifyObject(
@VMLogParam(name = "hubClassActor") ClassActor hubClassActor,
@VMLogParam(name = "cell") Pointer cell,
@VMLogParam(name = "size") int size);
}
/**
* A generic logger for moving collector details.
* Provides method for logging visited cells, forwarded objects, etc.
*
*/
public static final class DetailLogger extends DetailLoggerAuto {
public DetailLogger() {
super("GCDetail", "detailed operation.");
}
@Override
public void checkOptions() {
super.checkOptions();
checkDominantLoggerOptions(Heap.gcAllLogger);
}
@Override
protected void traceVisitCell(Pointer cell) {
Log.print("Visiting cell ");
Log.println(cell);
}
@Override
protected void traceForward(int classID, Pointer at, Pointer fromCell, Pointer toCell, int size) {
Log.print("Forwarding <classId=");
Log.print(classID);
Log.print("> @");
Log.print(at);
Log.print(" from ");
Log.print(fromCell);
Log.print(" to ");
Log.print(toCell);
Log.print(" [");
Log.print(size);
Log.println(" bytes]");
}
@Override
protected void traceSkipped(int padBytes) {
Log.print("Skipped ");
Log.print(padBytes);
Log.println(" cell padding bytes");
}
@Override
protected void traceVerifyObject(ClassActor hubClassActor, Pointer cell, int size) {
Log.print("Verifying ");
Log.print(hubClassActor.name.string);
Log.print(" at ");
Log.print(cell);
Log.print(" [");
Log.print(size);
Log.println(" bytes]");
}
}
// START GENERATED CODE
private static abstract class DetailLoggerAuto extends com.sun.max.vm.log.VMLogger {
public enum Operation {
Forward, Skipped, VerifyObject,
VisitCell;
@SuppressWarnings("hiding")
public static final Operation[] VALUES = values();
}
private static final int[] REFMAPS = null;
protected DetailLoggerAuto(String name, String optionDescription) {
super(name, Operation.VALUES.length, optionDescription, REFMAPS);
}
@Override
public String operationName(int opCode) {
return Operation.VALUES[opCode].name();
}
@INLINE
public final void logForward(int classID, Pointer at, Pointer fromCell, Pointer toCell, int size) {
log(Operation.Forward.ordinal(), intArg(classID), at, fromCell, toCell, intArg(size));
}
protected abstract void traceForward(int classID, Pointer at, Pointer fromCell, Pointer toCell, int size);
@INLINE
public final void logSkipped(int padBytes) {
log(Operation.Skipped.ordinal(), intArg(padBytes));
}
protected abstract void traceSkipped(int padBytes);
@INLINE
public final void logVerifyObject(ClassActor hubClassActor, Pointer cell, int size) {
log(Operation.VerifyObject.ordinal(), classActorArg(hubClassActor), cell, intArg(size));
}
protected abstract void traceVerifyObject(ClassActor hubClassActor, Pointer cell, int size);
@INLINE
public final void logVisitCell(Pointer cell) {
log(Operation.VisitCell.ordinal(), cell);
}
protected abstract void traceVisitCell(Pointer cell);
@Override
protected void trace(Record r) {
switch (r.getOperation()) {
case 0: { //Forward
traceForward(toInt(r, 1), toPointer(r, 2), toPointer(r, 3), toPointer(r, 4), toInt(r, 5));
break;
}
case 1: { //Skipped
traceSkipped(toInt(r, 1));
break;
}
case 2: { //VerifyObject
traceVerifyObject(toClassActor(r, 1), toPointer(r, 2), toInt(r, 3));
break;
}
case 3: { //VisitCell
traceVisitCell(toPointer(r, 1));
break;
}
}
}
}
// END GENERATED CODE
}