/* * 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.tele.debug; import static com.sun.max.platform.Platform.*; import java.io.*; import java.util.*; import java.util.concurrent.*; import com.sun.max.tele.*; import com.sun.max.tele.data.*; import com.sun.max.tele.debug.BreakpointCondition.ExpressionException; import com.sun.max.tele.method.*; import com.sun.max.tele.method.CodeLocation.MachineCodeLocation; import com.sun.max.tele.util.*; import com.sun.max.unsafe.*; import com.sun.max.vm.tele.*; /** * A breakpoint set at a machine code location in VM memory. * <p> * If the breakpoint location is known to point, expressed as an instance of {@link RemoteCodePointer}, into compiled * code in a managed region of the VM's code cache, then the breakpoint will be relocated automatically should the method * compilation be relocated. * * @see RemoteCodePointer * */ public abstract class VmTargetBreakpoint extends VmBreakpoint { private static TargetBreakpointManager manager; public static TargetBreakpointManager makeManager(TeleVM vm) { if (manager == null) { manager = new TargetBreakpointManager(vm); } return manager; } /** * A copy of the code in the VM that was replaced when the breakpoint code was patched in, saved so that it can be * restored when the breakpoint becomes inactive. * <p> * Assumes that the code does not change, although its original location in memory might if a compilation is relocated. */ protected final byte[] originalCodeAtBreakpoint; /** * Records whether the breakpoint is active in the VM, and if so where. * <ul> * <li>{@code null} if the breakpoint is <em>inactive</em>;</li> * <li>the absolute location of the breakpoint in VM memory if <em>active</em>.</li> * </ul> * Note that this is the <em>only state</em> concerning code location that is held as a concrete {@link Address}, * since it corresponds directly to an operation (writing and restoring breakpoint code in VM memory) at a specific * memory location. All other references to code are expressed in terms of {@link RemoteCodePointer}s, which track * relocated code and are canonical. */ private Address activeAddress; /** * Is the location of the breakpoint in a managed code cache region. */ private final boolean codeLocationIsManaged; private final VmBytecodeBreakpoint owner; /** * Creates a target code breakpoint for a given code location in the VM. * * @param vm the VM * @param codeLocation the location at which the breakpoint is to be created * @param originalCode the machine code at the breakpoint location that will be overwritten by the breakpoint * instruction. If this value is null, then the code will be read from the current code location. * @param kind the kind of breakpoint * @param owner the bytecode breakpoint for which this is being created, null if none. */ private VmTargetBreakpoint(TeleVM vm, CodeLocation codeLocation, byte[] originalCode, BreakpointKind kind, VmBytecodeBreakpoint owner) { super(vm, codeLocation, kind); final VmCodeCacheRegion codeCacheRegion = vm.codeCache().findCodeCacheRegion(codeLocation.address()); this.owner = owner; this.codeLocationIsManaged = codeCacheRegion != null && codeCacheRegion.isManaged(); this.originalCodeAtBreakpoint = originalCode == null ? vm.memoryIO().readBytes(codeLocation.address(), manager.codeSize()) : originalCode; } public boolean isBytecodeBreakpoint() { return false; } @Override public String toString() { final StringBuilder sb = new StringBuilder("Target breakpoint"); sb.append("{0x").append(codeLocation().address().toHexString()).append(", "); sb.append(kind().toString()).append(", "); sb.append(isEnabled() ? "enabled" : "disabled").append(", "); sb.append(isActive() ? "active" : "inactive"); if (getDescription() != null) { sb.append(", \"").append(getDescription()).append("\""); } sb.append("}"); return sb.toString(); } public VmBytecodeBreakpoint owner() { return owner; } /** * {@inheritDoc} * <br> * Thread-safe; synchronizes on the VM lock. */ @Override public void remove() throws MaxVMBusyException { if (!vm().tryLock()) { throw new MaxVMBusyException(); } try { manager.removeNonTransientBreakpointAt(codeLocation().codePointer()); } finally { vm().unlock(); } } /** * Determines if the target code in the VM is currently patched at this breakpoint's address with the * platform-dependent target instructions implementing a breakpoint. */ boolean isActive() { return activeAddress != null; } /** * Sets the activation state of the breakpoint in the VM; no-op if breakpoint already in that state. * * @param active new activation state for the breakpoint */ void setActive(boolean active) { if (active != isActive()) { if (active) { // Make the breakpoint active, using the current absolute memory address of the code location. // Patches the target code in the VM at this breakpoint's address with platform-dependent target instructions implementing a breakpoint. final Address newActiveAddress = codeLocation().address(); memory().writeBytes(newActiveAddress, manager.code()); activeAddress = newActiveAddress; } else { // Make the breakpoint inactive: patch the memory at the original breakpoint location with code originally there. memory().writeBytes(activeAddress, originalCodeAtBreakpoint); activeAddress = null; } } } /** * A target breakpoint set explicitly by a client, with an optional <em>condition</em>. * <p> * This kind of breakpoint is visible to clients and can be explicitly enabled/disabled/removed by the client. */ private static final class ClientTargetBreakpoint extends VmTargetBreakpoint { private boolean enabled = true; private BreakpointCondition condition; /** * A client-created breakpoint for a given machine code location in VM memory, enabled by default. * * @param vm the VM * @param manager the manager that manages these breakpoints. * @param codeLocation the location at which the breakpoint is to be created * @param originalCode the target code at {@code address} that will be overwritten by the breakpoint * instruction. If this value is null, then the code will be read from {@code address}. */ ClientTargetBreakpoint(TeleVM vm, TargetBreakpointManager manager, CodeLocation codeLocation, byte[] originalCode) { super(vm, codeLocation, originalCode, BreakpointKind.CLIENT, null); } @Override public boolean isEnabled() { return enabled; } @Override public void setEnabled(boolean enabled) throws MaxVMBusyException { if (!vm().tryLock()) { throw new MaxVMBusyException(); } try { this.enabled = enabled; manager.updateAfterBreakpointChanges(true); } finally { vm().unlock(); } } @Override public BreakpointCondition getCondition() { return condition; } @Override public void setCondition(final String conditionDescriptor) throws ExpressionException, MaxVMBusyException { if (!vm().tryLock()) { throw new MaxVMBusyException(); } try { this.condition = new BreakpointCondition(vm(), conditionDescriptor); setTriggerEventHandler(this.condition); } finally { vm().unlock(); } } } /** * A target breakpoint set for internal use by other inspection services, with optional condition. * <p> * It may or may not be visible to clients, but can be explicitly enabled/disabled/removed by the internal * service for which it was created. */ private static final class SystemTargetBreakpoint extends VmTargetBreakpoint { private boolean enabled = true; private BreakpointCondition condition; /** * A system-created breakpoint for a given target code address, enabled by default. * There is by default no special handling, but this can be changed by overriding * {@link #handleTriggerEvent(TeleNativeThread)}. * * @param codeLocation the location at which the breakpoint will be created * @param originalCode the target code at {@code address} that will be overwritten by the breakpoint * instruction. If this value is null, then the code will be read from {@code address}. */ SystemTargetBreakpoint(TeleVM vm, TargetBreakpointManager manager, CodeLocation codeLocation, byte[] originalCode, VmBytecodeBreakpoint owner) { super(vm, codeLocation, originalCode, BreakpointKind.SYSTEM, owner); } @Override public boolean isEnabled() { return enabled; } @Override public void setEnabled(boolean enabled) throws MaxVMBusyException { if (!vm().tryLock()) { throw new MaxVMBusyException(); } try { this.enabled = enabled; } finally { vm().unlock(); } } @Override public BreakpointCondition getCondition() { return condition; } @Override public void setCondition(String conditionDescriptor) throws ExpressionException, MaxVMBusyException { if (!vm().tryLock()) { throw new MaxVMBusyException(); } try { condition = new BreakpointCondition(vm(), conditionDescriptor); setTriggerEventHandler(condition); } finally { vm().unlock(); } } } /** * A target breakpoint set by the process controller, intended to persist only for the * duration of a single execution cycle. It is always enabled for the duration of its lifetime. * <br> * It should not be visible to clients, has no condition, and cannot be explicitly enabled/disabled. */ private static final class TransientTargetBreakpoint extends VmTargetBreakpoint { /** * A transient breakpoint for a given target code address. * * @param vm the VM * @param manager the manager for these breakpoints * @param codeLocation location containing the target code address at which the breakpoint is to be created * @param originalCode the target code at {@code address} that will be overwritten by the breakpoint * instruction. If this value is null, then the code will be read from {@code address}. */ TransientTargetBreakpoint(TeleVM vm, TargetBreakpointManager manager, CodeLocation codeLocation, byte[] originalCode) { super(vm, codeLocation, originalCode, BreakpointKind.TRANSIENT, null); } @Override public boolean isEnabled() { // Transients are always enabled return true; } @Override public void setEnabled(boolean enabled) { TeleError.unexpected("Can't enable/disable transient breakpoints"); } @Override public BreakpointCondition getCondition() { // Transients do not have conditions. return null; } @Override public void setCondition(String conditionDescriptor) throws ExpressionException { TeleError.unexpected("Transient breakpoints do not have conditions"); } } public static final class TargetBreakpointManager extends AbstractVmHolder implements TeleVMCache { private final byte[] code; // Maps: RemoteCodePointer --> TargetBreakpoint // This relies on RemoteCodePointers being canonical // The map implementations are not thread-safe; the manager must take care of that. private final Map<RemoteCodePointer, ClientTargetBreakpoint> clientBreakpoints = new HashMap<RemoteCodePointer, ClientTargetBreakpoint>(); private final Map<RemoteCodePointer, SystemTargetBreakpoint> systemBreakpoints = new HashMap<RemoteCodePointer, SystemTargetBreakpoint>(); private final Map<RemoteCodePointer, TransientTargetBreakpoint> transientBreakpoints = new HashMap<RemoteCodePointer, TransientTargetBreakpoint>(); // Thread-safe, immutable versions of the client map. Will be read many, many more times than will change. private volatile List<ClientTargetBreakpoint> clientBreakpointsCache = Collections.emptyList(); private List<MaxBreakpointListener> breakpointListeners = new CopyOnWriteArrayList<MaxBreakpointListener>(); private TargetBreakpointManager(TeleVM vm) { super(vm); this.code = TargetBreakpoint.createBreakpointCode(platform().isa); } public void updateCache(long epoch) { // Review client breakpoints, those set explicitly in machine code only, and handle evictions. final List<VmTargetBreakpoint> evictedClientBreakpoints = new ArrayList<VmTargetBreakpoint>(); for (VmTargetBreakpoint clientBreakpoint : clientBreakpoints.values()) { if (!clientBreakpoint.codeLocation().codePointer().isCodeLive()) { evictedClientBreakpoints.add(clientBreakpoint); } } for (VmTargetBreakpoint evictedClientBreakpoint : evictedClientBreakpoints) { final String reason = "The compilation has been evicted from the code cache"; TeleWarning.message("Breakpoint removed: " + evictedClientBreakpoint + "(" + reason + ")"); for (final MaxBreakpointListener listener : breakpointListeners) { listener.breakpointToBeDeleted(evictedClientBreakpoint, reason); } removeNonTransientBreakpointAt(evictedClientBreakpoint.codeLocation().codePointer()); } // Check for system breakpoint set on behalf of a bytecode breakpoint final List<VmTargetBreakpoint> evictedSystemBreakpoints = new ArrayList<VmTargetBreakpoint>(); for (VmTargetBreakpoint systemBreakpoint : systemBreakpoints.values()) { if (systemBreakpoint.owner != null && !systemBreakpoint.codeLocation().codePointer().isCodeLive()) { // The compilation in which this breakpoint is set has been evicted and is no longer live. evictedSystemBreakpoints.add(systemBreakpoint); } } for (VmTargetBreakpoint evictedSystemBreakpoint : evictedSystemBreakpoints) { removeNonTransientBreakpointAt(evictedSystemBreakpoint.codeLocation().codePointer()); // Notify the bytecode breakpoint for which this breakpoint was originally created. evictedSystemBreakpoint.owner().notifyCompilationEvicted(evictedSystemBreakpoint); } } /** * Adds a listener for breakpoint changes. * * @param listener a breakpoint listener */ void addListener(MaxBreakpointListener listener) { assert listener != null; breakpointListeners.add(listener); } /** * Removes a listener for breakpoint changes. * * @param listener a breakpoint listener */ void removeListener(MaxBreakpointListener listener) { assert listener != null; breakpointListeners.remove(listener); } /** * Gets the bytes encoding the platform dependent instruction(s) representing a breakpoint. */ private byte[] code() { return code.clone(); } // TODO (mlvdv) inline /** * Gets number of bytes that encode the platform dependent instruction(s) representing a breakpoint. */ int codeSize() { return code.length; } /** * @return all the client-visible persistent target code breakpoints that currently exist * in the VM. Modification safe against breakpoint removal. */ synchronized List<ClientTargetBreakpoint> clientBreakpoints() { return clientBreakpointsCache; } /** * Gets a target code breakpoint set at a specified location in the VM. * <br> * If multiple breakpoints are set at the location, then one is selected * according to the following preference: a client breakpoint, if one exists, * otherwise a system breakpoint, if one exists, otherwise a transient breakpoint, * if one exists. * * @return the target code breakpoint a the specified location, if it exists, null otherwise. */ synchronized VmTargetBreakpoint find(RemoteCodePointer codePointer) { final ClientTargetBreakpoint clientBreakpoint = clientBreakpoints.get(codePointer); if (clientBreakpoint != null) { return clientBreakpoint; } final SystemTargetBreakpoint systemBreakpoint = systemBreakpoints.get(codePointer); if (systemBreakpoint != null) { return systemBreakpoint; } TransientTargetBreakpoint transientTargetBreakpoint = transientBreakpoints.get(codePointer); if (transientTargetBreakpoint != null) { return transientTargetBreakpoint; } try { byte[] c = new byte[code.length]; memory().readBytes(codePointer.getAddress(), c); if (Arrays.equals(c, code)) { final MachineCodeLocation codeLocation = vm().codeLocations().createMachineCodeLocation(codePointer, "discovered breakpoint"); return new TransientTargetBreakpoint(vm(), this, codeLocation, null); } } catch (DataIOError e) { e.printStackTrace(); } return null; } synchronized VmTargetBreakpoint findClientBreakpoint(RemoteCodePointer codePointer) { return clientBreakpoints.get(codePointer); } /** * Return a client-visible target code breakpoint, creating a new one if none exists at that location. * <br> * Thread-safe (synchronizes on the VM lock) * * @param codeLocation location (with address) for the breakpoint * @return a possibly new target code breakpoint * @throws MaxVMBusyException */ VmTargetBreakpoint makeClientBreakpoint(CodeLocation codeLocation) throws MaxVMBusyException { assert codeLocation.hasAddress(); if (!vm().tryLock()) { throw new MaxVMBusyException(); } VmTargetBreakpoint breakpoint; try { breakpoint = find(codeLocation.codePointer()); if (breakpoint == null || breakpoint.isTransient()) { final ClientTargetBreakpoint clientBreakpoint = new ClientTargetBreakpoint(vm(), this, codeLocation, null); final VmTargetBreakpoint oldBreakpoint = clientBreakpoints.put(codeLocation.codePointer(), clientBreakpoint); assert oldBreakpoint == null; breakpoint = clientBreakpoint; updateAfterBreakpointChanges(true); } } finally { vm().unlock(); } return breakpoint; } /** * Return a client-invisible target code breakpoint, creating a new one if none exists at that location. * <br> * Thread-safe (synchronizes on the VM lock) * * @param codeLocation location (with address) for the breakpoint * @param handler an optional handler to be invoked when the breakpoint triggers * @param owner a bytecode breakpoint for which this breakpoint is being created. * @return a possibly new target code breakpoint * @throws MaxVMBusyException */ VmTargetBreakpoint makeSystemBreakpoint(CodeLocation codeLocation, VMTriggerEventHandler handler, VmBytecodeBreakpoint owner) throws MaxVMBusyException { vm().lock(); assert codeLocation.hasAddress(); TeleError.check(codeLocation.codePointer().isCodeLive()); SystemTargetBreakpoint systemBreakpoint; try { systemBreakpoint = systemBreakpoints.get(codeLocation.codePointer()); // TODO (mlvdv) handle case where there is already a client breakpoint at this address. if (systemBreakpoint == null) { systemBreakpoint = new SystemTargetBreakpoint(vm(), this, codeLocation, null, owner); systemBreakpoint.setTriggerEventHandler(handler); systemBreakpoint.setDescription(codeLocation.description()); final SystemTargetBreakpoint oldBreakpoint = systemBreakpoints.put(codeLocation.codePointer(), systemBreakpoint); assert oldBreakpoint == null; updateAfterBreakpointChanges(false); } } finally { vm().unlock(); } return systemBreakpoint; } /** * Return a client-invisible target code breakpoint, creating a new one if none exists at that location. * <br> * Thread-safe (synchronizes on the VM lock) * * @param codeLocation location (with address) for the breakpoint * @param handler an optional handler to be invoked when the breakpoint triggers * @return a possibly new target code breakpoint * @throws MaxVMBusyException */ VmTargetBreakpoint makeSystemBreakpoint(CodeLocation codeLocation, VMTriggerEventHandler handler) throws MaxVMBusyException { return makeSystemBreakpoint(codeLocation, handler, null); } /** * Return a client-invisible transient breakpoint at a specified target code address in the VM, creating a new one first if needed. * <br> * Thread-safe (synchronized on the VM lock) * * @param codeLocation location (with address) for the breakpoint * @return a possibly new target code breakpoint * @throws MaxVMBusyException */ VmTargetBreakpoint makeTransientBreakpoint(CodeLocation codeLocation) throws MaxVMBusyException { assert codeLocation.hasAddress(); TeleError.check(codeLocation.address().isNotZero()); if (!vm().tryLock()) { throw new MaxVMBusyException(); } try { VmTargetBreakpoint breakpoint = find(codeLocation.codePointer()); if (breakpoint == null || !breakpoint.isTransient()) { final TransientTargetBreakpoint transientBreakpoint = new TransientTargetBreakpoint(vm(), this, codeLocation, null); final VmTargetBreakpoint oldBreakpoint = transientBreakpoints.put(codeLocation.codePointer(), transientBreakpoint); assert oldBreakpoint == null; breakpoint = transientBreakpoint; updateAfterBreakpointChanges(false); } return breakpoint; } finally { vm().unlock(); } } /** * Removes the client or system breakpoint, if it exists, at specified target code location in the VM. */ private void removeNonTransientBreakpointAt(RemoteCodePointer codePointer) { if (clientBreakpoints.remove(codePointer) != null) { updateAfterBreakpointChanges(true); } else { if (systemBreakpoints.remove(codePointer) != null) { updateAfterBreakpointChanges(false); } } } /** * Sets the activation state of all target breakpoints in the VM. * <br> * Assumes VM lock held * * @param active new activation state for all breakpoints * @see VmTargetBreakpoint#setActive(boolean) */ void setActiveAll(boolean active) { assert vm().lockHeldByCurrentThread(); for (VmTargetBreakpoint breakpoint : clientBreakpoints.values()) { if (breakpoint.isEnabled()) { breakpoint.setActive(active); } } for (VmTargetBreakpoint breakpoint : systemBreakpoints.values()) { if (breakpoint.isEnabled()) { breakpoint.setActive(active); } } for (VmTargetBreakpoint breakpoint : transientBreakpoints.values()) { breakpoint.setActive(active); } } /** * Sets the activation state of all non-client target breakpoints in the VM. * <br> * Assumes VM lock held * * @param active new activation state for all breakpoints * @see VmTargetBreakpoint#setActive(boolean) */ void setActiveNonClient(boolean active) { assert vm().lockHeldByCurrentThread(); for (VmTargetBreakpoint breakpoint : systemBreakpoints.values()) { if (breakpoint.isEnabled()) { breakpoint.setActive(active); } } for (VmTargetBreakpoint breakpoint : transientBreakpoints.values()) { breakpoint.setActive(active); } } /** * Removes and clears all state associated with transient breakpoints. */ void removeTransientBreakpoints() { assert vm().lockHeldByCurrentThread(); if (transientBreakpoints.size() > 0) { transientBreakpoints.clear(); } updateAfterBreakpointChanges(false); } /** * Update immutable cache of breakpoint list and possibly notify listeners. * * @param announce whether to notify listeners */ private void updateAfterBreakpointChanges(boolean announce) { clientBreakpointsCache = Collections.unmodifiableList(new ArrayList<ClientTargetBreakpoint>(clientBreakpoints.values())); if (announce) { for (final MaxBreakpointListener listener : breakpointListeners) { listener.breakpointsChanged(); } } } /** * Writes a description of every target breakpoint to the stream, including those usually not shown to clients, * with more detail than typically displayed. * <br> * Thread-safe * * @param printStream */ void writeSummaryToStream(PrintStream printStream) { printStream.println("Target breakpoints :"); for (ClientTargetBreakpoint targetBreakpoint : clientBreakpointsCache) { printStream.println(" " + targetBreakpoint + describeLocation(targetBreakpoint)); } for (SystemTargetBreakpoint targetBreakpoint : systemBreakpoints.values()) { printStream.println(" " + targetBreakpoint + describeLocation(targetBreakpoint)); } for (TransientTargetBreakpoint targetBreakpoint : transientBreakpoints.values()) { printStream.println(" " + targetBreakpoint + describeLocation(targetBreakpoint)); } } private String describeLocation(VmTargetBreakpoint targetBreakpoint) { final MaxMachineCodeRoutine maxMachineCode = vm().machineCode().findMachineCode(targetBreakpoint.codeLocation().codePointer().getAddress()); if (maxMachineCode != null) { return " in " + maxMachineCode.entityName(); } return ""; } } }