/*
* 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.ins;
import java.util.*;
import com.sun.max.ins.InspectionSettings.AbstractSaveSettingsListener;
import com.sun.max.ins.InspectionSettings.SaveSettingsEvent;
import com.sun.max.ins.util.*;
import com.sun.max.program.option.*;
import com.sun.max.tele.*;
import com.sun.max.tele.debug.*;
import com.sun.max.tele.method.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.actor.member.MethodKey.DefaultMethodKey;
import com.sun.max.vm.classfile.constant.*;
import com.sun.max.vm.type.*;
/**
* Singleton manager for the Inspector's relationship with breakpoints in the VM.
* Saves breakpoints to persistent storage and reloads at initialization.
*/
public final class BreakpointPersistenceManager extends AbstractSaveSettingsListener implements MaxBreakpointListener {
private static BreakpointPersistenceManager breakpointPersistenceManager;
/**
* Sets up a manager for making breakpoints set in the VM persistent, saved
* after each breakpoint change.
*/
public static void initialize(Inspection inspection) {
if (breakpointPersistenceManager == null) {
breakpointPersistenceManager = new BreakpointPersistenceManager(inspection);
}
}
private final Inspection inspection;
private BreakpointPersistenceManager(Inspection inspection) {
super("breakpoints");
this.inspection = inspection;
final InspectionSettings settings = inspection.settings();
// Register with the persistence service; must do this before load.
settings.addSaveSettingsListener(this);
// These should reload ok, even if the image has been changed, since we only support them at method entry.
loadBytecodeBreakpoints(settings);
if (!settings.bootImageChanged()) {
loadMachineCodeBreakpoints(settings);
} else {
// TODO (mlvdv) can we attempt to recreate these is some cases?
InspectorWarning.message(inspection, "Ignoring breakpoints related to a different boot image");
}
// Once load-in is finished, register for notification of subsequent breakpoint changes in the VM.
inspection.vm().breakpointManager().addListener(this);
}
// Keys used for making data persistent
private static final String MACHINE_CODE_BREAKPOINT_KEY = "machineCodeBreakpoint";
private static final String BYTECODE_BREAKPOINT_KEY = "bytecodeBreakpoint";
private static final String ADDRESS_KEY = "address";
private static final String CONDITION_KEY = "condition";
private static final String COUNT_KEY = "count";
private static final String DESCRIPTION_KEY = "description";
private static final String ENABLED_KEY = "enabled";
private static final String METHOD_HOLDER_KEY = "method.holder";
private static final String METHOD_NAME_KEY = "method.name";
private static final String METHOD_SIGNATURE_KEY = "method.signature";
private static final String BCI_KEY = "bci";
public void breakpointsChanged() {
// Breakpoints in the VM have changed.
inspection.settings().save();
}
public void breakpointToBeDeleted(MaxBreakpoint breakpoint, String reason) {
}
public void saveSettings(SaveSettingsEvent saveSettingsEvent) {
final List<MaxBreakpoint> machineCodeBreakpoints = new LinkedList<MaxBreakpoint>();
final List<MaxBreakpoint> bytecodeBreakpoints = new LinkedList<MaxBreakpoint>();
for (MaxBreakpoint breakpoint : inspection.vm().breakpointManager().breakpoints()) {
if (breakpoint.isBytecodeBreakpoint()) {
bytecodeBreakpoints.add(breakpoint);
} else {
machineCodeBreakpoints.add(breakpoint);
}
}
saveMachineCodeBreakpoints(saveSettingsEvent, machineCodeBreakpoints);
saveBytecodeBreakpoints(saveSettingsEvent, bytecodeBreakpoints);
}
private void saveMachineCodeBreakpoints(SaveSettingsEvent saveSettingsEvent, List<MaxBreakpoint> machineCodeBreakpoints) {
saveSettingsEvent.save(MACHINE_CODE_BREAKPOINT_KEY + "." + COUNT_KEY, machineCodeBreakpoints.size());
int index = 0;
for (MaxBreakpoint breakpoint : machineCodeBreakpoints) {
final String prefix = MACHINE_CODE_BREAKPOINT_KEY + index++;
final Address bootImageOffset = breakpoint.codeLocation().address().minus(inspection.vm().bootImageStart());
saveSettingsEvent.save(prefix + "." + ADDRESS_KEY, bootImageOffset.toLong());
saveSettingsEvent.save(prefix + "." + ENABLED_KEY, breakpoint.isEnabled());
final BreakpointCondition condition = breakpoint.getCondition();
if (condition != null) {
saveSettingsEvent.save(prefix + "." + CONDITION_KEY, condition.toString());
}
// Always save, even if empty, so old values get overwritten if description is removed.
saveSettingsEvent.save(prefix + "." + DESCRIPTION_KEY, breakpoint.getDescription() == null ? "" : breakpoint.getDescription());
}
}
private void loadMachineCodeBreakpoints(final InspectionSettings settings) {
final int numberOfBreakpoints = settings.get(this, MACHINE_CODE_BREAKPOINT_KEY + "." + COUNT_KEY, OptionTypes.INT_TYPE, 0);
for (int i = 0; i < numberOfBreakpoints; i++) {
final String prefix = MACHINE_CODE_BREAKPOINT_KEY + i;
final Address bootImageOffset = Address.fromLong(settings.get(this, prefix + "." + ADDRESS_KEY, OptionTypes.LONG_TYPE, null));
final Address address = inspection.vm().bootImageStart().plus(bootImageOffset);
final boolean enabled = settings.get(this, prefix + "." + ENABLED_KEY, OptionTypes.BOOLEAN_TYPE, null);
final String condition = settings.get(this, prefix + "." + CONDITION_KEY, OptionTypes.STRING_TYPE, null);
final String description = settings.get(this, prefix + "." + DESCRIPTION_KEY, OptionTypes.STRING_TYPE, null);
if (inspection.vm().codeCache().contains(address)) {
try {
final MaxCodeLocation codeLocation = inspection.vm().codeLocations().createMachineCodeLocation(address, "loaded by breakpoint persistence manager");
final MaxBreakpoint breakpoint = inspection.vm().breakpointManager().makeBreakpoint(codeLocation);
if (condition != null) {
breakpoint.setCondition(condition);
}
breakpoint.setDescription(description);
breakpoint.setEnabled(enabled);
} catch (BreakpointCondition.ExpressionException expressionException) {
inspection.gui().errorMessage(String.format("Error parsing saved breakpoint condition:%n expression: %s%n error: " + condition, expressionException.getMessage()), "Breakpoint Condition Error");
} catch (MaxVMBusyException maxVMBusyException) {
InspectorWarning.message(inspection, "Unable to recreate machine code breakpoint from saved settings at: " + address, maxVMBusyException);
} catch (InvalidCodeAddressException e) {
InspectorWarning.message(inspection, "Bad code breakpoint location from saved settings @ " + e.getAddressString() + ": " + e.getMessage());
}
} else {
InspectorWarning.message(inspection, "dropped former breakpoint in runtime-generated code at address: " + address);
}
}
}
private void saveBytecodeBreakpoints(SaveSettingsEvent settings, List<MaxBreakpoint> bytecodeBreakpoints) {
int index;
settings.save(BYTECODE_BREAKPOINT_KEY + "." + COUNT_KEY, bytecodeBreakpoints.size());
index = 0;
for (MaxBreakpoint breakpoint : bytecodeBreakpoints) {
final String prefix = BYTECODE_BREAKPOINT_KEY + index++;
final MaxCodeLocation codeLocation = breakpoint.codeLocation();
final MethodKey methodKey = codeLocation.methodKey();
if (methodKey != null) {
settings.save(prefix + "." + METHOD_HOLDER_KEY, methodKey.holder().string);
settings.save(prefix + "." + METHOD_NAME_KEY, methodKey.name().string);
settings.save(prefix + "." + METHOD_SIGNATURE_KEY, methodKey.signature().string);
settings.save(prefix + "." + BCI_KEY, codeLocation.bci());
settings.save(prefix + "." + ENABLED_KEY, breakpoint.isEnabled());
} else {
InspectorWarning.message(inspection, "Unable to save bytecode breakpoint, no key in " + breakpoint);
}
}
}
private void loadBytecodeBreakpoints(final InspectionSettings settings) {
final int numberOfBreakpoints = settings.get(this, BYTECODE_BREAKPOINT_KEY + "." + COUNT_KEY, OptionTypes.INT_TYPE, 0);
for (int i = 0; i < numberOfBreakpoints; i++) {
final String prefix = BYTECODE_BREAKPOINT_KEY + i;
final TypeDescriptor holder = JavaTypeDescriptor.parseTypeDescriptor(settings.get(this, prefix + "." + METHOD_HOLDER_KEY, OptionTypes.STRING_TYPE, null));
final Utf8Constant name = PoolConstantFactory.makeUtf8Constant(settings.get(this, prefix + "." + METHOD_NAME_KEY, OptionTypes.STRING_TYPE, null));
final SignatureDescriptor signature = SignatureDescriptor.create(settings.get(this, prefix + "." + METHOD_SIGNATURE_KEY, OptionTypes.STRING_TYPE, null));
final MethodKey methodKey = new DefaultMethodKey(holder, name, signature);
final int bci = settings.get(this, prefix + "." + BCI_KEY, OptionTypes.INT_TYPE, 0);
if (bci > 0) {
InspectorWarning.message(inspection, "Ignoring non-zero bytecode index for saved breakpoint in " + methodKey);
}
final boolean enabled = settings.get(this, prefix + "." + ENABLED_KEY, OptionTypes.BOOLEAN_TYPE, true);
final MaxCodeLocation location = inspection.vm().codeLocations().createBytecodeLocation(methodKey, "loaded by breakpoint persistence manager");
MaxBreakpoint bytecodeBreakpoint;
try {
bytecodeBreakpoint = inspection.vm().breakpointManager().makeBreakpoint(location);
if (bytecodeBreakpoint.isEnabled() != enabled) {
bytecodeBreakpoint.setEnabled(enabled);
}
} catch (MaxVMBusyException maxVMBusyException) {
InspectorWarning.message(inspection, "Unable to recreate bytecode breakpoint from saved settings at: " + methodKey, maxVMBusyException);
}
}
}
}