/*
* Copyright (c) 2008, 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.tele.debug;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import com.sun.max.program.*;
import com.sun.max.tele.*;
import com.sun.max.tele.data.*;
import com.sun.max.tele.memory.*;
import com.sun.max.tele.util.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.heap.*;
import com.sun.max.vm.layout.Layout.HeaderField;
import com.sun.max.vm.thread.*;
import com.sun.max.vm.type.*;
/**
* <strong>Watchpoints</strong>.
* <p>
* A watchpoint triggers <strong>after</strong> a specified event has occurred: read, write, or exec. So-called "before"
* watchpoints are not supported.
* <p>
* Watchpoint creation may fail for platform-specific reasons, for example if watchpoints are not supported at all, or are
* only supported in limited numbers, or only permitted in certain sizes or locations.
* <p>
* A new watchpoint is "alive" and remains so until removed (deleted), at which time it become permanently inert. Any attempt
* to enable or otherwise manipulate a removed watchpoint will cause a TeleError to be thrown.
* <p>
* A watchpoint is by definition "enabled" (client concept) if it is alive and one or more of the three trigger settings
* is true: <strong>trapOnRead</strong>, <strong>trapOnWrite</strong>, or <strong>trapOnExec</strong>.
* If none is true, then the watchpoint is by definition "disabled" and can have no effect on VM execution.
* <p>
* A watchpoint is "active" (implementation concept) if it has been installed in the process running the VM, something that may
* happen when when it is enabled. If a watchpoint becomes disabled, it will be deactivated (removed from the process).
* A watchpoint may also be deactivated/reactivated transparently to the client for implementation purposes.
* <p>
* A watchpoint with <strong>enabledDuringGC</strong> set to false will be effectively disabled during any period of time when
* the VM is performing GC. In practice, the watchpoint may trigger if an event takes place during GC, but execution will then
* resume silently when it is determined that GC is underway. This is true whether the watchpoint is relocatable or not.
* <p>
* A <strong>relocatable</strong> watchpoint is set on a location that is part of an object's representation. Such a watchpoint
* follows the object should its representation be moved during GC to a different location. The life cycle of a relocatable watchpoint
* depends on the state of the object when first created, on the treatment of the object by the GC in the VM, and by the timing
* in which the Inspector is able to update its state in response to GC actions.
* <p>
* A watchpoint may only be created on an object known to the inspector as live (neither dead nor forwarded).
* Attempting to set a watchpoint on an object known to the inspector to be not live will cause a TeleError to be thrown.
* <p>
* A relocatable watchpoint associated with an object that is eventually determined to have been collected will be removed and
* replaced with a non-relocatable watchpoint covering the same memory region.
* <p>
* <strong>Concurrency:</strong> operations that modify a watchpoint must necessarily be implemented by directly affecting
* the VM processes. Such operations fail if they are unable to acquire the VM lock held during the execution of VM commands.
*/
public abstract class VmWatchpoint extends AbstractVmHolder implements VMTriggerEventHandler, MaxWatchpoint {
// TODO (mlvdv) Consider a response when user tries to set a watchpoint on first header word. May mean that
// there can be multiple watchpoints at a location. Work through the use cases.
// Note that system watchpoint code does not check for too many or for overlap.
/**
* Distinguishes among uses for watchpoints, independently of how the location is specified.
*/
private enum WatchpointKind {
/**
* A watchpoint created on behalf of a client external to the VM. Such
* a watchpoint is presumed to be managed completely by the client: creation/deletion,
* enable/disable etc. Only client watchpoints are visible to the client in ordinary use.
*/
CLIENT,
/**
* A watchpoint created by one of the services in the VM, generally in order
* to catch certain events in the VM so that state can be synchronized for
* some purpose. Presumed to be managed completely by the service using it. These
* are generally not visible to clients.
* <p>
* Not relocatable.
*/
SYSTEM;
}
private static final int TRACE_VALUE = 1;
private final WatchpointKind kind;
private final String description;
private volatile TeleFixedMemoryRegion memoryRegion;
/**
* Watchpoints manager.
*/
protected final VmWatchpointManager watchpointManager;
/**
* Is this watchpoint still alive (not yet removed) and available for activation/deactivation?
* This is true from the creation of the watchpoint until it is removed, at which event
* it becomes permanently false and it cannot be used.
*/
private volatile boolean alive = true;
/**
* Is this watchpoint currently active in the process?
* <p>
* This is an implementation issue, which should not be visible to clients.
* <p>
* Only "live" watchpoints may be activated.
*/
private boolean active = false;
/**
* Watchpoint configuration.
*/
private volatile WatchpointSettings settings;
/**
* Stores data read from the memory covered by watchpoint.
*/
private byte[] memoryCache;
private VMTriggerEventHandler triggerEventHandler = VMTriggerEventHandler.Static.ALWAYS_TRUE;
private VmWatchpoint(WatchpointKind kind, VmWatchpointManager watchpointManager, String description, Address start, long nBytes, WatchpointSettings settings) {
super(watchpointManager.vm());
this.kind = kind;
this.watchpointManager = watchpointManager;
this.settings = settings;
this.memoryRegion = new TeleFixedMemoryRegion(vm(), "watchpoint region", start, nBytes);
this.description = description;
}
private VmWatchpoint(WatchpointKind kind, VmWatchpointManager watchpointManager, String description, MaxMemoryRegion memoryRegion, WatchpointSettings settings) {
this(kind, watchpointManager, description, memoryRegion.start(), memoryRegion.nBytes(), settings);
}
public final TeleFixedMemoryRegion memoryRegion() {
return memoryRegion;
}
public final String description() {
return description;
}
@Override
public final boolean equals(Object o) {
// For the purposes of the collection, define ordering and equality in terms of start location only.
if (o instanceof VmWatchpoint) {
final VmWatchpoint watchpoint = (VmWatchpoint) o;
return memoryRegion().start().equals(watchpoint.memoryRegion().start());
}
return false;
}
public final WatchpointSettings getSettings() {
return settings;
}
public final boolean setTrapOnRead(boolean trapOnRead) throws MaxVMBusyException, TeleError {
TeleError.check(alive, "Attempt to modify settings on a removed watchpoint");
if (!vm().tryLock()) {
throw new MaxVMBusyException();
}
boolean success = false;
final WatchpointSettings oldSettings = settings;
try {
this.settings = new WatchpointSettings(trapOnRead, oldSettings.trapOnWrite, oldSettings.trapOnExec, oldSettings.enabledDuringGC);
success = reset();
} finally {
if (!success) {
this.settings = oldSettings;
}
vm().unlock();
}
return success;
}
public final boolean setTrapOnWrite(boolean trapOnWrite) throws MaxVMBusyException, TeleError {
TeleError.check(alive, "Attempt to modify settings on a removed watchpoint");
if (!vm().tryLock()) {
throw new MaxVMBusyException();
}
boolean success = false;
final WatchpointSettings oldSettings = settings;
try {
this.settings = new WatchpointSettings(settings.trapOnRead, trapOnWrite, settings.trapOnExec, settings.enabledDuringGC);
success = reset();
} finally {
if (!success) {
this.settings = oldSettings;
}
vm().unlock();
}
return success;
}
public final boolean setTrapOnExec(boolean trapOnExec) throws MaxVMBusyException, TeleError {
TeleError.check(alive, "Attempt to modify settings on a removed watchpoint");
if (!vm().tryLock()) {
throw new MaxVMBusyException();
}
boolean success = false;
final WatchpointSettings oldSettings = settings;
try {
this.settings = new WatchpointSettings(settings.trapOnRead, settings.trapOnWrite, trapOnExec, settings.enabledDuringGC);
success = reset();
} finally {
if (!success) {
this.settings = oldSettings;
}
vm().unlock();
}
return success;
}
public final boolean setEnabledDuringGC(boolean enabledDuringGC) throws MaxVMBusyException, TeleError {
TeleError.check(alive, "Attempt to modify settings on a removed watchpoint");
if (!vm().tryLock()) {
throw new MaxVMBusyException();
}
boolean success = false;
final WatchpointSettings oldSettings = settings;
try {
this.settings = new WatchpointSettings(settings.trapOnRead, settings.trapOnWrite, settings.trapOnExec, enabledDuringGC);
if (enabledDuringGC && watchpointManager.heap().phase().isCollecting() && !active) {
setActive(true);
}
success = reset();
} finally {
if (!success) {
this.settings = oldSettings;
}
vm().unlock();
}
return success;
}
public final boolean isEnabled() {
final WatchpointSettings settings = this.settings;
return alive && (settings.trapOnRead || settings.trapOnWrite || settings.trapOnExec);
}
public boolean remove() throws MaxVMBusyException, TeleError {
TeleError.check(alive, "Attempt to remove a removed watchpoint");
if (!vm().tryLock()) {
throw new MaxVMBusyException();
}
boolean success = false;
try {
if (active) {
setActive(false);
}
success = watchpointManager.removeWatchpoint(this);
if (success) {
alive = false;
}
} finally {
vm().unlock();
}
return success;
}
public final boolean handleTriggerEvent(TeleNativeThread teleNativeThread) {
assert alive;
assert teleNativeThread.state() == MaxThreadState.WATCHPOINT;
Trace.begin(TRACE_VALUE, tracePrefix() + "handling trigger event for " + this);
if (watchpointManager.heap().phase().isCollecting() && !settings.enabledDuringGC) {
// Ignore the event if the VM is in GC and the watchpoint is not to be enabled during GC.
// This is a lazy policy that avoids the need to interrupt the VM every time GC starts.
// Just in case such a watchpoint would trigger repeatedly during GC, however, deactivate
// it now (at first trigger) for the duration of the GC. All such watchpoints will be
// reactivated at the conclusion of GC, when it is necessary to interrupt the VM anyway.
setActive(false);
Trace.end(TRACE_VALUE, tracePrefix() + "handling trigger event (IGNORED) for " + this);
return false;
}
final boolean handleTriggerEvent = triggerEventHandler.handleTriggerEvent(teleNativeThread);
Trace.end(TRACE_VALUE, tracePrefix() + "handling trigger event for " + this);
return handleTriggerEvent;
}
@Override
public final String toString() {
final StringBuilder sb = new StringBuilder(getClass().getSimpleName());
sb.append("{").append(kind.toString());
if (!alive) {
sb.append("(DELETED)");
}
sb.append(", ").append(isEnabled() ? "enabled" : "disabled");
sb.append(", ").append(isActive() ? "active" : "inactive");
sb.append(", 0x").append(memoryRegion.start().toHexString());
sb.append(", size=").append(memoryRegion.nBytes());
sb.append(", \"").append(description).append("\"");
sb.append("}");
return sb.toString();
}
protected final boolean isAlive() {
return alive;
}
protected final boolean isActive() {
return active;
}
/**
* Assigns to this watchpoint a handler for events triggered by this watchpoint. A null handler
* is equivalent to there being no handling action and a return of true (VM execution should halt).
*
* @param triggerEventHandler handler for VM execution events triggered by this watchpoint.
*/
protected final void setTriggerEventHandler(VMTriggerEventHandler triggerEventHandler) {
this.triggerEventHandler =
(triggerEventHandler == null) ? VMTriggerEventHandler.Static.ALWAYS_TRUE : triggerEventHandler;
}
/**
* Reads and stores the contents of VM memory in the region of the watchpoint.
*
* Future usage: e.g. for conditional Watchpoints
*/
private void updateMemoryCache() {
long nBytes = memoryRegion().nBytes();
assert nBytes < Integer.MAX_VALUE;
if (memoryCache == null || memoryCache.length != nBytes) {
memoryCache = new byte[(int) nBytes];
}
try {
memoryCache = watchpointManager.vm().memoryIO().readBytes(memoryRegion().start(), (int) nBytes);
} catch (DataIOError e) {
// Must be a watchpoint in an address space that doesn't (yet?) exist in the VM process.
memoryCache = null;
}
}
private void setStart(Address start) {
memoryRegion = new TeleFixedMemoryRegion(vm(), "", start, memoryRegion.nBytes());
}
/**
* Change the activation state of the watchpoint in the VM.
*
* @param active the desired activation state
* @return whether the change succeeded
* @throws TeleError if requested state same as current state
*/
private boolean setActive(boolean active) {
assert alive;
if (active) { // Try to activate
TeleError.check(!this.active, "Attempt to activate an active watchpoint:", this);
if (watchpointManager.teleProcess.activateWatchpoint(this)) {
this.active = true;
Trace.line(TRACE_VALUE, tracePrefix() + "Watchpoint activated: " + this);
return true;
} else {
TeleWarning.message("Failed to activate watchpoint: " + this);
return false;
}
} else { // Try to deactivate
TeleError.check(this.active, "Attempt to deactivate an inactive watchpoint:", this);
if (watchpointManager.teleProcess.deactivateWatchpoint(this)) {
this.active = false;
Trace.line(TRACE_VALUE, tracePrefix() + "Watchpoint deactivated " + this);
return true;
}
TeleWarning.message("Failed to deactivate watchpoint: " + this);
return false;
}
}
/**
* Resets a watchpoint by deactivating it and then reactivating at the same
* location. This should be done when any settings change.
*
* @return true if reset was successful
*/
private boolean reset() {
assert alive;
if (active) {
if (!setActive(false)) {
TeleWarning.message("Failed to reset watchpoint: " + this);
return false;
}
if (!setActive(true)) {
TeleWarning.message("Failed to reset and install watchpoint: " + this);
return false;
}
}
Trace.line(TRACE_VALUE, tracePrefix() + "Watchpoint reset " + this);
watchpointManager.updateAfterWatchpointChanges();
return true;
}
/**
* Relocates a watchpoint by deactivating it and then reactivating
* at a new start location.
* <p>
* Note that the location of a watchpoint should <strong>only</strong> be changed
* via this method, since it must first be deactivated at its old location before the
* new location is set.
*
* @param newAddress a new starting location for the watchpoint
* @return true if reset was successful
*/
protected final boolean relocate(Address newAddress) {
assert newAddress != null;
assert alive;
if (active) {
// Must deactivate before we change the location
if (!setActive(false)) {
TeleWarning.message("Failed to reset watchpoint: " + this);
return false;
}
setStart(newAddress);
if (!setActive(true)) {
TeleWarning.message("Failed to reset and install watchpoint: " + this);
return false;
}
} else { // not active
setStart(newAddress);
}
Trace.line(TRACE_VALUE, tracePrefix() + "Watchpoint reset " + memoryRegion().start().toHexString());
watchpointManager.updateAfterWatchpointChanges();
return true;
}
/**
* Perform any updates on watchpoint state at the conclusion of a GC.
*/
protected void updateAfterGC() {
if (isEnabled() && !active) {
// This watchpoint was apparently deactivated during GC because
// it is not to be enabled during GC.
setActive(true);
}
}
/**
* A watchpoint for a specified, fixed memory region.
*/
private static final class TeleRegionWatchpoint extends VmWatchpoint {
private TeleRegionWatchpoint(WatchpointKind kind, VmWatchpointManager watchpointManager, String description, MaxMemoryRegion memoryRegion, WatchpointSettings settings) {
super(kind, watchpointManager, description, memoryRegion.start(), memoryRegion.nBytes(), settings);
}
public boolean isRelocatable() {
return false;
}
public MaxObject getWatchedObject() {
return null;
}
}
/**
* A watchpoint for the memory holding a {@linkplain VmThreadLocal thread local variable}.
*
* @see VmThreadLocal
*/
private static final class TeleVmThreadLocalWatchpoint extends VmWatchpoint {
private TeleVmThreadLocalWatchpoint(WatchpointKind kind, VmWatchpointManager watchpointManager, String description, MaxThreadLocalVariable threadLocalVariable, WatchpointSettings settings) {
super(kind, watchpointManager, description, threadLocalVariable.memoryRegion(), settings);
}
public boolean isRelocatable() {
return false;
}
public MaxObject getWatchedObject() {
return null;
}
}
/**
* Abstraction for watchpoints covering some or all of an object, and which will
* be relocated to follow the absolute location of the object whenever it is relocated by GC.
*
*/
private abstract static class ObjectWatchpoint extends VmWatchpoint {
/**
* Watchpoint settings to use when a system watchpoint is placed on the field
* to which a forwarding pointer gets written, designed to catch the relocation
* of this specific object.
*/
private static final WatchpointSettings relocationWatchpointSettings = new WatchpointSettings(false, true, false, true);
/**
* The VM heap object on which this watchpoint is set.
*/
private MaxObject object;
/**
* Starting location of the watchpoint, relative to the origin of the object.
*/
private final int offset;
/**
* A hidden (system) watchpoint set on the object's field that the GC uses to store forwarding
* pointers.
*/
private VmWatchpoint relocationWatchpoint = null;
private ObjectWatchpoint(WatchpointKind kind, VmWatchpointManager watchpointManager, String description, MaxObject object, int offset, long nBytes, WatchpointSettings settings)
throws MaxWatchpointManager.MaxTooManyWatchpointsException, MaxWatchpointManager.MaxDuplicateWatchpointException {
super(kind, watchpointManager, description, object.origin().plus(offset), nBytes, settings);
TeleError.check(object.status().isLive(), "Attempt to set an object-based watchpoint on an object that is not live: ", object);
this.object = object;
this.offset = offset;
setRelocationWatchpoint(object);
}
/**
* Sets a watchpoint on the area of the object where GC writes a forwarding pointer; when
* triggered, the watchpoint relocates this watchpoint as well as itself to the new location
* identified by the forwarding pointer.
*
* @param object the object origin for this watchpoint
* @throws MaxWatchpointManager.MaxTooManyWatchpointsException
*/
private void setRelocationWatchpoint(final MaxObject object) throws MaxWatchpointManager.MaxTooManyWatchpointsException {
final MaxVM vm = watchpointManager.vm();
final Address forwardingPointerAddress = objects().getForwardingPointerAddress(object);
final TeleFixedMemoryRegion forwardPointerRegion = new TeleFixedMemoryRegion(vm(), "Forwarding pointer for object relocation watchpoint", forwardingPointerAddress, vm.platform().nBytesInWord());
relocationWatchpoint = watchpointManager.createSystemWatchpoint("Object relocation watchpoint", forwardPointerRegion, relocationWatchpointSettings);
relocationWatchpoint.setTriggerEventHandler(new VMTriggerEventHandler() {
public boolean handleTriggerEvent(TeleNativeThread teleNativeThread) {
final ObjectWatchpoint thisWatchpoint = ObjectWatchpoint.this;
final Address origin = object.origin();
if (objects().hasForwardingAddressUnsafe(origin)) {
final MaxObject newObject = objects().findObjectAt(objects().getForwardingAddressUnsafe(origin));
if (newObject == null) {
TeleWarning.message("Unlable to find relocated object" + this);
} else {
ObjectWatchpoint.this.object = newObject;
final Pointer newWatchpointStart = newObject.origin().plus(thisWatchpoint.offset);
Trace.line(TRACE_VALUE, thisWatchpoint.tracePrefix() + " relocating watchpoint " + thisWatchpoint.memoryRegion().start().toHexString() + "-->" + newWatchpointStart.toHexString());
thisWatchpoint.relocate(newWatchpointStart);
// Now replace this relocation watchpoint for the next time the objects gets moved.
thisWatchpoint.clearRelocationWatchpoint();
try {
thisWatchpoint.setRelocationWatchpoint(newObject);
} catch (MaxWatchpointManager.MaxTooManyWatchpointsException maxTooManyWatchpointsException) {
TeleError.unexpected(thisWatchpoint.tracePrefix() + " failed to relocate the relocation watchpoint for " + thisWatchpoint);
}
}
} else {
Trace.line(TRACE_VALUE, thisWatchpoint.tracePrefix() + " relocating watchpoint (IGNORED) 0x" + thisWatchpoint.memoryRegion().start().toHexString());
}
return false;
}
});
relocationWatchpoint.setActive(true);
}
/**
* Clears the watchpoint, if any, set on the area of the object where GC writes a forwarding pointer.
*/
private void clearRelocationWatchpoint() {
if (relocationWatchpoint != null) {
try {
relocationWatchpoint.remove();
} catch (MaxVMBusyException maxVMBusyException) {
TeleError.unexpected("Should only be called with lock held");
} catch (TeleError teleError) {
TeleError.unexpected("Attempt to remove an already removed object relocation watchpoint");
}
relocationWatchpoint = null;
}
}
@Override
public boolean remove() throws MaxVMBusyException, TeleError {
if (!vm().tryLock()) {
throw new MaxVMBusyException();
}
boolean success = false;
try {
clearRelocationWatchpoint();
success = super.remove();
} finally {
vm().unlock();
}
return success;
}
public final boolean isRelocatable() {
return true;
}
public final MaxObject getWatchedObject() {
assert isAlive();
return object;
}
@Override
protected void updateAfterGC() {
assert isAlive();
super.updateAfterGC();
// TODO (mlvdv) watchpoint on forwarded object?
switch(object.status()) {
case LIVE:
// A relocatable watchpoint on a live object should have been relocated
// (eagerly) just as the relocation took place. Check that the locations match.
if (!object.objectMemoryRegion().start().plus(offset).equals(memoryRegion().start())) {
TeleWarning.message("Watchpoint relocation failure - watchpoint on live object at wrong location " + this);
}
break;
case DEAD:
// The watchpoint's object has been collected; convert it to a fixed memory region watchpoint
try {
remove();
final TeleFixedMemoryRegion watchpointRegion = new TeleFixedMemoryRegion(vm(), "Old memory location of watched object", memoryRegion().start(), memoryRegion().nBytes());
final VmWatchpoint newRegionWatchpoint =
watchpointManager.createRegionWatchpoint("Replacement for watchpoint on GC'd object", watchpointRegion, getSettings());
Trace.line(TRACE_VALUE, tracePrefix() + "Watchpoint on collected object replaced: " + newRegionWatchpoint);
} catch (MaxWatchpointManager.MaxTooManyWatchpointsException maxTooManyWatchpointsException) {
TeleWarning.message("Failed to replace object watchpoint with region watchpoint", maxTooManyWatchpointsException);
} catch (MaxWatchpointManager.MaxDuplicateWatchpointException maxDuplicateWatchpointException) {
TeleWarning.message("Failed to replace object watchpoint with region watchpoint", maxDuplicateWatchpointException);
} catch (MaxVMBusyException maxVMBusyException) {
TeleError.unexpected("Should only be called on request handling thread, where lock acquision should not fail");
}
}
}
}
/**
* A watchpoint for a whole object.
*/
private static final class WholeObjectWatchpoint extends ObjectWatchpoint {
private WholeObjectWatchpoint(WatchpointKind kind, VmWatchpointManager watchpointManager, String description, MaxObject object, WatchpointSettings settings)
throws MaxWatchpointManager.MaxTooManyWatchpointsException, MaxWatchpointManager.MaxDuplicateWatchpointException {
super(kind, watchpointManager, description, object, 0, object.objectMemoryRegion().nBytes(), settings);
}
}
/**
* A watchpoint for the memory holding an object's field.
*/
private static final class FieldWatchpoint extends ObjectWatchpoint {
private FieldWatchpoint(WatchpointKind kind, VmWatchpointManager watchpointManager, String description, MaxObject object, FieldActor fieldActor, WatchpointSettings settings)
throws MaxWatchpointManager.MaxTooManyWatchpointsException, MaxWatchpointManager.MaxDuplicateWatchpointException {
super(kind, watchpointManager, description, object, fieldActor.offset(), object.fieldMemoryRegion(fieldActor).nBytes(), settings);
}
}
/**
*A watchpoint for the memory holding an array element.
*/
private static final class ArrayElementWatchpoint extends ObjectWatchpoint {
private ArrayElementWatchpoint(WatchpointKind kind, VmWatchpointManager watchpointManager, String description, MaxObject object, Kind elementKind, int arrayOffsetFromOrigin, int index, WatchpointSettings settings)
throws MaxWatchpointManager.MaxTooManyWatchpointsException, MaxWatchpointManager.MaxDuplicateWatchpointException {
super(kind, watchpointManager, description, object, arrayOffsetFromOrigin + (index * elementKind.width.numberOfBytes), elementKind.width.numberOfBytes, settings);
}
}
/**
* A watchpoint for the memory holding an object's header field.
*/
private static final class HeaderWatchpoint extends ObjectWatchpoint {
private HeaderWatchpoint(WatchpointKind kind, VmWatchpointManager watchpointManager, String description, MaxObject object, HeaderField headerField, WatchpointSettings settings)
throws MaxWatchpointManager.MaxTooManyWatchpointsException, MaxWatchpointManager.MaxDuplicateWatchpointException {
super(kind, watchpointManager, description, object, object.headerOffset(headerField), object.headerMemoryRegion(headerField).nBytes(), settings);
}
}
/**
* Singleton manager for creating and managing process watchpoints.
* <p>
* Overlapping watchpoints are not permitted.
*
*/
public static final class VmWatchpointManager extends AbstractVmHolder implements MaxWatchpointManager {
private static VmWatchpointManager vmWatchpointManager;
public static VmWatchpointManager make(TeleVM vm, TeleProcess teleProcess) {
if (vmWatchpointManager == null) {
vmWatchpointManager = new VmWatchpointManager(vm, teleProcess);
}
return vmWatchpointManager;
}
private final TeleProcess teleProcess;
private final Comparator<VmWatchpoint> watchpointComparator = new Comparator<VmWatchpoint>() {
public int compare(VmWatchpoint o1, VmWatchpoint o2) {
// For the purposes of the collection, define equality and comparison to be based
// exclusively on starting address.
return o1.memoryRegion().start().compareTo(o2.memoryRegion().start());
}
};
// This implementation is not thread-safe; this manager must take care of that.
// Keep the set ordered by start address only, implemented by the comparator and equals().
// An additional constraint imposed by this manager is that no regions overlap,
// either in part or whole, with others in the set.
private final TreeSet<VmWatchpoint> clientWatchpoints = new TreeSet<VmWatchpoint>(watchpointComparator);
/**
* A thread-safe, immutable collection of the current watchpoint list.
* This list will be read many, many more times than it will change.
*/
private volatile List<MaxWatchpoint> clientWatchpointsCache = Collections.emptyList();
// Watchpoints used for internal purposes, for example for GC and relocation services
private final TreeSet<VmWatchpoint> systemWatchpoints = new TreeSet<VmWatchpoint>(watchpointComparator);
private volatile List<MaxWatchpoint> systemWatchpointsCache = Collections.emptyList();
/**
* A listener for GC completions, whenever there are any watchpoints; null when no watchpoints.
*/
private MaxGCPhaseListener gcCompletedListener = null;
private volatile List<MaxWatchpointListener> watchpointListeners = new CopyOnWriteArrayList<MaxWatchpointListener>();
/**
* Creates a manager for creating and managing watchpoints in the VM.
*
*/
private VmWatchpointManager(TeleVM vm, TeleProcess teleProcess) {
super(vm);
this.teleProcess = teleProcess;
vm().addVMStateListener(new MaxVMStateListener() {
public void stateChanged(MaxVMState maxVMState) {
if (maxVMState.processState() == MaxProcessState.TERMINATED) {
clientWatchpoints.clear();
systemWatchpoints.clear();
updateAfterWatchpointChanges();
}
}
});
}
/**
* Adds a listener for watchpoint changes.
* <p>
* Thread-safe
*
* @param listener a watchpoint listener
*/
public void addListener(MaxWatchpointListener listener) {
assert listener != null;
watchpointListeners.add(listener);
}
/**
* Removes a listener for watchpoint changes.
* <p>
* Thread-safe
*
* @param listener a watchpoint listener
*/
public void removeListener(MaxWatchpointListener listener) {
assert listener != null;
watchpointListeners.remove(listener);
}
/**
* Creates a new, active watchpoint that covers a given memory region in the VM.
* <p>
* The trigger occurs <strong>after</strong> the specified event.
*
* @param description text useful to a person, for example capturing the intent of the watchpoint
* @param memoryRegion the region of memory in the VM to be watched.
* @param settings initial settings for the watchpoint
*
* @return a new watchpoint, if successful
* @throws MaxWatchpointManager.MaxTooManyWatchpointsException if setting a watchpoint would exceed a platform-specific limit
* @throws MaxWatchpointManager.MaxDuplicateWatchpointException if the region overlaps, in part or whole, with an existing watchpoint.
* @throws MaxVMBusyException if watchpoints cannot be set at present, presumably because the VM is running.
*/
public VmWatchpoint createRegionWatchpoint(String description, MaxMemoryRegion memoryRegion, WatchpointSettings settings)
throws MaxWatchpointManager.MaxTooManyWatchpointsException, MaxWatchpointManager.MaxDuplicateWatchpointException, MaxVMBusyException {
if (!vm().tryLock()) {
throw new MaxVMBusyException();
}
VmWatchpoint watchpoint;
try {
watchpoint = new TeleRegionWatchpoint(WatchpointKind.CLIENT, this, description, memoryRegion, settings);
watchpoint = addClientWatchpoint(watchpoint);
} finally {
vm().unlock();
}
return watchpoint;
}
/**
* Creates a new, active watchpoint that covers an entire heap object's memory in the VM.
* @param description text useful to a person, for example capturing the intent of the watchpoint
* @param object a heap object in the VM
* @param settings initial settings for the watchpoint
* @return a new watchpoint, if successful
* @throws MaxWatchpointManager.MaxTooManyWatchpointsException if setting a watchpoint would exceed a platform-specific limit
* @throws MaxWatchpointManager.MaxDuplicateWatchpointException if the region overlaps, in part or whole, with an existing watchpoint.
* @throws MaxVMBusyException if watchpoints cannot be set at present, presumably because the VM is running.
*/
public VmWatchpoint createObjectWatchpoint(String description, MaxObject object, WatchpointSettings settings)
throws MaxWatchpointManager.MaxTooManyWatchpointsException, MaxWatchpointManager.MaxDuplicateWatchpointException, MaxVMBusyException {
if (!vm().tryLock()) {
throw new MaxVMBusyException();
}
VmWatchpoint watchpoint;
try {
if (object.status().isNotDead()) {
watchpoint = new WholeObjectWatchpoint(WatchpointKind.CLIENT, this, description, object, settings);
} else {
String amendedDescription = (description == null) ? "" : description;
amendedDescription = amendedDescription + " (non-live object))";
final TeleFixedMemoryRegion region = object.objectMemoryRegion();
watchpoint = new TeleRegionWatchpoint(WatchpointKind.CLIENT, this, amendedDescription, region, settings);
}
watchpoint = addClientWatchpoint(watchpoint);
} finally {
vm().unlock();
}
return watchpoint;
}
/**
* Creates a new, active watchpoint that covers a heap object's field in the VM. If the object is live,
* than this watchpoint will track the object's location during GC.
* <p>
* If the object is not live, a plain memory region watchpoint is returned, one that does not relocate.
*
* @param description text useful to a person, for example capturing the intent of the watchpoint
* @param object a heap object in the VM
* @param fieldActor description of a field in object of that type
* @param settings initial settings for the watchpoint
* @return a new watchpoint, if successful
* @throws MaxWatchpointManager.MaxTooManyWatchpointsException if setting a watchpoint would exceed a platform-specific limit
* @throws MaxWatchpointManager.MaxDuplicateWatchpointException if the region overlaps, in part or whole, with an existing watchpoint.
* @throws MaxVMBusyException if watchpoints cannot be set at present, presumably because the VM is running.
*/
public VmWatchpoint createFieldWatchpoint(String description, MaxObject object, FieldActor fieldActor, WatchpointSettings settings)
throws MaxWatchpointManager.MaxTooManyWatchpointsException, MaxWatchpointManager.MaxDuplicateWatchpointException, MaxVMBusyException {
if (!vm().tryLock()) {
throw new MaxVMBusyException();
}
VmWatchpoint watchpoint;
try {
if (object.status().isNotDead()) {
watchpoint = new FieldWatchpoint(WatchpointKind.CLIENT, this, description, object, fieldActor, settings);
} else {
String amendedDescription = (description == null) ? "" : description;
amendedDescription = amendedDescription + " (non-live object))";
final TeleFixedMemoryRegion region = object.fieldMemoryRegion(fieldActor);
watchpoint = new TeleRegionWatchpoint(WatchpointKind.CLIENT, this, amendedDescription, region, settings);
}
watchpoint = addClientWatchpoint(watchpoint);
} finally {
vm().unlock();
}
return watchpoint;
}
/**
* Creates a new, active watchpoint that covers an element in an array in the VM.
*
* @param description text useful to a person, for example capturing the intent of the watchpoint
* @param object a heap object in the VM that contains the array
* @param elementKind the type category of the array elements
* @param arrayOffsetFromOrigin location relative to the object's origin of element 0 in the array
* @param index index of the element to watch
* @param settings initial settings for the watchpoint
* @return a new watchpoint, if successful
* @throws MaxWatchpointManager.MaxTooManyWatchpointsException if setting a watchpoint would exceed a platform-specific limit
* @throws MaxWatchpointManager.MaxDuplicateWatchpointException if the region overlaps, in part or whole, with an existing watchpoint.
* @throws MaxVMBusyException if watchpoints cannot be set at present, presumably because the VM is running.
*/
public VmWatchpoint createArrayElementWatchpoint(String description, MaxObject object, Kind elementKind, int arrayOffsetFromOrigin, int index, WatchpointSettings settings)
throws MaxWatchpointManager.MaxTooManyWatchpointsException, MaxWatchpointManager.MaxDuplicateWatchpointException, MaxVMBusyException {
if (!vm().tryLock()) {
throw new MaxVMBusyException();
}
VmWatchpoint watchpoint;
try {
if (object.status().isNotDead()) {
watchpoint = new ArrayElementWatchpoint(WatchpointKind.CLIENT, this, description, object, elementKind, arrayOffsetFromOrigin, index, settings);
} else {
String amendedDescription = (description == null) ? "" : description;
amendedDescription = amendedDescription + " (non-live object))";
final Pointer address = object.origin().plus(arrayOffsetFromOrigin + (index * elementKind.width.numberOfBytes));
final TeleFixedMemoryRegion region = new TeleFixedMemoryRegion(vm(), "", address, elementKind.width.numberOfBytes);
watchpoint = new TeleRegionWatchpoint(WatchpointKind.CLIENT, this, amendedDescription, region, settings);
}
watchpoint = addClientWatchpoint(watchpoint);
} finally {
vm().unlock();
}
return watchpoint;
}
/**
* Creates a new, active watchpoint that covers a field in an object's header in the VM. If the object is live,
* than this watchpoint will track the object's location during GC.
* <p>
* If the object is not live, a plain memory region watchpoint is returned, one that does not relocate.
*
* @param description text useful to a person, for example capturing the intent of the watchpoint
* @param object a heap object in the VM
* @param headerField a field in the object's header
* @param settings initial settings for the watchpoint
* @return a new watchpoint, if successful
* @throws MaxWatchpointManager.MaxTooManyWatchpointsException if setting a watchpoint would exceed a platform-specific limit
* @throws MaxWatchpointManager.MaxDuplicateWatchpointException if the region overlaps, in part or whole, with an existing watchpoint.
* @throws MaxVMBusyException if watchpoints cannot be set at present, presumably because the VM is running.
*/
public VmWatchpoint createHeaderWatchpoint(String description, MaxObject object, HeaderField headerField, WatchpointSettings settings)
throws MaxWatchpointManager.MaxTooManyWatchpointsException, MaxWatchpointManager.MaxDuplicateWatchpointException, MaxVMBusyException {
if (!vm().tryLock()) {
throw new MaxVMBusyException();
}
VmWatchpoint watchpoint;
try {
if (object.status().isNotDead()) {
watchpoint = new HeaderWatchpoint(WatchpointKind.CLIENT, this, description, object, headerField, settings);
} else {
String amendedDescription = (description == null) ? "" : description;
amendedDescription = amendedDescription + " (non-live object)";
final TeleFixedMemoryRegion region = object.headerMemoryRegion(headerField);
watchpoint = new TeleRegionWatchpoint(WatchpointKind.CLIENT, this, amendedDescription, region, settings);
}
watchpoint = addClientWatchpoint(watchpoint);
} finally {
vm().unlock();
}
return watchpoint;
}
/**
* Creates a new, active watchpoint that covers a thread local variable in the VM.
*
* @param description text useful to a person, for example capturing the intent of the watchpoint
* @param threadLocalVariable a thread local variable in the VM
* @param settings initial settings for the watchpoint
* @return a new watchpoint, if successful
* @throws MaxWatchpointManager.MaxTooManyWatchpointsException if setting a watchpoint would exceed a platform-specific limit
* @throws MaxWatchpointManager.MaxDuplicateWatchpointException if the region overlaps, in part or whole, with an existing watchpoint.
* @throws MaxVMBusyException if watchpoints cannot be set at present, presumably because the VM is running.
*/
public VmWatchpoint createVmThreadLocalWatchpoint(String description, MaxThreadLocalVariable threadLocalVariable, WatchpointSettings settings)
throws MaxWatchpointManager.MaxTooManyWatchpointsException, MaxWatchpointManager.MaxDuplicateWatchpointException, MaxVMBusyException {
if (!vm().tryLock(100)) {
throw new MaxVMBusyException();
}
VmWatchpoint watchpoint;
try {
watchpoint = new TeleVmThreadLocalWatchpoint(WatchpointKind.CLIENT, this, description, threadLocalVariable, settings);
watchpoint = addClientWatchpoint(watchpoint);
} finally {
vm().unlock();
}
return watchpoint;
}
/**
* Find existing <strong>client</strong> watchpoints in the VM by location.
* <p>
* Returns an immutable collection; membership is thread-safe
*
* @param memoryRegion a memory region in the VM
* @return all watchpoints whose memory regions overlap the specified region, empty sequence if none.
*/
public List<MaxWatchpoint> findWatchpoints(MaxMemoryRegion memoryRegion) {
List<MaxWatchpoint> watchpoints = Collections.emptyList();
for (MaxWatchpoint maxWatchpoint : clientWatchpointsCache) {
if (maxWatchpoint.memoryRegion().overlaps(memoryRegion)) {
if (watchpoints.isEmpty()) {
watchpoints = new ArrayList<MaxWatchpoint>(1);
}
watchpoints.add(maxWatchpoint);
}
}
return Collections.unmodifiableList(watchpoints);
}
/**
* Find an existing client watchpoint set in the VM.
* <p>
* Thread-safe
*
* @param address a memory address in the VM
* @return the watchpoint whose memory region includes the address, null if none.
*/
VmWatchpoint findClientWatchpointContaining(Address address) {
for (MaxWatchpoint maxWatchpoint : clientWatchpointsCache) {
if (maxWatchpoint.memoryRegion().contains(address)) {
return (VmWatchpoint) maxWatchpoint;
}
}
return null;
}
public List<MaxWatchpoint> watchpoints() {
// Hand out the cached, thread-safe summary
return clientWatchpointsCache;
}
/**
* Creates a new, inactive system watchpoint. This watchpoint is not shown in the list of current watchpoints.
* This watchpoint has to be explicitly activated.
*
* @param description a human-readable description of the watchpoint's purpose, for debugging.
* @param memoryRegion the memory region to watch.
* @param settings initial settings for the watchpoint
* @return a new, inactive system watchpoint
* .
* @throws MaxWatchpointManager.MaxTooManyWatchpointsException
*/
private VmWatchpoint createSystemWatchpoint(String description, TeleFixedMemoryRegion memoryRegion, WatchpointSettings settings) throws MaxWatchpointManager.MaxTooManyWatchpointsException {
final VmWatchpoint watchpoint = new TeleRegionWatchpoint(WatchpointKind.SYSTEM, this, description, memoryRegion, settings);
return addSystemWatchpoint(watchpoint);
}
/**
* Find an system watchpoint set at a particular location.
* <p>
* Thread-safe
*
* @param address a location in VM memory
* @return a system watchpoint, null if none exists at the address.
*/
public VmWatchpoint findSystemWatchpoint(Address address) {
for (MaxWatchpoint maxWatchpoint : systemWatchpointsCache) {
if (maxWatchpoint.memoryRegion().contains(address)) {
return (VmWatchpoint) maxWatchpoint;
}
}
return null;
}
/**
* Updates the watchpoint caches of memory contents.
*/
void updateWatchpointMemoryCaches() {
for (VmWatchpoint watchpoint : clientWatchpoints) {
watchpoint.updateMemoryCache();
}
}
/**
* @return total number of existing watchpoints of all kinds.[
*/
private int watchpointCount() {
return clientWatchpoints.size() + systemWatchpoints.size();
}
private void updateAfterWatchpointChanges() {
clientWatchpointsCache = Collections.unmodifiableList(new ArrayList<MaxWatchpoint>(clientWatchpoints));
systemWatchpointsCache = new ArrayList<MaxWatchpoint>(systemWatchpoints);
// Ensure that the manager listens for GC completion events iff
// there are watchpoints.
if (watchpointCount() > 0) {
if (gcCompletedListener == null) {
gcCompletedListener = new MaxGCPhaseListener() {
public void gcPhaseChange(HeapPhase phase) {
for (MaxWatchpoint maxWatchpoint : clientWatchpointsCache) {
final VmWatchpoint watchpoint = (VmWatchpoint) maxWatchpoint;
Trace.line(TRACE_VALUE, watchpoint.tracePrefix() + "updating after GC: " + watchpoint);
watchpoint.updateAfterGC();
}
}
};
try {
vm().addGCPhaseListener(HeapPhase.MUTATING, gcCompletedListener);
} catch (MaxVMBusyException maxVMBusyException) {
TeleWarning.message("update after watchpoint changes failed to set GC completed listener", maxVMBusyException);
gcCompletedListener = null;
}
}
} else { // no watchpoints
if (gcCompletedListener != null) {
try {
vm().removeGCPhaseListener(gcCompletedListener, HeapPhase.MUTATING);
} catch (MaxVMBusyException maxVMBusyException) {
TeleWarning.message("update after watchpoint changes failed to remove GC completed listener", maxVMBusyException);
gcCompletedListener = null;
}
gcCompletedListener = null;
}
}
for (final MaxWatchpointListener listener : watchpointListeners) {
listener.watchpointsChanged();
}
}
/**
* Adds a watchpoint to the list of current client watchpoints, and activates this watchpoint.
* <p>
* If the addition fails, the watchpoint is not activated.
*
* @param watchpoint the new client watchpoint, presumed to be inactive and not to have been added before.
* @return the watchpoint, null if failed to create for some reason
* @throws MaxWatchpointManager.MaxTooManyWatchpointsException
* @throws MaxWatchpointManager.MaxDuplicateWatchpointException
*/
private VmWatchpoint addClientWatchpoint(VmWatchpoint watchpoint) throws MaxWatchpointManager.MaxTooManyWatchpointsException, MaxWatchpointManager.MaxDuplicateWatchpointException {
assert watchpoint.kind == WatchpointKind.CLIENT;
assert watchpoint.isAlive();
assert !watchpoint.isActive();
if (watchpointCount() >= teleProcess.platformWatchpointCount()) {
throw new MaxWatchpointManager.MaxTooManyWatchpointsException("Number of watchpoints supported by platform (" +
teleProcess.platformWatchpointCount() + ") exceeded");
}
if (!clientWatchpoints.add(watchpoint)) {
// TODO (mlvdv) call out special case where there's a hidden system watchpoint at the same location as this.
// An existing watchpoint starts at the same location
throw new MaxWatchpointManager.MaxDuplicateWatchpointException("Watchpoint already exists at location: " + watchpoint);
}
// Check for possible overlaps with predecessor or successor (according to start location)
final VmWatchpoint lowerWatchpoint = clientWatchpoints.lower(watchpoint);
final VmWatchpoint higherWatchpoint = clientWatchpoints.higher(watchpoint);
if ((lowerWatchpoint != null && lowerWatchpoint.memoryRegion().overlaps(watchpoint.memoryRegion())) ||
(higherWatchpoint != null && higherWatchpoint.memoryRegion().overlaps(watchpoint.memoryRegion()))) {
clientWatchpoints.remove(watchpoint);
final StringBuilder msgBuilder = new StringBuilder();
msgBuilder.append("Watchpoint already exists that overlaps with start=");
msgBuilder.append(watchpoint.memoryRegion().start().toHexString());
msgBuilder.append(", size=");
msgBuilder.append(watchpoint.memoryRegion().nBytes());
throw new MaxWatchpointManager.MaxDuplicateWatchpointException(msgBuilder.toString());
}
if (!heap().phase().isCollecting() || watchpoint.settings.enabledDuringGC) {
// Try to activate the new watchpoint
if (!watchpoint.setActive(true)) {
clientWatchpoints.remove(watchpoint);
return null;
}
}
Trace.line(TRACE_VALUE, watchpoint.tracePrefix() + "added watchpoint: " + watchpoint);
updateAfterWatchpointChanges();
return watchpoint;
}
/**
* Add a system watchpoint, assumed to be newly created.
* <p>Does <strong>not</strong> activate the watchpoint.
* <p>Does <strong>not</strong> check for overlap with existing watchpoints.
*
* @param watchpoint
* @return the watchpoint
* @throws MaxWatchpointManager.MaxTooManyWatchpointsException
*/
private VmWatchpoint addSystemWatchpoint(VmWatchpoint watchpoint) throws MaxWatchpointManager.MaxTooManyWatchpointsException {
if (watchpointCount() >= teleProcess.platformWatchpointCount()) {
throw new MaxWatchpointManager.MaxTooManyWatchpointsException("Number of watchpoints supported by platform (" +
teleProcess.platformWatchpointCount() + ") exceeded");
}
systemWatchpoints.add(watchpoint);
updateAfterWatchpointChanges();
return watchpoint;
}
/**
* Removes a memory watchpoint from the VM.
* <p>
* Notifies observers if a client watchpoint.
*
* @param watchpoint an existing, inactive watchpoint in the VM
* @return true if successful
*/
private boolean removeWatchpoint(VmWatchpoint watchpoint) {
assert watchpoint.isAlive();
assert !watchpoint.isActive();
switch(watchpoint.kind) {
case CLIENT: {
if (!clientWatchpoints.remove(watchpoint)) {
TeleError.unexpected(tracePrefix() + " Failed to remove watchpoint: " + watchpoint);
}
Trace.line(TRACE_VALUE, tracePrefix() + "Removed watchpoint: " + watchpoint);
updateAfterWatchpointChanges();
return true;
}
case SYSTEM: {
if (!systemWatchpoints.remove(watchpoint)) {
TeleError.unexpected(watchpoint.tracePrefix() + " Failed to remove watchpoint: " + watchpoint);
}
Trace.line(TRACE_VALUE, watchpoint.tracePrefix() + "Removed watchpoint: " + watchpoint);
return true;
}
default:
TeleError.unknownCase();
return false;
}
}
public void writeSummary(PrintStream printStream) {
printStream.println("Watchpoints :");
for (MaxWatchpoint maxWatchpoint : clientWatchpointsCache) {
printStream.println(" " + maxWatchpoint.toString());
}
for (MaxWatchpoint maxWatchpoint : systemWatchpoints) {
printStream.println(" " + maxWatchpoint.toString());
}
}
}
}