/*
* 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;
import static com.sun.max.tele.debug.ProcessState.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import java.util.logging.*;
import javax.swing.*;
import com.sun.max.config.*;
import com.sun.max.ide.*;
import com.sun.max.jdwp.vm.core.*;
import com.sun.max.jdwp.vm.proxy.*;
import com.sun.max.jdwp.vm.proxy.VMValue.Type;
import com.sun.max.platform.*;
import com.sun.max.program.*;
import com.sun.max.program.Classpath.Entry;
import com.sun.max.program.option.*;
import com.sun.max.tele.channel.*;
import com.sun.max.tele.channel.tcp.*;
import com.sun.max.tele.data.*;
import com.sun.max.tele.debug.*;
import com.sun.max.tele.debug.VmBytecodeBreakpoint.BytecodeBreakpointManager;
import com.sun.max.tele.debug.VmWatchpoint.VmWatchpointManager;
import com.sun.max.tele.debug.no.*;
import com.sun.max.tele.field.*;
import com.sun.max.tele.heap.*;
import com.sun.max.tele.interpreter.*;
import com.sun.max.tele.jdwputil.*;
import com.sun.max.tele.memory.*;
import com.sun.max.tele.method.*;
import com.sun.max.tele.method.CodeLocation.BytecodeLocation;
import com.sun.max.tele.method.CodeLocation.MachineCodeLocation;
import com.sun.max.tele.method.CodeLocation.VmCodeLocationManager;
import com.sun.max.tele.object.*;
import com.sun.max.tele.reference.*;
import com.sun.max.tele.reference.direct.*;
import com.sun.max.tele.type.*;
import com.sun.max.tele.util.*;
import com.sun.max.tele.value.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.heap.*;
import com.sun.max.vm.hosted.*;
import com.sun.max.vm.layout.*;
import com.sun.max.vm.tele.*;
import com.sun.max.vm.thread.*;
import com.sun.max.vm.type.*;
import com.sun.max.vm.value.*;
/**
* Implementation of remote access to an instance of the Maxine VM.
* Access from the Inspector or other clients of this implementation
* gain access through the {@link MaxVM} interface.
* <p>
* <strong>Concurrency policy:</strong> VM access is protected
* by a reentrant lock that must be honored by all client-visible
* methods that are not thread-safe. Consequences of failure to
* do so can result in either (a) undefined behavior of the VM
* process (when inappropriate process operations are made
* while the process is running), or (b) race conditions in the
* data caches being revised a the conclusion of each process
* execution. The lock is managed differently by the process and
* by client methods.
* <ol>
* <li>the VM process (see {@link TeleProcess}) enqueues requests for VM
* execution; these requests may be made on client threads. The requests
* are executed on a separate "request handling" thread, which acquires
* and holds the lock unconditionally during the entire cycle of request
* execution: setup of state, VM execution, waiting for VM execution
* to conclude, refreshing caches of VM state.</li>
* <li>any method made available to clients (see {@link MaxVM} and
* related interfaces) must either be made thread-safe (and documented
* as such) or must be wrapped in a conditional attempt to acquire the
* lock. Client attempts to acquire the lock that fail, must respond
* immediately by throwing an {@link MaxVMBusyException}.</li>
* <li>note that the lock is reentrant, so that nested attempts to
* acquire/release the lock will behave identically to standard
* Java synchronization semantics.</li>
* </ol>
*/
public abstract class TeleVM implements MaxVM {
private static final int TRACE_VALUE = 1;
/**
* The of the binary file in which the VM executable is stored.
*/
private static final String BOOTIMAGE_FILE_NAME = "maxvm";
/**
* The name of the native library that supports the Inspector.
*/
public static final String TELE_LIBRARY_NAME = "tele";
private static final List<MaxEntityMemoryRegion<? extends MaxEntity> > EMPTY_MAXMEMORYREGION_LIST = Collections.emptyList();
/**
* Defines whether the target VM running locally or on a remote machine, or is core-dump.
*/
public static final class TargetLocation {
public enum Kind {
LOCAL("Native"), // target VM is on the same machine as Inspector
REMOTE("TCP"), // target VM is on a remote machine
FILE("Dump"); // target VM is a core dump
String classNameComponent;
Kind(String name) {
classNameComponent = name;
}
}
public final Kind kind;
public final String target; // pathname to dump file if kind == FILE, else remote machine id
public final int port; // port to communicate on
public final int id; // process id (to attach to)
private TargetLocation(Kind kind, String target, int port, int id) {
this.kind = kind;
this.target = target;
this.port = port;
this.id = id;
}
static void set(Options options) {
final String targetKind = options.targetKindOption.getValue();
String target = null;
Kind kind = Kind.LOCAL;
int port = TCPTeleChannelProtocol.DEFAULT_PORT;
int id = -1;
final List<String> targetLocationValue = options.targetLocationOption.getValue();
if (targetKind.equals("remote")) {
kind = Kind.REMOTE;
final int size = targetLocationValue.size();
if (size == 0 || size > 3) {
usage(options.targetLocationOption);
}
if (size >= 1) {
target = targetLocationValue.get(0);
}
if (size >= 2) {
final String portString = targetLocationValue.get(1);
if (!portString.isEmpty()) {
port = Integer.parseInt(portString);
}
}
if (size == 3) {
id = Integer.parseInt(targetLocationValue.get(2));
}
} else if (targetKind.equals("file")) {
kind = Kind.FILE;
if (targetLocationValue.size() > 0) {
target = targetLocationValue.get(0);
}
} else if (targetKind.equals("local")) {
kind = Kind.LOCAL;
if (targetLocationValue.size() == 1) {
id = Integer.parseInt(targetLocationValue.get(0));
} else if (targetLocationValue.size() != 0) {
usage(options.targetLocationOption);
}
} else {
TeleError.unexpected("usage: " + options.targetKindOption.getHelp());
}
if (mode == MaxInspectionMode.ATTACH || mode == MaxInspectionMode.ATTACHWAITING) {
if (kind == Kind.FILE) {
// must have a dump file, if not provided put up a dialog to get it.
if (target == null) {
target = JOptionPane.showInputDialog(null, "Enter the path to the VM dump file");
}
} else {
// must have an id, if not provided put up a dialog to get it.
if (id < 0) {
id = Integer.parseInt(JOptionPane.showInputDialog(null, "Enter the target VM id"));
}
}
}
targetLocation = new TargetLocation(kind, target, port, id);
}
private static void usage(Option<List<String>> locationOption) {
TeleError.unexpected("usage: " + locationOption.getHelp());
}
}
/**
* The mode of the inspection, which require different startup behavior.
*/
public static MaxInspectionMode mode;
/**
* Information about where the (running/dumped) target VM is located.
*/
private static TargetLocation targetLocation;
/**
* Where the meta-data associated with the target VM is located {link #vmDirectoryOption}.
*/
private static File vmDirectory;
/**
* The VM object that represents the VM itself.
*/
private static TeleMaxineVM teleMaxineVM;
/**
* The VM object that holds configuration information, including scheme implementations.
*/
private static TeleVMConfiguration teleVMConfiguration;
/**
* An abstraction description of the VM's platform, suitable for export.
*/
private VmPlatform platform;
/**
* If {@code true}, always prompt for native code frame view when entering native code.
*/
public static boolean promptForNativeCodeView;
/**
* Interface for notification that all of the initialization for a remote inspection session
* are substantially complete; some services have needs for setup that can only happen very
* late, most notably anything that requires setting a breakpoint.
*/
public interface InitializationListener {
/**
* Notifies listener that all of the remote inspection services are substantially complete,
* and that it is safe to use them, for example setting breakpoints.
*/
void initialiationComplete(long epoch);
}
/**
* The options controlling how a VM instance is created}.
*/
public static class Options extends OptionSet {
public final Option<String> modeOption = newStringOption("mode", "create",
"Mode of operation: create | attach | attachwaiting | image");
public final Option<String> targetKindOption = newStringOption("target", "local",
"Location kind of target VM: local | remote | file");
public final Option<List<String>> targetLocationOption = newStringListOption("location", "",
"Location info of target VM: hostname[, port, id] | pathname");
public final Option<List<String>> classpathOption = newStringListOption("cp", null, File.pathSeparatorChar,
"Additional locations to use when searching for Java class files. These locations are searched after the jar file containing the " +
"boot image classes but before the locations corresponding to the class path of this JVM process.");
public final Option<List<String>> sourcepathOption = newStringListOption("sourcepath", null, File.pathSeparatorChar,
"Additional locations to use when searching for Java source files. These locations are searched before the default locations.");
public final Option<File> commandFileOption = newFileOption("c", "",
"Executes the commands in a file on startup.");
public final Option<String> logLevelOption = newStringOption("logLevel", Level.SEVERE.getName(),
"Level to set for java.util.logging root logger.");
public final Option<Boolean> usePrecompilationBreakpoints = newBooleanOption("precomp-bp", false,
"Method entry bytecode breakpoints also stop VM prior to compilation of matching methods.");
public final Option<Boolean> nativePrompt = newBooleanOption("ncv", false,
"Prompt for native code view when entering native code");
public final Option<String> vmLogFileOption = newStringOption("vmlog", null, "file containg VMLog for mode==image");
/**
* An option to explicitly set the boot heap address (maybe useful for core dump).
*/
public final Option<String> heapOption;
/**
* This field is {@code null} if inspecting read-only.
*/
public final Option<String> vmArguments;
/**
* Creates command line options that are specific to certain operation modes. No longer tries to customize the
* options based on mode.
*/
public Options() {
heapOption = newStringOption("heap", null, "Relocation address for the heap and code in the boot image.");
vmArguments = newStringOption("a", "", "Specifies the arguments to the target VM.");
// We do not want to check the auto generated code for consistency (by default), so change default value
BootImageGenerator.checkGeneratedCodeOption.setDefaultValue(false);
addOptions(BootImageGenerator.inspectorSharedOptions);
}
}
private static boolean needTeleLibrary() {
return targetLocation.kind == TargetLocation.Kind.LOCAL;
}
public boolean isAttaching() {
return mode == MaxInspectionMode.ATTACH;
}
public static boolean isDump() {
return mode == MaxInspectionMode.ATTACH && targetLocation.kind == TargetLocation.Kind.FILE;
}
/**
* Create the correct instance of {@link TeleChannelProtocol} based on {@link #targetLocation} and
* {@link OS}.
*
* @param os
*/
protected void setTeleChannelProtocol(OS os) {
if (mode == MaxInspectionMode.IMAGE) {
teleChannelProtocol = new ReadOnlyTeleChannelProtocol();
return;
}
/*
* To avoid boilerplate switch statements, the format of the class is required to be:
* com.sun.max.tele.debug.<ospackage>.<os><kind>TeleChannelProtocol, where Kind == Native for LOCAL, TCP for
* REMOTE and Dump for FILE. os is sanitized to conform to standard class naming rules. E.g. SOLARIS -> Solaris
*/
final String className = "com.sun.max.tele.debug." + os.asPackageName() + "." + os.className +
targetLocation.kind.classNameComponent + "TeleChannelProtocol";
try {
final Class< ? > klass = Class.forName(className);
Constructor< ? > cons;
Object[] args;
if (targetLocation.kind == TargetLocation.Kind.REMOTE) {
cons = klass.getDeclaredConstructor(new Class[] {String.class, int.class});
args = new Object[] {targetLocation.target, targetLocation.port};
} else if (targetLocation.kind == TargetLocation.Kind.FILE) {
// dump
final File dumpFile = new File(targetLocation.target);
if (!dumpFile.exists()) {
TeleError.unexpected("core dump file: " + targetLocation.target + " does not exist or is not accessible");
}
final File vmFile = new File(vmDirectory, "maxvm");
if (!vmFile.exists()) {
TeleError.unexpected("vm file: " + vmFile + " does not exist or is not accessible");
}
cons = klass.getDeclaredConstructor(new Class[] {MaxVM.class, File.class, File.class});
args = new Object[] {this, vmFile, dumpFile};
} else {
cons = klass.getDeclaredConstructor(new Class[] {});
args = new Object[0];
}
teleChannelProtocol = (TeleChannelProtocol) cons.newInstance(args);
} catch (Exception ex) {
TeleError.unexpected("failed to create instance of " + className, ex);
}
}
/**
* Creates a new VM instance based on a given set of options.
*
* @param options the options controlling specifics of the VM instance to be created
* @return a new VM instance
*/
public static TeleVM create(Options options) throws BootImageException {
mode = MaxInspectionMode.valueOf(options.modeOption.getValue().toUpperCase());
TargetLocation.set(options);
// Ensure that method actors are available for class initializers loaded at runtime.
MaxineVM.preserveClinitMethods = true;
if (options.usePrecompilationBreakpoints.getValue()) {
BytecodeBreakpointManager.usePrecompilationBreakpoints = true;
}
promptForNativeCodeView = options.nativePrompt.getValue();
final String logLevel = options.logLevelOption.getValue();
try {
LogManager.getLogManager().getLogger("").setLevel(Level.parse(logLevel));
} catch (IllegalArgumentException e) {
TeleWarning.message("Invalid level specified for java.util.logging root logger: " + logLevel + " [using " + Level.SEVERE + "]");
LogManager.getLogManager().getLogger("").setLevel(Level.SEVERE);
}
TeleVM vm = null;
// Configure the prototype class loader gets the class files used to build the image
Classpath classpathPrefix = Classpath.EMPTY;
final List<String> classpathList = options.classpathOption.getValue();
if (classpathList != null) {
final Classpath extraClasspath = new Classpath(classpathList.toArray(new String[classpathList.size()]));
classpathPrefix = classpathPrefix.prepend(extraClasspath);
}
vmDirectory = BootImageGenerator.getDefaultVMDirectory(true);
classpathPrefix = classpathPrefix.prepend(BootImageGenerator.getBootImageJarFile(vmDirectory).getAbsolutePath());
checkClasspath(classpathPrefix);
final Classpath classpath = Classpath.fromSystem().prepend(classpathPrefix);
HostedVMClassLoader.HOSTED_VM_CLASS_LOADER.setClasspath(classpath);
HostedBootClassLoader.noOmittedClassExceptions();
if (needTeleLibrary()) {
Prototype.loadLibrary(TELE_LIBRARY_NAME);
}
final File bootImageFile = BootImageGenerator.getBootImageFile(vmDirectory);
Classpath sourcepath = JavaProject.getSourcePath(TeleVM.class, true);
final List<String> sourcepathList = options.sourcepathOption.getValue();
if (sourcepathList != null) {
sourcepath = sourcepath.prepend(new Classpath(sourcepathList.toArray(new String[sourcepathList.size()])));
}
checkClasspath(sourcepath);
String heap = options.heapOption.getValue();
if (heap != null) {
System.setProperty(VmObjectAccess.HEAP_ADDRESS_PROPERTY, heap);
}
switch (mode) {
case CREATE:
case ATTACHWAITING:
final String value = options.vmArguments.getValue();
final String[] commandLineArguments = "".equals(value) ? new String[0] : value.trim().split(" ");
vm = create(bootImageFile, sourcepath, commandLineArguments);
vm.lock();
try {
vm.updateVMCaches(0L);
vm.teleProcess().initializeState();
vm.modifyInspectableFlags(Inspectable.INSPECTED, true);
} finally {
vm.unlock();
}
try {
vm.advanceToJavaEntryPoint();
} catch (IOException ioException) {
throw new BootImageException(ioException);
}
break;
case ATTACH:
/* The fundamental difference in this mode is that VM has executed for a while.
* This means that boot heap relocation has (almost certainly) been performed
* AND the boot heap will contain references to the dynamic heap.
* So the delicate dance that us normally performed when setting up the
* {@link VmClassRegistry} is neither entirely necessary nor sufficient.
* This is handled by doing two passes over the class registry and
* deferring resolution of those references that are outside the boot heap
* until the second pass, after the TeleHeap is fully initialized.
* We also need to explicitly refresh the threads and update state.
*/
vm = create(bootImageFile, sourcepath, null);
vm.lock();
try {
vm.updateVMCaches(0L);
vm.teleProcess().initializeStateOnAttach();
} finally {
vm.unlock();
}
break;
case IMAGE:
System.setProperty(VmObjectAccess.HEAP_ADDRESS_PROPERTY, "1024");
vm = createReadOnly(bootImageFile, sourcepath);
String vmLogFileOption = options.vmLogFileOption.getValue();
if (vmLogFileOption != null) {
vm.vmLogFile = new File(vmLogFileOption);
}
vm.updateVMCaches(0L);
}
final File commandFile = options.commandFileOption.getValue();
if (commandFile != null && !commandFile.equals("")) {
vm.executeCommandsFromFile(commandFile.getPath());
}
return vm;
}
public static TargetLocation targetLocation() {
return targetLocation;
}
/**
* Creates and installs the {@linkplain MaxineVM#vm() global VM} context based on a given
* configuration loaded from a boot image.
*
* @param bootImageConfig information about the particular build, extracted from the boot image
*/
public static void initializeVM(VMConfiguration bootImageConfig) {
MaxineVM vm = new MaxineVM(bootImageConfig);
MaxineVM.set(vm);
bootImageConfig.loadAndInstantiateSchemes(null);
// Create a mirror of the VM's configuration, substituting an implementation of ReferenceScheme specialized for the Inspector.
final VMConfiguration config = new VMConfiguration(
bootImageConfig.buildLevel,
Platform.platform(),
getInspectorReferencePackage(bootImageConfig.referencePackage),
bootImageConfig.layoutPackage,
bootImageConfig.heapPackage,
bootImageConfig.monitorPackage,
bootImageConfig.runPackage).gatherBootImagePackages();
vm = new MaxineVM(config);
MaxineVM.set(vm);
config.loadAndInstantiateSchemes(bootImageConfig.vmSchemes());
JavaPrototype.initialize(BootImageGenerator.checkGeneratedCodeOption.getValue());
}
/**
* Create the appropriate subclass of {@link TeleVM} based on VM configuration.
*
* @param bootImageFile
* @param sourcepath
* @param commandlineArguments {@code null} if {@code processId > 0} else command line arguments for new VM process
* @return appropriate subclass of TeleVM for target VM
* @throws BootImageException
*/
private static TeleVM create(File bootImageFile, Classpath sourcepath, String[] commandlineArguments) throws BootImageException {
final BootImage bootImage = new BootImage(bootImageFile);
initializeVM(bootImage.vmConfiguration);
TeleVM vm = null;
final OS os = Platform.platform().os;
final String className = "com.sun.max.tele.debug." + os.asPackageName() + "." + os.className + "TeleVM";
try {
final Class< ? > klass = Class.forName(className);
final Constructor< ? > cons = klass.getDeclaredConstructor(new Class[] {BootImage.class, Classpath.class, String[].class});
vm = (TeleVM) cons.newInstance(new Object[] {bootImage, sourcepath, commandlineArguments});
} catch (Exception ex) {
TeleError.unexpected("failed to instantiate " + className, ex);
}
return vm;
}
private static void checkClasspath(Classpath classpath) {
for (Entry classpathEntry : classpath.entries()) {
if (classpathEntry.isPlainFile()) {
TeleWarning.message("Class path entry is neither a directory nor a JAR file: " + classpathEntry);
}
}
}
/**
* Creates a VM instance that is read-only and is only useful for inspecting a boot image.
*
* @param bootImageFile the file containing the boot image
* @param sourcepath the source code path to search for class or interface definitions
* @throws BootImageException
*/
private static TeleVM createReadOnly(File bootImageFile, Classpath sourcepath) throws BootImageException {
final BootImage bootImage = new BootImage(bootImageFile);
initializeVM(bootImage.vmConfiguration);
return new ReadOnlyTeleVM(bootImage, sourcepath);
}
private static final Logger LOGGER = Logger.getLogger(TeleVM.class.getName());
/**
* An object that delays evaluation of a trace message for controller actions.
*/
private class Tracer {
private final String message;
/**
* An object that delays evaluation of a trace message.
* @param message identifies what is being traced
*/
public Tracer(String message) {
this.message = message;
}
@Override
public String toString() {
return tracePrefix() + message;
}
}
private final Tracer refreshTracer = new Tracer("refresh");
private static BootImagePackage getInspectorReferencePackage(BootImagePackage referencePackage) {
final String suffix = referencePackage.name().substring("com.sun.max.vm.reference".length());
final BootImagePackage inspectorReferenceRootPackage = new com.sun.max.tele.reference.Package();
return BootImagePackage.fromName(inspectorReferenceRootPackage.name() + suffix);
}
private String tracePrefix() {
return "[TeleVM: " + Thread.currentThread().getName() + "] ";
}
private final BootImage bootImage;
private final File bootImageFile;
private File vmLogFile;
final File programFile;
private final VmAddressSpace addressSpace;
private final VmMemoryIO memoryIO;
private final VmObjectAccess objectAccess;
private final VmReferenceManager referenceManager;
private final VmHeapAccess heapAccess;
private VmCodeCacheAccess codeCacheAccess = null;
private NativeCodeAccess nativeCodeAccess = null;
private final VmCodeLocationManager codeLocationManager;
private final VmMachineCodeAccess machineCodeAccess;
/**
* Breakpoint manager, for both target and bytecode breakpoints.
*/
private final VmBreakpointManager breakpointManager;
private final VmWatchpoint.VmWatchpointManager watchpointManager;
private final VmThreadAccess threadAccess;
/**
* The immutable history of all VM states, as of the last state transition; thread safe
* for access by client methods on any thread.
*/
private volatile TeleVMState teleVMState;
private List<InitializationListener> initializationListeners = new ArrayList<InitializationListener>();
private List<MaxVMStateListener> vmStateListeners = new CopyOnWriteArrayList<MaxVMStateListener>();
/**
* Dispatcher for GC start events, i.e. when entering the {@link HeapPhase#ANALYZING} phase.
*/
private VMEventDispatcher<MaxGCPhaseListener> gcAnalyzingListeners;
/**
* Dispatcher for GC start events, i.e. when entering the {@link HeapPhase#RECLAIMING} phase.
*/
private VMEventDispatcher<MaxGCPhaseListener> gcReclaimingListeners;
/**
* Dispatcher for GC completion events, i.e. when entering the {@link HeapPhase#MUTATING} phase.
*/
private VMEventDispatcher<MaxGCPhaseListener> gcMutatingListeners;
/**
* Dispatcher for thread entry events (i.e., when a {@link VmThread} enters its run method).
*/
private VMEventDispatcher<MaxVMThreadEntryListener> threadEntryListeners;
/**
* Dispatcher for thread detaching events (i.e., when a {@link VmThread} has detached itself from the {@link VmThreadMap#ACTIVE} list of threads).
*/
private VMEventDispatcher<MaxVMThreadDetachedListener> threadDetachListeners;
private final TeleProcess teleProcess;
public static TeleChannelProtocol teleChannelProtocol() {
return teleChannelProtocol;
}
public final TeleProcess teleProcess() {
return teleProcess;
}
public boolean isBootImageRelocated() {
return true;
}
private final Pointer bootImageStart;
public final Pointer bootImageStart() {
return bootImageStart;
}
private final VmFieldAccess fieldAccess;
public final VmFieldAccess fields() {
return fieldAccess;
}
private final VmMethodAccess methodAccess;
/**
* Clone of the configuration descriptor for the current VM, with inspector-specific adjustments.
*/
private final VMConfiguration vmConfiguration;
private final Classpath sourcepath;
private int interpreterUseLevel = 0;
private VmClassAccess classAccess;
private final TimedTrace updateTracer;
private final InvalidReferencesLogger invalidReferencesLogger;
public final InvalidReferencesLogger invalidReferencesLogger() {
return invalidReferencesLogger;
}
protected VMLock makeVMLock() {
return new ReentrantVMLock();
}
protected VMLock getLock() {
return lock;
}
/**
* A lock designed to keep all non-thread-safe client calls from being handled during the VM setup/execute/refresh cycle.
*/
private VMLock lock;
protected interface VMLock {
void lock();
boolean tryLock();
void unlock();
boolean isHeldByCurrentThread();
}
private static class ReentrantVMLock implements VMLock {
private ReentrantLock lock = new ReentrantLock();
@Override
public void lock() {
lock.lock();
}
@Override
public boolean tryLock() {
return lock.tryLock();
}
@Override
public void unlock() {
lock.unlock();
}
@Override
public boolean isHeldByCurrentThread() {
return lock.isHeldByCurrentThread();
}
}
/**
* The protocol that is being used to communicate with the target VM.
*/
private static TeleChannelProtocol teleChannelProtocol;
/**
* Creates a VM instance by creating or attaching to a Maxine VM process.
*
* @param bootImage the metadata describing the contents in the boot image
* @param sourcepath path used to search for Java source files
* @param commandLineArguments the command line arguments to be used when creating a new VM process. If this value
* is {@code null}, then an attempt is made to attach to the process whose id is {@code processID}.
* @throws BootImageException
*/
protected TeleVM(BootImage bootImage, Classpath sourcepath, String[] commandLineArguments) throws BootImageException {
this.lock = makeVMLock();
final TimedTrace tracer = new TimedTrace(TRACE_VALUE, tracePrefix() + " creating");
tracer.begin();
this.teleVMState = TeleVMState.nullState(mode);
this.bootImageFile = bootImage.imageFile;
this.bootImage = bootImage;
this.sourcepath = sourcepath;
this.platform = new VmPlatform(Platform.platform());
setTeleChannelProtocol(Platform.platform().os);
this.updateTracer = new TimedTrace(TRACE_VALUE, tracePrefix() + " updating all");
// Pre-initialize the disassembler to save time.
TeleDisassembler.initialize(Platform.platform());
this.programFile = new File(bootImageFile.getParent(), BOOTIMAGE_FILE_NAME);
if (mode == MaxInspectionMode.ATTACH || mode == MaxInspectionMode.ATTACHWAITING) {
this.teleProcess = attachToTeleProcess();
} else {
this.teleProcess = createTeleProcess(commandLineArguments);
}
this.bootImageStart = loadBootImage();
this.vmConfiguration = VMConfiguration.vmConfig();
this.addressSpace = VmAddressSpace.make(this);
this.memoryIO = VmMemoryIO.make(this, this.teleProcess);
this.referenceManager = VmReferenceManager.make(this, (RemoteReferenceScheme) this.vmConfiguration.referenceScheme());
this.threadAccess = VmThreadAccess.make(this);
this.codeLocationManager = VmCodeLocationManager.make(this);
this.machineCodeAccess = VmMachineCodeAccess.make(this);
if (!tryLock(DEFAULT_MAX_LOCK_TRIALS)) {
TeleError.unexpected("unable to lock during creation");
}
this.fieldAccess = VmFieldAccess.make(this);
this.methodAccess = VmMethodAccess.make(this, codeLocationManager);
this.objectAccess = VmObjectAccess.make(this);
this.heapAccess = VmHeapAccess.make(this, this.addressSpace);
unlock();
// Provide access to JDWP server - DISABLED - not being used now.
this.jdwpAccess = new VMAccessImpl();
// addVMStateListener(jdwpStateModel);
this.javaThreadGroupProvider = new ThreadGroupProviderImpl(this, true);
this.nativeThreadGroupProvider = new ThreadGroupProviderImpl(this, false);
this.breakpointManager = VmBreakpointManager.make(this);
this.watchpointManager = teleProcess.watchpointsEnabled() ? VmWatchpointManager.make(this, teleProcess) : null;
this.invalidReferencesLogger = new InvalidReferencesLogger(this);
this.gcAnalyzingListeners = new VMEventDispatcher<MaxGCPhaseListener>(methodAccess.gcAnalyzingMethodLocation(), "at GC start") {
@Override
protected void listenerDo(MaxThread thread, MaxGCPhaseListener listener) {
listener.gcPhaseChange(HeapPhase.ANALYZING);
}
};
this.gcReclaimingListeners = new VMEventDispatcher<MaxGCPhaseListener>(methodAccess.gcReclaimingMethodLocation(), "at GC transition to reclaiming") {
@Override
protected void listenerDo(MaxThread thread, MaxGCPhaseListener listener) {
listener.gcPhaseChange(HeapPhase.RECLAIMING);
}
};
this.gcMutatingListeners = new VMEventDispatcher<MaxGCPhaseListener>(methodAccess.gcMutatingMethodLocation(), "at GC completion") {
@Override
protected void listenerDo(MaxThread thread, MaxGCPhaseListener listener) {
listener.gcPhaseChange(HeapPhase.MUTATING);
}
};
this.threadEntryListeners = new VMEventDispatcher<MaxVMThreadEntryListener>(methodAccess.vmThreadRunMethodLocation(), "at VmThread entry") {
@Override
protected void listenerDo(MaxThread thread, MaxVMThreadEntryListener listener) {
listener.entered(thread);
}
};
this.threadDetachListeners = new VMEventDispatcher<MaxVMThreadDetachedListener>(methodAccess.vmThreadDetachedMethodLocation(), "after VmThread detach") {
@Override
protected void listenerDo(MaxThread thread, MaxVMThreadDetachedListener listener) {
listener.detached(thread);
}
};
tracer.end(null);
}
/**
* Updates information about the state of the VM that is read
* and cached at the end of each VM execution cycle.
* <p>
* This must be called in a context where thread-safe read access to the VM can
* be achieved.
* <p>
* Some lazy initialization is done, in order to avoid cycles during startup.
* <p>
* Note that gathering of thread information happens <em>after</em> this during
* the normal refresh cycle. See {@link TeleProcess}.
*
* @param epoch the number of times the process has run so far
* @throws TeleError if unable to acquire the VM lock
* @see #lock
*/
public final void updateVMCaches(long epoch) {
if (!tryLock(DEFAULT_MAX_LOCK_TRIALS)) {
TeleError.unexpected("TeleVM unable to acquire VM lock for update at epoch=" + epoch);
}
try {
updateTracer.begin("epoch=" + epoch);
if (classAccess == null) {
/**
* Must delay creation/initialization of the {@linkplain VmClassAccess "class registry"} until after
* we hit the first execution breakpoint; otherwise addresses won't have been relocated.
* This depends on the {@link VmHeapAccess} already existing.
*/
classAccess = VmClassAccess.make(this, epoch);
/**
* Can only fully initialize the {@link VmHeapAccess} once
* the {@link VmClassAccess} is fully created, otherwise there's a cycle.
*/
heapAccess.initialize(epoch);
// Now set up the initial map of the compiled code cache
codeCacheAccess = new VmCodeCacheAccess(this);
codeCacheAccess.initialize(epoch);
nativeCodeAccess = new NativeCodeAccess(this);
// Locate the root object in the VM that holds the VM's configuration.
// We can determine most things from the local instance, but the remote
// object is needed for references to specific objects in the VM.
teleMaxineVM = (TeleMaxineVM) objects().makeTeleObject(fields().MaxineVM_vm.readRemoteReference(this));
teleVMConfiguration = teleMaxineVM.teleVMConfiguration();
if (isAttaching()) {
// Check that the target was run with option MakeInspectable otherwise the dynamic heap info will not be available
TeleError.check((fields().Inspectable_flags.readInt(this) & Inspectable.INSPECTED) != 0, "target VM was not run with -XX:+MakeInspectable option");
classAccess.processAttachFixupList();
}
// read the list of actual VmThreadLocal values from the target
TeleThreadLocalsArea.Static.values(this);
// At this point everything should be read to go; handle any requests
// for late initialization.
for (InitializationListener listener : initializationListeners) {
listener.initialiationComplete(epoch);
}
}
// The standard update cycle follows; it is sensitive to ordering.
// The general ordering is:
// 1. Identify any new memory locations that can hold objects and/or code
// 2. Update any existing remote object references or code pointers, based
// on the state of the manager for each region. As much as possible, this
// should be independent of object state, since that hasn't been updated yet.
// In cases where there are dependencies, steps must be taken to avoid
// circularities: (a) have the state be static in the boot heap, (b) have
// the state be in some other non-managed heap, (c) force any depended-upon
// objects to refresh before depending on their state.
// 3. Update information about classes loaded since the previous refresh; this
// will be needed to model correctly any newly allocated objects or references
// of the newly-loaded types.
// 4. Update the status, including any cached information, concerning every
// remote heap object. Remote object state can depend on just about everything else,
// especially the location and status of every allocated memory region, their
// management(GC) status, and any remote object references that point into those
// regions.
// 5. Update the status of remote code pointers and remote object references that
// point at objects in code cache memory.
// Update status of the heap: any new heap allocations, the management (GC) status
// of each region, and updating any references whose state may have changed.
heapAccess.updateMemoryStatus(epoch);
// Update the general status of the code cache, including eviction status and any new
// allocations. This also includes updating existing remote object references that
// point into any code cache that is managed. This latter requirement creates a
// circularity, since some remote object references depend on the status of objects
// (TeleTargetMethod)s in the dynamic heap, which will not have been refreshed yet.
// This is resolved by having any such update to a reference first force an explicit
// object refresh on any TaleTargetMethod whose state matters. That refresh, in turn,
// depends on the reference to that TeleTargetMethod having been updated. That creates
// the requirement that references in the dynamic heap be updated before any references
// that might be in the code cache, i.e. why this update follows the heap update.
codeCacheAccess.updateMemoryStatus(epoch);
// Update the general status of any native, dynamically loaded libraries in the address space
nativeCodeAccess.updateMemoryStatus(epoch);
// Update registry of loaded classes, so we can understand object types
classAccess.updateCache(epoch);
// Update every local surrogate for a VM object.
// All these updates depend on remote object references, all of which must have been
// refreshed earlier.
objectAccess.updateCache(epoch);
// Detailed update of the contents of every code cache region, as well as information about native code.
machineCodeAccess.updateCache(epoch);
// Check the status of breakpoints, for example if any are set in recently evicted compilations.
// This requires that the status of any managed code cache has already been updated.
breakpointManager.updateCache(epoch);
// At this point in the refresh cycle, we should be current with every VM-allocated memory region.
// What's not done yet is updating the thread memory regions, which happens by refresh calls in TeleProcess.
updateTracer.end("epoch=" + epoch);
} finally {
unlock();
}
}
public final TeleVM vm() {
return this;
}
public final String entityName() {
return MaxineVM.name();
}
public final String entityDescription() {
return MaxineVM.description();
}
public final MaxEntityMemoryRegion<MaxVM> memoryRegion() {
return null;
}
/**
* {@inheritDoc}
* <p>
* Note that this implementation does not use the most current
* information if called during the VM refresh cycle.
*/
public final boolean contains(Address address) {
return teleVMState.findMemoryRegion(address) != null;
}
public final TeleObject representation() {
return teleMaxineVM;
}
public final String getVersion() {
return MaxineVM.VERSION_STRING;
}
public final String getDescription() {
return MaxineVM.description();
}
public final VmPlatform platform() {
return platform;
}
public final File vmDirectory() {
return vmDirectory;
}
public final BootImage bootImage() {
return bootImage;
}
public final File bootImageFile() {
return bootImageFile;
}
public final File vmLogFile() {
return vmLogFile;
}
public final File programFile() {
return programFile;
}
public final MaxInspectionMode inspectionMode() {
return mode;
}
public final VmClassAccess classes() {
return classAccess;
}
public final VmAddressSpace addressSpace() {
return addressSpace;
}
public final VmMemoryIO memoryIO() {
return memoryIO;
}
public final VmObjectAccess objects() {
return objectAccess;
}
public final VmReferenceManager referenceManager() {
return referenceManager;
}
public final VmHeapAccess heap() {
return heapAccess;
}
public final VmMethodAccess methods() {
return methodAccess;
}
public final VmCodeCacheAccess codeCache() {
return codeCacheAccess;
}
public final NativeCodeAccess nativeCode() {
return nativeCodeAccess;
}
public final VmCodeLocationManager codeLocations() {
return codeLocationManager;
}
public final VmMachineCodeAccess machineCode() {
return machineCodeAccess;
}
public final VmBreakpointManager breakpointManager() {
return breakpointManager;
}
public final VmWatchpoint.VmWatchpointManager watchpointManager() {
return watchpointManager;
}
public final VmThreadAccess threadManager() {
return threadAccess;
}
public final TeleVMLog vmLog() {
return TeleVMLog.getVMLog(this);
}
/**
* Register an action that will take place once, all of the initialization for a remote inspection session
* are substantially complete; some services have needs for setup that can only happen very
* late, most notably anything that requires setting a breakpoint.
*/
public final void addInitializationListener(InitializationListener initializationListener) {
initializationListeners.add(initializationListener);
}
/**
* Returns the most recently notified VM state. Note that this
* isn't updated until the very end of a refresh cycle after VM
* halt, so it should be considered out of date until the refresh
* cycle is complete. This is especially important when making
* decisions concerning the process epoch.
* <p>
* Use {@link TeleProcess#epoch()} directly during the refresh
* cycle, which is updated at the beginning of the refresh cycle.
*
* @return VM state; thread safe.
*/
public final TeleVMState state() {
return teleVMState;
}
public final void addVMStateListener(MaxVMStateListener listener) {
vmStateListeners.add(listener);
}
public final void removeVMStateListener(MaxVMStateListener listener) {
vmStateListeners.remove(listener);
}
/**
* {@inheritDoc}
* <p>
* Registering this listener will cause one or more breakpoints to be created, if they don't exist,
* so this must be called after all the other inspection services are in place.
*/
public final void addGCPhaseListener(HeapPhase phase, MaxGCPhaseListener listener) throws MaxVMBusyException {
if (phase == null) {
gcAnalyzingListeners.add(listener, teleProcess);
gcReclaimingListeners.add(listener, teleProcess);
gcMutatingListeners.add(listener, teleProcess);
} else {
switch (phase) {
case ANALYZING:
gcAnalyzingListeners.add(listener, teleProcess);
break;
case RECLAIMING:
gcReclaimingListeners.add(listener, teleProcess);
break;
case MUTATING:
gcMutatingListeners.add(listener, teleProcess);
break;
}
}
}
/**
* {@inheritDoc}
* <p>
* Registering this listener will cause one or more breakpoints to be created, if they don't exist,
* so this must be called after all the other inspection services are in place.
*/
public final void removeGCPhaseListener(MaxGCPhaseListener listener, HeapPhase phase) throws MaxVMBusyException {
if (phase == null) {
gcAnalyzingListeners.remove(listener);
gcReclaimingListeners.remove(listener);
gcMutatingListeners.remove(listener);
} else {
switch (phase) {
case ANALYZING:
gcAnalyzingListeners.remove(listener);
break;
case RECLAIMING:
gcReclaimingListeners.remove(listener);
break;
case MUTATING:
gcMutatingListeners.remove(listener);
break;
}
}
}
public final void addThreadEnterListener(MaxVMThreadEntryListener listener) throws MaxVMBusyException {
threadEntryListeners.add(listener, teleProcess);
}
public final void addThreadDetachedListener(MaxVMThreadDetachedListener listener) throws MaxVMBusyException {
threadDetachListeners.add(listener, teleProcess);
}
public final void removeThreadEnterListener(MaxVMThreadEntryListener listener) throws MaxVMBusyException {
threadEntryListeners.remove(listener);
}
public final void removeThreadDetachedListener(MaxVMThreadDetachedListener listener) throws MaxVMBusyException {
threadDetachListeners.remove(listener);
}
/**
* Acquires a lock on the VM process and related cached state; blocks until lock
* can be acquired. The lock is reentrant, so that nested lock acquisition behaves with
* standard Java synchronization semantics.
*/
public final void lock() {
lock.lock();
}
public final boolean tryLock(int maxTrials) {
int trials = 0;
while (!vm().tryLock()) {
if (++trials > maxTrials) {
return false;
}
}
return true;
}
/**
* Determines whether the calling thread holds the VM lock.
* <p>
* <strong>Note: this device is mainly used at present to
* support the re-engineering effort to add reliable thread safety.
* It may be set to be always {@code true} in released versions
* of the code.
*
* @return whether the VM lock is held (now always TRUE)
* @see #lock
* @see #tryLock()
* @see #unlock()
*/
public final boolean lockHeldByCurrentThread() {
// TODO (mlvdv) restore thread lock predicate to operation; always true now
return true;
// return lock.isHeldByCurrentThread();
}
/**
* Releases the lock on the VM process and related cached state; returns
* immediately. The lock is reentrant, so that nested lock acquisition behaves with
* standard Java synchronization semantics.
*/
public final void unlock() {
lock.unlock();
}
private static final int DEFAULT_MAX_LOCK_TRIALS = 100;
/**
* Attempts to acquire a lock on the VM process and related cached state; returns
* immediately. The lock is reentrant, so that nested lock acquisition behaves with
* standard Java synchronization semantics.
*
* @return whether the lock was acquired
*/
public final boolean tryLock() {
return lock.tryLock();
}
public final void acquireLegacyVMAccess() throws MaxVMBusyException {
if (!tryLock(DEFAULT_MAX_LOCK_TRIALS)) {
throw new MaxVMBusyException();
}
}
public final void releaseLegacyVMAccess() {
assert lockHeldByCurrentThread();
unlock();
}
/**
* Sets or clears some bits of the {@link Inspectable} field in the VM process.
* <p>
* Must be called in a thread holding the VM lock.
*
* @param flags specifies which bits to set or clear
* @param set if {@code true}, then the bits are set otherwise they are cleared
*/
public final void modifyInspectableFlags(int flags, boolean set) {
assert lockHeldByCurrentThread();
int newFlags = fieldAccess.Inspectable_flags.readInt(this);
if (set) {
newFlags |= flags;
} else {
newFlags &= ~flags;
}
fieldAccess.Inspectable_flags.writeInt(this, newFlags);
}
/**
* Starts a new VM process and returns a handle to it.
*
* @param commandLineArguments the command line arguments to use when starting the VM process
* @return a handle to the created VM process
* @throws BootImageException if there was an error launching the VM process
*/
protected abstract TeleProcess createTeleProcess(String[] commandLineArguments) throws BootImageException;
/**
* Gets any memory regions of potential interest that are specific to a particular VM platform.
* This gets called during the VM refresh cycle and the results are cached along with all other
* known allocations.
*
* @return a list of platform-specific memory regions, empty if none.
*/
protected List<MaxEntityMemoryRegion<? extends MaxEntity> > platformMemoryRegions() {
return EMPTY_MAXMEMORYREGION_LIST;
}
/**
* Attach to an existing VM process or code dump file.
* @return TeleProcess instance
* @throws BootImageException
*/
protected TeleProcess attachToTeleProcess() throws BootImageException {
throw TeleError.unimplemented();
}
/**
* Gets a pointer to the boot image in the remote VM.
*
* @throws BootImageException if the address of the boot image could not be obtained
*/
protected Pointer loadBootImage() throws BootImageException {
final long value = teleChannelProtocol.getBootHeapStart();
if (value == 0) {
throw new BootImageException("failed to get boot image start from target VM");
}
return Pointer.fromLong(value);
}
private static void addNonNull(ArrayList<MaxMemoryRegion> regions, MaxMemoryRegion region) {
if (region != null) {
regions.add(region);
}
}
/**
* Notifies all registered listeners that the state of the process has changed,
* for example started, stopped, or terminated. Gathers up summary information
* and creates a (top-level) immutable record of the state to accompany the notification.
* <p>
* <strong>Notes:</strong> This data gets posted only at the very end of the VM update
* cycle, and so should not be relied upon during the update.
*
* @param processState the new process state
* @param epoch
* @param singleStepThread the thread, if any, that just completed a single step
* @param threads currently existing threads
* @param threadsStarted threads newly created since last notification
* @param threadsDied threads newly died since last notification
* @param breakpointEvents breakpoint events, if any, that caused this state change
* @param watchpointEvent watchpoint, if any, that caused this state change
* @see ProcessState
*/
public final void notifyStateChange(
ProcessState processState,
long epoch,
TeleNativeThread singleStepThread,
Collection<TeleNativeThread> threads,
List<TeleNativeThread> threadsStarted,
List<TeleNativeThread> threadsDied,
List<TeleBreakpointEvent> breakpointEvents,
VmWatchpointEvent watchpointEvent) {
this.teleVMState = new TeleVMState(
mode,
processState,
epoch,
addressSpace.allocations(),
threads,
singleStepThread,
threadsStarted,
threadsDied,
breakpointEvents,
watchpointEvent,
heapAccess.phase(), codeCacheAccess.isInEviction(), teleVMState);
for (final MaxVMStateListener listener : vmStateListeners) {
listener.stateChanged(teleVMState);
}
}
public final int getInterpreterUseLevel() {
return interpreterUseLevel;
}
public final void setInterpreterUseLevel(int interpreterUseLevel) {
this.interpreterUseLevel = interpreterUseLevel;
}
public final int getVMTraceLevel() {
return fields().Trace_level.readInt(this);
}
public final void setVMTraceLevel(int newLevel) {
fields().Trace_level.writeInt(this, newLevel);
}
public final long getVMTraceThreshold() {
return fields().Trace_threshold.readLong(this);
}
public final void setVMTraceThreshold(long newThreshold) {
fields().Trace_threshold.writeLong(this, newThreshold);
}
public final HeapScheme heapScheme() {
return vmConfiguration.heapScheme();
}
public final LayoutScheme layoutScheme() {
return vmConfiguration.layoutScheme();
}
public final RemoteReferenceScheme referenceScheme() {
return (RemoteReferenceScheme) vmConfiguration.referenceScheme();
}
public final RemoteReference makeReference(Address origin) {
return referenceManager.makeReference(origin);
}
public final RemoteReference makeQuasiObjectReference(Address origin) {
return referenceManager.makeQuasiReference(origin);
}
public final ReferenceValue createReferenceValue(RemoteReference reference) {
return referenceManager.createReferenceValue(reference);
}
/**
* Returns a local copy of a {@link String} object in the VM's heap.
*
* @param stringRef A {@link String} object in the VM.
* @return A local {@link String} duplicating the object's contents.
* @throws InvalidReferenceException if the argument does not point a valid heap object.
*/
public final String getString(RemoteReference stringRef) throws InvalidReferenceException {
return TeleString.getString(this, stringRef);
}
/**
* Returns a local copy of the contents of a {@link String} object in the VM's heap,
* using low level mechanisms and performing no checking that the location
* or object are valid.
* <p>
* The intention is to provide a fast, low-level mechanism for reading strings that
* can be used outside of the AWT event thread without danger of deadlock,
* for example on the canonical reference machinery.
* <p>
* <strong>Unsafe:</strong> this method depends on knowledge of the implementation of
* class {@link String}.
*
* @param origin a {@link String} object in the VM
* @return A local {@link String} duplicating the remote object's contents, null if it can't be read.
*/
public String getStringUnsafe(Address origin) {
return TeleString.getStringUnsafe(this, origin);
}
public final List<MaxObject> inspectableObjects() {
final List<MaxObject> inspectableObjects = new ArrayList<MaxObject>();
inspectableObjects.add(teleMaxineVM);
inspectableObjects.add(teleVMConfiguration);
try {
inspectableObjects.add(objectAccess.vmBootClassRegistry());
} catch (MaxVMBusyException e) {
}
inspectableObjects.addAll(objectAccess.schemes());
inspectableObjects.addAll(codeCacheAccess.codeCacheInspectableObjects());
inspectableObjects.addAll(heapAccess.heapInspectableObjects());
return inspectableObjects;
}
public final List<MaxCodeLocation> inspectableMethods() {
final List<MaxCodeLocation> inspectableMethods = new ArrayList<MaxCodeLocation>(methods().clientInspectableMethods());
inspectableMethods.addAll(heapAccess.heapInspectableMethods());
return inspectableMethods;
}
public final <TeleMethodActor_Type extends TeleMethodActor> TeleMethodActor_Type findTeleMethodActor(Class<TeleMethodActor_Type> teleMethodActorType, MethodActor methodActor) {
final TeleClassActor teleClassActor = classAccess.findTeleClassActor(methodActor.holder().typeDescriptor);
if (teleClassActor != null) {
for (TeleMethodActor teleMethodActor : teleClassActor.getTeleMethodActors()) {
final MethodActor actor = teleMethodActor.methodActor();
if (actor != null && actor.equals(methodActor)) {
return teleMethodActorType.cast(teleMethodActor);
}
}
}
return null;
}
public final void setTransportDebugLevel(int level) {
teleProcess.setTransportDebugLevel(level);
}
public final int transportDebugLevel() {
return teleProcess.transportDebugLevel();
}
public void advanceToJavaEntryPoint() throws IOException {
final Address startEntryAddress = bootImageStart().plus(bootImage().header.vmRunMethodOffset);
try {
final MachineCodeLocation entryLocation = codeLocations().createMachineCodeLocation(startEntryAddress, "vm start address");
runToInstruction(entryLocation, true, false);
} catch (InvalidCodeAddressException exception) {
TeleError.unexpected("Unable to set breakpoint at Java entry point " + exception.getAddressString() + ": " + exception.getMessage());
} catch (Exception exception) {
throw new IOException(exception);
}
}
public final Value interpretMethod(ClassMethodActor classMethodActor, Value... arguments) throws InvocationTargetException {
return TeleInterpreter.execute(this, classMethodActor, arguments);
}
public final void resume(final boolean synchronous, final boolean withClientBreakpoints) throws InvalidVMRequestException, OSExecutionRequestException {
teleProcess.resume(synchronous, withClientBreakpoints);
}
public final void singleStepThread(final MaxThread maxThread, boolean synchronous) throws InvalidVMRequestException, OSExecutionRequestException {
final TeleNativeThread teleNativeThread = (TeleNativeThread) maxThread;
teleProcess.singleStepThread(teleNativeThread, synchronous);
}
public final void stepOver(final MaxThread maxThread, boolean synchronous, final boolean withClientBreakpoints) throws InvalidVMRequestException, OSExecutionRequestException {
final TeleNativeThread teleNativeThread = (TeleNativeThread) maxThread;
teleProcess.stepOver(teleNativeThread, synchronous, withClientBreakpoints);
}
public final void runToInstruction(final MaxCodeLocation maxCodeLocation, final boolean synchronous, final boolean withClientBreakpoints) throws OSExecutionRequestException, InvalidVMRequestException {
final CodeLocation codeLocation = (CodeLocation) maxCodeLocation;
teleProcess.runToInstruction(codeLocation, synchronous, withClientBreakpoints);
}
public final void returnFromFrame(final MaxThread thread, final boolean synchronous, final boolean withClientBreakpoints) throws OSExecutionRequestException, InvalidVMRequestException {
final TeleNativeThread teleNativeThread = (TeleNativeThread) thread;
final CodeLocation returnLocation = teleNativeThread.stack().returnLocation();
if (returnLocation == null) {
throw new InvalidVMRequestException("No return location available");
}
teleProcess.runToInstruction(returnLocation, synchronous, withClientBreakpoints);
}
public final void pauseVM() throws InvalidVMRequestException, OSExecutionRequestException {
teleProcess.pauseProcess();
}
public final void terminateVM() throws Exception {
teleProcess.terminateProcess();
}
public final File findJavaSourceFile(ClassActor classActor) {
final String sourceFilePath = classActor.sourceFilePath();
return sourcepath.findFile(sourceFilePath);
}
public final void executeCommandsFromFile(String fileName) {
FileCommands.executeCommandsFromFile(this, fileName);
}
/**
* Gets the configuration descriptor instance on the VM; the local clone can be used
* for most things, but for references to other VM objects we require the remote object.
*
* @return the VM object that holds configuration information.
*/
public final TeleVMConfiguration teleVMConfiguration() {
return teleVMConfiguration;
}
//
// Code from here to end of file supports the Maxine JDWP server
//
/**
* Provides access to the VM from a JDWP server.
*/
private final VMAccess jdwpAccess;
/**
* @return access to the VM for the JDWP server.
*/
public final VMAccess vmAccess() {
return jdwpAccess;
}
public final void fireJDWPThreadEvents() {
for (MaxThread thread : teleVMState.threadsDied()) {
fireJDWPThreadDiedEvent((TeleNativeThread) thread);
}
for (MaxThread thread : teleVMState.threadsStarted()) {
fireJDWPThreadStartedEvent((TeleNativeThread) thread);
}
}
private final ArrayList<VMListener> jdwpListeners = new ArrayList<VMListener>();
/**
* Informs all JDWP listeners that the VM died.
*/
private void fireJDWPVMDiedEvent() {
LOGGER.info("VM EVENT: VM died");
for (VMListener listener : jdwpListeners) {
listener.vmDied();
}
}
/**
* Informs all JDWP listeners that a single step has been completed.
*
* @param thread the thread that did the single step
* @param location the code location onto which the thread just stepped
*/
private void fireJDWPSingleStepEvent(ThreadProvider thread, JdwpCodeLocation location) {
LOGGER.info("VM EVENT: Single step was made at thread " + thread
+ " to location " + location);
for (VMListener listener : jdwpListeners) {
listener.singleStepMade(thread, location);
}
}
/**
* Informs all JDWP listeners that a breakpoint has been hit.
*
* @param thread the thread that hit the breakpoint
* @param location the code location at which the breakpoint was hit
*/
private void fireJDWPBreakpointEvent(ThreadProvider thread, JdwpCodeLocation location) {
LOGGER.info("VM EVENT: Breakpoint hit at thread " + thread
+ " at location " + location);
for (VMListener listener : jdwpListeners) {
listener.breakpointHit(thread, location);
}
}
/**
* Informs all JDWP listeners that a thread has started.
*
* @param thread the thread that has started
*/
private void fireJDWPThreadStartedEvent(ThreadProvider thread) {
LOGGER.info("VM EVENT: Thread started: " + thread);
for (VMListener listener : jdwpListeners) {
listener.threadStarted(thread);
}
}
/**
* Informs all JDWP listeners that a thread has died.
*
* @param thread the thread that has died
*/
private void fireJDWPThreadDiedEvent(ThreadProvider thread) {
LOGGER.info("VM EVENT: Thread died: " + thread);
for (VMListener listener : jdwpListeners) {
listener.threadDied(thread);
}
}
private final MaxVMStateListener jdwpStateModel = new MaxVMStateListener() {
public void stateChanged(MaxVMState maxVMState) {
Trace.begin(TRACE_VALUE, tracePrefix() + "handling " + maxVMState);
fireJDWPThreadEvents();
switch(maxVMState.processState()) {
case TERMINATED:
fireJDWPVMDiedEvent();
break;
case STOPPED:
if (!jdwpListeners.isEmpty()) {
for (MaxBreakpointEvent maxBreakpointEvent : maxVMState.breakpointEvents()) {
final TeleNativeThread teleNativeThread = (TeleNativeThread) maxBreakpointEvent.thread();
fireJDWPBreakpointEvent(teleNativeThread, teleNativeThread.getFrames()[0].getLocation());
}
final MaxThread singleStepThread = maxVMState.singleStepThread();
if (singleStepThread != null) {
final TeleNativeThread thread = (TeleNativeThread) singleStepThread;
fireJDWPSingleStepEvent(thread, thread.getFrames()[0].getLocation());
}
}
break;
case RUNNING:
LOGGER.info("VM continued to RUN!");
break;
}
Trace.end(TRACE_VALUE, tracePrefix() + "handling " + maxVMState);
}
};
/**
* Tries to find a JDWP ObjectProvider that represents the object that is
* referenced by the parameter.
*
* @param reference
* a reference to the object that should be represented as a JDWP
* ObjectProvider
* @return a JDWP ObjectProvider object or null, if no object is found at
* the address specified by the reference
*/
private ObjectProvider findObject(RemoteReference reference) {
return objects().makeTeleObject(reference);
}
private final ThreadGroupProvider javaThreadGroupProvider;
/**
* @return Thread group that should be used to logically group Java threads in the VM.
*/
public final ThreadGroupProvider javaThreadGroupProvider() {
return javaThreadGroupProvider;
}
private final ThreadGroupProvider nativeThreadGroupProvider;
/**
* @return Thread group that should be used to logically group native threads.
*/
public final ThreadGroupProvider nativeThreadGroupProvider() {
return nativeThreadGroupProvider;
}
/**
* Converts a value kind as seen by the Maxine world to a VMValue type as
* seen by the VM interface used by the JDWP server.
*
* @param kind the Maxine kind value
* @return the type as seen by the JDWP server
*/
public static Type maxineKindToJDWPType(Kind kind) {
final KindEnum e = kind.asEnum;
switch (e) {
case BOOLEAN:
return VMValue.Type.BOOLEAN;
case BYTE:
return VMValue.Type.BYTE;
case CHAR:
return VMValue.Type.CHAR;
case DOUBLE:
return VMValue.Type.DOUBLE;
case FLOAT:
return VMValue.Type.FLOAT;
case INT:
return VMValue.Type.INT;
case LONG:
return VMValue.Type.LONG;
case REFERENCE:
return VMValue.Type.PROVIDER;
case SHORT:
return VMValue.Type.SHORT;
case VOID:
return VMValue.Type.VOID;
case WORD:
break;
}
throw new IllegalArgumentException("Typeype " + kind
+ " cannot be resolved to a virtual machine value type");
}
/**
* Converts a value as seen by the Maxine VM to a value as seen by the JDWP
* server.
*
* @param value the value as seen by the Maxine VM
* @return the value as seen by the JDWP server
*/
public final VMValue maxineValueToJDWPValue(Value value) {
switch (value.kind().asEnum) {
case BOOLEAN:
return jdwpAccess.createBooleanValue(value.asBoolean());
case BYTE:
return jdwpAccess.createByteValue(value.asByte());
case CHAR:
return jdwpAccess.createCharValue(value.asChar());
case DOUBLE:
return jdwpAccess.createDoubleValue(value.asDouble());
case FLOAT:
return jdwpAccess.createFloatValue(value.asFloat());
case INT:
return jdwpAccess.createIntValue(value.asInt());
case LONG:
return jdwpAccess.createLongValue(value.asLong());
case REFERENCE:
return jdwpAccess.createObjectProviderValue(findObject((RemoteReference) value.asReference()));
case SHORT:
return jdwpAccess.createShortValue(value.asShort());
case VOID:
return jdwpAccess.getVoidValue();
case WORD:
final Word word = value.asWord();
LOGGER.warning("Tried to convert a word, this is not implemented yet! (word="
+ word + ")");
return jdwpAccess.getVoidValue();
}
throw new IllegalArgumentException("Unkown kind: " + value.kind());
}
/**
* Converts a JDWP value object to a Maxine value object.
*
* @param vmValue the value as seen by the JDWP server
* @return a newly created value as seen by the Maxine VM
*/
public final Value jdwpValueToMaxineValue(VMValue vmValue) {
if (vmValue.isVoid()) {
return VoidValue.VOID;
} else if (vmValue.asBoolean() != null) {
return BooleanValue.from(vmValue.asBoolean());
} else if (vmValue.asByte() != null) {
return ByteValue.from(vmValue.asByte());
} else if (vmValue.asChar() != null) {
return CharValue.from(vmValue.asChar());
} else if (vmValue.asDouble() != null) {
return DoubleValue.from(vmValue.asDouble());
} else if (vmValue.asFloat() != null) {
return FloatValue.from(vmValue.asFloat());
} else if (vmValue.asInt() != null) {
return IntValue.from(vmValue.asInt());
} else if (vmValue.asLong() != null) {
return LongValue.from(vmValue.asLong());
} else if (vmValue.asShort() != null) {
return ShortValue.from(vmValue.asShort());
} else if (vmValue.asProvider() != null) {
final Provider p = vmValue.asProvider();
if (p instanceof TeleObject) {
return TeleReferenceValue.from(this, ((TeleObject) p).reference());
}
throw new IllegalArgumentException(
"Could not convert the provider object " + p
+ " to a reference!");
}
throw new IllegalArgumentException("Unknown VirtualMachineValue type!");
}
private TeleNativeThread registeredSingleStepThread;
public final void registerSingleStepThread(TeleNativeThread teleNativeThread) {
if (registeredSingleStepThread != null) {
LOGGER.warning("Overwriting registered single step thread! "
+ registeredSingleStepThread);
}
registeredSingleStepThread = teleNativeThread;
}
private TeleNativeThread registeredStepOutThread;
public final void registerStepOutThread(TeleNativeThread teleNativeThread) {
if (registeredStepOutThread != null) {
LOGGER.warning("Overwriting registered step out thread! "
+ registeredStepOutThread);
}
registeredStepOutThread = teleNativeThread;
}
/**
* Provides access to a VM by a JDWP server.
* Not fully implemented
* TeleVM might eventually implement the interfaced {@link VMAccess} directly; moving in that direction.
*
*/
private final class VMAccessImpl implements VMAccess {
// Factory for creating fake object providers that represent Java objects
// living in the JDWP server.
private final JavaProviderFactory javaProviderFactory;
private final Set<JdwpCodeLocation> breakpointLocations = new HashSet<JdwpCodeLocation>();
public VMAccessImpl() {
javaProviderFactory = new JavaProviderFactory(this, null);
}
public String getName() {
return TeleVM.this.entityName();
}
public String getVersion() {
return TeleVM.this.getVersion();
}
public String getDescription() {
return TeleVM.this.getDescription();
}
public void dispose() {
// TODO: Consider implementing disposal of the VM when told so by a JDWP
// command.
LOGGER.warning("Asked to DISPOSE VM, doing nothing");
}
public void suspend() {
if (teleProcess.processState() == RUNNING) {
LOGGER.info("Pausing VM...");
try {
TeleVM.this.pauseVM();
} catch (OSExecutionRequestException osExecutionRequestException) {
LOGGER.log(Level.SEVERE,
"Unexpected error while pausing the VM", osExecutionRequestException);
} catch (InvalidVMRequestException invalidProcessRequestException) {
LOGGER.log(Level.SEVERE,
"Unexpected error while pausing the VM", invalidProcessRequestException);
}
} else {
LOGGER.warning("Suspend called while VM not running!");
}
}
public void resume() {
if (teleProcess.processState() == STOPPED) {
if (registeredSingleStepThread != null) {
// There has been a thread registered for performing a single
// step => perform single step instead of resume.
try {
LOGGER.info("Doing single step instead of resume!");
TeleVM.this.singleStepThread(registeredSingleStepThread, false);
} catch (OSExecutionRequestException osExecutionRequestException) {
LOGGER.log(
Level.SEVERE,
"Unexpected error while performing a single step in the VM",
osExecutionRequestException);
} catch (InvalidVMRequestException e) {
LOGGER.log(
Level.SEVERE,
"Unexpected error while performing a single step in the VM",
e);
}
registeredSingleStepThread = null;
} else if (registeredStepOutThread != null
&& registeredStepOutThread.stack().returnLocation().address() != null) {
// There has been a thread registered for performing a step out
// => perform a step out instead of resume.
final CodeLocation returnLocation = registeredStepOutThread.stack().returnLocation();
assert returnLocation != null;
try {
TeleVM.this.runToInstruction(returnLocation, false, true);
} catch (OSExecutionRequestException osExecutionRequestException) {
LOGGER.log(
Level.SEVERE,
"Unexpected error while performing a run-to-instruction in the VM",
osExecutionRequestException);
} catch (InvalidVMRequestException invalidProcessRequestException) {
LOGGER.log(
Level.SEVERE,
"Unexpected error while performing a run-to-instruction in the VM",
invalidProcessRequestException);
}
registeredStepOutThread = null;
} else {
// Nobody registered for special commands => resume the Vm.
try {
LOGGER.info("Client tried to resume the VM!");
TeleVM.this.resume(false, true);
} catch (OSExecutionRequestException e) {
LOGGER.log(Level.SEVERE,
"Unexpected error while resuming the VM", e);
} catch (InvalidVMRequestException e) {
LOGGER.log(Level.SEVERE,
"Unexpected error while resuming the VM", e);
}
}
} else {
LOGGER.severe("Client tried to resume the VM, but tele process is not in stopped state!");
}
}
public void exit(int code) {
try {
TeleVM.this.terminateVM();
} catch (Exception exception) {
LOGGER.log(Level.SEVERE,
"Unexpected error while exidting the VM", exception);
}
}
public void addListener(VMListener listener) {
jdwpListeners.add(listener);
}
public void removeListener(VMListener listener) {
jdwpListeners.remove(listener);
}
/**
* Sets a breakpoint at the specified code location. This function currently has the following severe limitations:
* Always sets the breakpoint at the call entry point of a method. Does ignore the suspendAll parameter, there will
* always be all threads suspended when the breakpoint is hit.
*
* TODO: Fix the limitations for breakpoints.
*
* @param codeLocation specifies the code location at which the breakpoint should be set
* @param suspendAll if true, all threads should be suspended when the breakpoint is hit
*/
public void addBreakpoint(JdwpCodeLocation codeLocation, boolean suspendAll) {
// For now ignore duplicates
if (breakpointLocations.contains(codeLocation)) {
return;
}
assert codeLocation.method() instanceof TeleClassMethodActor : "Only tele method actors allowed here";
assert !breakpointLocations.contains(codeLocation);
breakpointLocations.add(codeLocation);
assert breakpointLocations.contains(codeLocation);
final TeleClassMethodActor teleClassMethodActor = (TeleClassMethodActor) codeLocation.method();
final BytecodeLocation methodCodeLocation = codeLocations().createBytecodeLocation(teleClassMethodActor, 0, "");
try {
TeleVM.this.breakpointManager().makeBreakpoint(methodCodeLocation);
} catch (MaxVMBusyException maxVMBusyException) {
TeleError.unexpected("breakpoint creation failed");
}
Trace.line(TRACE_VALUE, tracePrefix() + "Breakpoint set at: " + methodCodeLocation);
}
public void removeBreakpoint(JdwpCodeLocation codeLocation) {
if (codeLocation.isMachineCode()) {
MachineCodeLocation location = null;
try {
location = codeLocations().createMachineCodeLocation(Address.fromLong(codeLocation.position()), "jdwp location");
final MaxBreakpoint breakpoint = TeleVM.this.breakpointManager().findBreakpoint(location);
if (breakpoint != null) {
breakpoint.remove();
}
} catch (MaxVMBusyException maxVMBusyException) {
TeleError.unexpected("breakpoint removal failed");
} catch (InvalidCodeAddressException e) {
TeleError.unexpected("bad breakpoint address");
}
}
assert breakpointLocations.contains(codeLocation);
breakpointLocations.remove(codeLocation);
assert !breakpointLocations.contains(codeLocation);
}
public byte[] accessMemory(long start, int length) {
final byte[] bytes = new byte[length];
TeleVM.this.memoryIO().readBytes(Address.fromLong(start), bytes);
return bytes;
}
public VMValue createBooleanValue(boolean b) {
return createJavaObjectValue(b, Boolean.TYPE);
}
public VMValue createByteValue(byte b) {
return createJavaObjectValue(b, Byte.TYPE);
}
public VMValue createCharValue(char c) {
return createJavaObjectValue(c, Character.TYPE);
}
public JdwpCodeLocation createCodeLocation(MethodProvider method, long position, boolean isMachineCode) {
return new JdwpCodeLocationImpl(method, position, isMachineCode);
}
public VMValue createDoubleValue(double d) {
return createJavaObjectValue(d, Double.TYPE);
}
public VMValue createFloatValue(float f) {
return createJavaObjectValue(f, Float.TYPE);
}
public VMValue createIntValue(int i) {
return createJavaObjectValue(i, Integer.TYPE);
}
public VMValue createJavaObjectValue(Object o, Class expectedClass) {
return VMValueImpl.fromJavaObject(o, this, expectedClass);
}
public VMValue createLongValue(long l) {
return VMValueImpl.fromJavaObject(l, this, Long.TYPE);
}
public VMValue createObjectProviderValue(ObjectProvider p) {
return createJavaObjectValue(p, null);
}
public VMValue createShortValue(short s) {
return VMValueImpl.fromJavaObject(s, this, Short.TYPE);
}
public StringProvider createString(String s) {
final VMValue vmValue = createJavaObjectValue(s, String.class);
assert vmValue.asProvider() != null : "Must be a provider value object";
assert vmValue.asProvider() instanceof StringProvider : "Must be a String provider object";
return (StringProvider) vmValue.asProvider();
}
public TargetMethodAccess[] findTargetMethods(long[] addresses) {
final TargetMethodAccess[] result = new TargetMethodAccess[addresses.length];
for (int i = 0; i < addresses.length; i++) {
result[i] = TeleVM.this.machineCode().findCompilation(Address.fromLong(addresses[i])).teleTargetMethod();
}
return result;
}
public ReferenceTypeProvider[] getAllReferenceTypes() {
return classAccess.teleClassActors();
}
public ThreadProvider[] getAllThreads() {
final Collection<TeleNativeThread> threads = teleProcess().threads();
final ThreadProvider[] threadProviders = new ThreadProvider[threads.size()];
return threads.toArray(threadProviders);
}
public String[] getBootClassPath() {
return Classpath.bootClassPath().toStringArray();
}
public String[] getClassPath() {
return HostedVMClassLoader.HOSTED_VM_CLASS_LOADER.classpath().toStringArray();
}
/**
* Looks up a JDWP reference type object based on a Java class object.
*
* @param klass
* the class object whose JDWP reference type should be looked up
* @return a JDWP reference type representing the Java class
*/
public ReferenceTypeProvider getReferenceType(Class klass) {
ReferenceTypeProvider referenceTypeProvider = null;
// Always fake the Object class, otherwise try to find a class in the
// Maxine VM that matches the signature.
if (!klass.equals(Object.class)) {
referenceTypeProvider = TeleVM.this.classes().findTeleClassActor(klass);
}
// If no class was found within the Maxine VM, create a faked reference
// type object.
if (referenceTypeProvider == null) {
LOGGER.info("Creating Java provider for class " + klass);
referenceTypeProvider = javaProviderFactory.getReferenceTypeProvider(klass);
}
return referenceTypeProvider;
}
public ReferenceTypeProvider[] getReferenceTypesBySignature(String signature) {
// Always fake the Object type. This means that calls to all methods of
// the Object class will be reflectively delegated to the Object class
// that lives
// on the Tele side not to the Object class in the VM.
if (signature.equals("Ljava/lang/Object;")) {
return new ReferenceTypeProvider[] {getReferenceType(Object.class)};
}
// Try to find a matching class actor that lives within the VM based on
// the signature.
final List<ReferenceTypeProvider> result = new LinkedList<ReferenceTypeProvider>();
for (TypeDescriptor typeDescriptor : TeleVM.this.classes().typeDescriptors()) {
if (typeDescriptor.toString().equals(signature)) {
final TeleClassActor teleClassActor = TeleVM.this.classes().findTeleClassActor(typeDescriptor);
// Do not include array types, there should always be faked in
// order to be able to call newInstance on them. Arrays that are
// created this way then do
// not really live within the VM, but on the JDWP server side.
if (!(teleClassActor instanceof TeleArrayClassActor)) {
result.add(teleClassActor);
}
}
}
// If no class living in the VM was found, try to lookup Java class
// known to the JDWP server. If such a class is found, then a JDWP
// reference type is faked for it.
if (result.size() == 0) {
try {
final Class klass = JavaTypeDescriptor.resolveToJavaClass(
JavaTypeDescriptor.parseTypeDescriptor(signature), getClass().getClassLoader());
result.add(javaProviderFactory.getReferenceTypeProvider(klass));
} catch (NoClassDefFoundError noClassDefFoundError) {
LOGGER.log(Level.SEVERE,
"Error while looking up class based on signature", noClassDefFoundError);
}
}
return result.toArray(new ReferenceTypeProvider[result.size()]);
}
public ThreadGroupProvider[] getThreadGroups() {
return new ThreadGroupProvider[] {javaThreadGroupProvider, nativeThreadGroupProvider};
}
public VMValue getVoidValue() {
return VMValueImpl.VOID_VALUE;
}
}
}