/*
* Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.vm.run.java;
import static com.sun.max.vm.MaxineVM.*;
import static com.sun.max.vm.VMConfiguration.*;
import static com.sun.max.vm.VMOptions.*;
import static com.sun.max.vm.type.ClassRegistry.*;
import java.io.*;
import java.lang.instrument.*;
import java.lang.reflect.*;
import java.net.*;
import java.security.*;
import java.util.*;
import java.util.jar.*;
import sun.misc.*;
import com.sun.max.annotate.*;
import com.sun.max.program.*;
import com.sun.max.vm.*;
import com.sun.max.vm.MaxineVM.Phase;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.compiler.deopt.*;
import com.sun.max.vm.heap.*;
import com.sun.max.vm.hosted.*;
import com.sun.max.vm.instrument.*;
import com.sun.max.vm.jni.*;
import com.sun.max.vm.log.*;
import com.sun.max.vm.profilers.sampling.*;
import com.sun.max.vm.run.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.thread.*;
import com.sun.max.vm.ti.*;
import com.sun.max.vm.type.*;
/**
* The normal Java run scheme that starts up the standard JDK services, loads a user
* class that has been specified on the command line, finds its main method, and
* runs it with the specified arguments on the command line. This run scheme
* is intended to provide the same usage as the standard "java" command in
* a standard JRE.
*
* This class incorporates a lot of nasty, delicate JDK hacks that are needed to
* get the JDK reinitialized to the point that it is ready to run a new program.
*/
public class JavaRunScheme extends AbstractVMScheme implements RunScheme {
private static final VMOption versionOption = register(new VMOption(
"-version", "print product version and exit"), MaxineVM.Phase.STARTING);
private static final VMOption showVersionOption = register(new VMOption(
"-showversion", "print product version and continue"), MaxineVM.Phase.STARTING);
private static final VMOption D64Option = register(new VMOption("-d64",
"Selects the 64-bit data model if available. Currently ignored."), MaxineVM.Phase.PRISTINE);
private static final JavaAgentVMOption javaagentOption = register(new JavaAgentVMOption(), MaxineVM.Phase.STARTING);
private static final VMExtensionVMOption vmExtensionOption = register(new VMExtensionVMOption(), MaxineVM.Phase.STARTING);
private static final VMStringOption profOption = register(new VMStringOption(
"-Xprof", false, null, "run sampling profiler"), MaxineVM.Phase.STARTING);
/**
* List of classes to explicitly reinitialise in the {@link Phase#STARTING} phase.
* This supports extensions to the boot image.
*/
private static List<String> reinitClasses = new LinkedList<String>();
private static boolean profiling;
private static String mainClassName;
@HOSTED_ONLY
public JavaRunScheme() {
}
/**
* JDK methods that need to be re-executed at startup, e.g. to re-register native methods.
*/
private StaticMethodActor[] initIDMethods;
@HOSTED_ONLY
public static void registerClassForReInit(String className) {
CompiledPrototype.registerVMEntryPoint(className + ".<clinit>");
MaxineVM.registerKeepClassInit(className);
Trace.line(2, "registering " + className + " for reinitialization");
reinitClasses.add(className);
}
/**
* While bootstrapping, searches the boot class registry classes that have methods called "initIDs" with
* signature "()V". Such methods are typically used in the JDK to initialize JNI identifiers for native code, and
* need to be re-executed upon startup.
*/
@HOSTED_ONLY
public List< ? extends MethodActor> gatherNativeInitializationMethods() {
final List<StaticMethodActor> methods = new LinkedList<StaticMethodActor>();
for (ClassActor classActor : BOOT_CLASS_REGISTRY.bootImageClasses()) {
for (StaticMethodActor method : classActor.localStaticMethodActors()) {
if ((method.name.equals("initIDs") || method.name.equals("initNative")) && (method.descriptor().numberOfParameters() == 0) && method.resultKind() == Kind.VOID) {
method.makeInvocationStub();
methods.add(method);
}
}
}
initIDMethods = methods.toArray(new StaticMethodActor[methods.size()]);
return methods;
}
/**
* Runs all the native initializer methods gathered while bootstrapping.
*/
public void runNativeInitializationMethods() {
final List<StaticMethodActor> methods = new LinkedList<StaticMethodActor>();
for (StaticMethodActor method : initIDMethods) {
try {
if (method.currentTargetMethod() == null) {
FatalError.unexpected("Native initialization method must be compiled in boot image: " + method);
}
method.invoke();
} catch (UnsatisfiedLinkError unsatisfiedLinkError) {
// Library not present yet - try again next time:
methods.add(method);
} catch (InvocationTargetException invocationTargetException) {
if (invocationTargetException.getTargetException() instanceof UnsatisfiedLinkError) {
// Library not present yet - try again next time:
methods.add(method);
} else {
throw ProgramError.unexpected(invocationTargetException.getTargetException());
}
} catch (Throwable throwable) {
throw ProgramError.unexpected(throwable);
}
}
initIDMethods = methods.toArray(new StaticMethodActor[methods.size()]);
}
@ALIAS(declaringClass = System.class)
public static native void initializeSystemClass();
/**
* The initialization method of the Java run scheme runs at both bootstrapping and startup.
* While bootstrapping, it gathers the methods needed for native initialization, and at startup
* it initializes basic VM services.
*/
@Override
public void initialize(MaxineVM.Phase phase) {
switch (phase) {
case BOOTSTRAPPING: {
if (MaxineVM.isHosted()) {
// Make sure MaxineVM.exit is available when running the JavaRunScheme.
new CriticalMethod(MaxineVM.class, "exit",
SignatureDescriptor.create(void.class, int.class, boolean.class));
}
break;
}
case STARTING: {
// This hack enables (platform-dependent) tracing before the eventual System properties are set:
System.setProperty("line.separator", "\n");
// Normally, we would have to initialize tracing this late,
// because 'PrintWriter.<init>()' relies on a system property ("line.separator"), which is accessed during 'initializeSystemClass()'.
initializeSystemClass();
// reinitialise any registered classes
for (String className : reinitClasses) {
try {
final ClassActor classActor = ClassActor.fromJava(Class.forName(className));
classActor.callInitializer();
} catch (Exception e) {
FatalError.unexpected("Error re-initializing" + className, e);
}
}
break;
}
case RUNNING: {
// This is always the last scheme to be initialized, so now is the right time
// to start the profiler if requested.
final String profValue = profOption.getValue();
if (profValue != null) {
profiling = true;
SamplingProfiler.create(profValue);
}
break;
}
case TERMINATING: {
JniFunctions.printJniFunctionTimers();
if (profiling) {
SamplingProfiler.terminate();
}
break;
}
default: {
break;
}
}
}
/**
* Initializes basic features of the VM, including all of the VM schemes and the trap handling mechanism.
* It also parses some program arguments that were not parsed earlier.
*/
protected final void initializeBasicFeatures() {
MaxineVM vm = vm();
vm.phase = MaxineVM.Phase.STARTING;
// Now we can decode all the other VM arguments using the full language
if (VMOptions.parseStarting()) {
VMLog.checkLogOptions();
vmConfig().initializeSchemes(MaxineVM.Phase.STARTING);
if (Heap.ExcessiveGCFrequency != 0) {
new ExcessiveGCDaemon(Heap.ExcessiveGCFrequency).start();
}
if (Deoptimization.DeoptimizeALot != 0 && Deoptimization.UseDeopt) {
new DeoptimizeALot(Deoptimization.DeoptimizeALot).start();
}
// Install the signal handler for dumping threads when SIGHUP is received
Signal.handle(new Signal("QUIT"), new PrintThreads(false));
}
}
protected boolean parseMain() {
return VMOptions.parseMain(true);
}
/**
* The run() method is the entrypoint to this run scheme, after the VM has started up.
* This method initializes the basic features, parses the main program arguments, looks
* up the user-specified main class, and invokes its main method with the specified
* command-line arguments
*/
public void run() throws Throwable {
boolean error = true;
String classKindName = "premain";
try {
initializeBasicFeatures();
if (VMOptions.earlyVMExitRequested()) {
return;
}
loadVMExtensions();
error = false;
if (versionOption.isPresent()) {
sun.misc.Version.print();
return;
}
if (showVersionOption.isPresent()) {
sun.misc.Version.print();
}
if (!parseMain()) {
return;
}
error = true;
MaxineVM vm = vm();
vm.phase = Phase.RUNNING;
vmConfig().initializeSchemes(MaxineVM.Phase.RUNNING);
mainClassName = getMainClassName();
VMTI.handler().vmInitialized();
VMTI.handler().threadStart(VmThread.current());
// load -javaagent agents
loadJavaAgents();
classKindName = "main";
Class<?> mainClass = loadMainClass();
if (mainClass != null) {
lookupAndInvokeMain(mainClass);
error = false;
}
} catch (ClassNotFoundException classNotFoundException) {
error = true;
System.err.println("Could not load " + classKindName + "class: " + classNotFoundException);
} catch (NoClassDefFoundError noClassDefFoundError) {
error = true;
System.err.println("Error loading " + classKindName + "class: " + noClassDefFoundError);
} catch (NoSuchMethodException noSuchMethodException) {
error = true;
System.err.println("Could not find " + classKindName + "method: " + noSuchMethodException);
} catch (InvocationTargetException invocationTargetException) {
// This is an application exception: let VmThread.run() handle this.
// We only catch it here to set the VM exit code to a non-zero value.
error = true;
throw invocationTargetException.getCause();
} catch (IllegalAccessException illegalAccessException) {
error = true;
System.err.println("Illegal access trying to invoke " + classKindName + "method: " + illegalAccessException);
} catch (IOException ioException) {
error = true;
System.err.println("error reading jar file: " + ioException);
} catch (ProgramError programError) {
error = true;
Log.print("ProgramError: ");
Log.println(programError.getMessage());
} finally {
if (error) {
MaxineVM.setExitCode(-1);
}
}
}
private void lookupAndInvokeMain(Class<?> mainClass) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
final Method mainMethod = lookupMainOrAgentClass(mainClass, "main", String[].class);
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
mainMethod.setAccessible(true);
return null;
}
});
mainMethod.invoke(null, new Object[] {VMOptions.mainClassArguments()});
}
/**
* Try to locate a given method name and signature that is also public static void in given class.
* @param mainClass class to search
* @param methodName name of method
* @param params parameter types
* @return the method instance
* @throws NoSuchMethodException if the method cannot be found
*/
public static Method lookupMainOrAgentClass(Class<?> mainClass, String methodName, Class<?> ...params) throws NoSuchMethodException {
final Method mainMethod = mainClass.getDeclaredMethod(methodName, params);
final int modifiers = mainMethod.getModifiers();
if ((!Modifier.isPublic(modifiers)) || (!Modifier.isStatic(modifiers)) || (mainMethod.getReturnType() != void.class)) {
throw new NoSuchMethodException(methodName);
}
return mainMethod;
}
private Class<?> loadMainClass() throws IOException, ClassNotFoundException {
final ClassLoader appClassLoader = Launcher.getLauncher().getClassLoader();
return appClassLoader.loadClass(mainClassName);
}
/**
* Finds the main class name from the command line either explicitly or via the jar file.
*
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public static String getMainClassName() throws IOException, ClassNotFoundException {
if (mainClassName == null) {
final String jarFileName = VMOptions.jarFile();
if (jarFileName == null) {
// the main class was specified on the command line
mainClassName = VMOptions.mainClassName();
} else {
// the main class is in the jar file
final JarFile jarFile = new JarFile(jarFileName);
mainClassName = findClassAttributeInJarFile(jarFile, "Main-Class");
if (mainClassName == null) {
throw new ClassNotFoundException("Could not find main class in jarfile: " + jarFileName);
}
}
VMProperty.SUN_JAVA_COMMAND.updateImmutableValue(mainClassName);
System.setProperty(VMProperty.SUN_JAVA_COMMAND.property, mainClassName);
}
return mainClassName;
}
/**
* Searches the manifest in given jar file for given attribute.
* @param jarFile jar file to search
* @param classAttribute attribute to search for
* @return the value of the attribute of null if not found
* @throws IOException if error reading jar file
*/
public static String findClassAttributeInJarFile(JarFile jarFile, String classAttribute) throws IOException {
final Manifest manifest = jarFile.getManifest();
if (manifest == null) {
return null;
}
return manifest.getMainAttributes().getValue(classAttribute);
}
/**
* The method used to extend the class path of the app class loader with entries specified by an agent.
* Reflection is used for this as the method used to make the addition depends on the JDK
* version in use.
*/
private static final Method addURLToAppClassLoader;
static {
Method method;
try {
method = Launcher.class.getDeclaredMethod("addURL", URL.class);
} catch (NoSuchMethodException e) {
try {
method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
} catch (NoSuchMethodException e2) {
throw FatalError.unexpected("Cannot find method to extend class path of app class loader");
}
}
method.setAccessible(true);
addURLToAppClassLoader = method;
}
/**
* Callback class for handling the option specific details of loading agent/vm extension code from jar files.
*/
private static abstract class JarFileOptionHandler {
abstract String classNameAttribute();
abstract String classPathAttribute();
abstract void handle(String className, URL[] urls, String agentArgs)
throws IOException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException;
}
private static class JavaAgentJarFileOptionHandler extends JarFileOptionHandler {
@Override
String classNameAttribute() {
return "Premain-Class";
}
@Override
String classPathAttribute() {
return "Boot-Class-Path";
}
@Override
void handle(String className, URL[] urls, String agentArgs)
throws IOException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
for (URL url : urls) {
addURLToAppClassLoader.invoke(Launcher.getLauncher().getClassLoader(), url);
}
invokeMethod(className, urls[0], "premain", agentArgs);
}
private void invokeMethod(String className, URL url, String methodName, String args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
final ClassLoader appClassLoader = Launcher.getLauncher().getClassLoader();
final Class<?> agentClass = appClassLoader.loadClass(className);
Method method = null;
Object[] invokeArgs = null;
try {
method = lookupMainOrAgentClass(agentClass, methodName, new Class<?>[] {String.class, Instrumentation.class});
invokeArgs = new Object[2];
invokeArgs[1] = InstrumentationManager.createInstrumentation();
} catch (NoSuchMethodException ex) {
method = lookupMainOrAgentClass(agentClass, methodName, new Class<?>[] {String.class});
invokeArgs = new Object[1];
}
invokeArgs[0] = args;
InstrumentationManager.registerAgent(url);
method.invoke(null, invokeArgs);
}
}
private static final JavaAgentJarFileOptionHandler javaAgentJarFileOptionHandler = new JavaAgentJarFileOptionHandler();
private void loadJavaAgents()
throws IOException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
loadJarFile(javaagentOption, javaAgentJarFileOptionHandler);
}
private void loadJarFile(JarFileVMOption jarFileVMOption, JarFileOptionHandler handler)
throws IOException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
for (int i = 0; i < jarFileVMOption.count(); i++) {
final String jarFileVMOptionString = jarFileVMOption.getValue(i);
String jarPath = null;
String agentArgs = null; // spec is silent, Hotspot passes null
final int cIndex = jarFileVMOptionString.indexOf(':');
if (jarFileVMOptionString.length() > 1 && cIndex >= 0) {
final int eIndex = jarFileVMOptionString.indexOf('=', cIndex);
if (eIndex > 0) {
jarPath = jarFileVMOptionString.substring(cIndex + 1, eIndex);
agentArgs = jarFileVMOptionString.substring(eIndex + 1);
} else {
jarPath = jarFileVMOptionString.substring(cIndex + 1);
}
JarFile jarFile = null;
try {
jarFile = new JarFile(jarPath);
final String className = findClassAttributeInJarFile(jarFile, handler.classNameAttribute());
if (className == null) {
throw new IOException("could not find " + handler.classNameAttribute() + "in jarfile manifest: " + jarFile.getName());
}
ArrayList<String> classPathParts = null;
final String classPath = findClassAttributeInJarFile(jarFile, handler.classPathAttribute());
if (classPath != null) {
classPathParts = jarFileClassPaths(classPath);
}
URL[] urls = new URL[classPath == null ? 1 : 1 + classPathParts.size()];
String jarAbsPath = new File(jarFile.getName()).getAbsolutePath();
urls[0] = new URL("file://" + jarAbsPath);
for (int u = 1; u < urls.length; u++) {
String classPathPart = classPathParts.get(u - 1);
String absClassPathPart = classPathPart;
if (classPathPart.charAt(0) == '/') {
// absolute
} else {
// relative to jar
absClassPathPart = new File(jarAbsPath).getParent() + File.separator + classPathPart;
}
urls[u] = new URL("file://" + absClassPathPart);
}
handler.handle(className, urls, agentArgs);
} finally {
if (jarFile != null) {
jarFile.close();
}
}
} else {
throw new IOException("syntax error in " + jarFileVMOption.optionName + jarFileVMOptionString);
}
}
}
private static ArrayList<String> jarFileClassPaths(String classPath) {
ArrayList<String> result = new ArrayList<String>();
String trimmedClassPath = classPath.trim();
int sx = trimmedClassPath.indexOf(' ');
if (sx < 0) {
result.add(trimmedClassPath);
return result;
}
int length = trimmedClassPath.length();
int psx = 0;
while (true) {
result.add(trimmedClassPath.substring(psx, sx));
if (sx >= length) {
break;
}
while (sx < length && trimmedClassPath.charAt(sx) == ' ') {
sx++;
}
psx = sx;
while (sx < length && trimmedClassPath.charAt(sx) != ' ') {
sx++;
}
}
return result;
}
private static class VMExtensionJarFileOptionHandler extends JarFileOptionHandler {
@Override
String classNameAttribute() {
return "VMExtension-Class";
}
@Override
String classPathAttribute() {
return "VM-Class-Path";
}
@Override
void handle(String className, URL[] urls, String args)
throws IOException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
for (URL url : urls) {
VMClassLoader.VM_CLASS_LOADER.addURL(url);
}
invokeMethod(className, args);
}
private void invokeMethod(String className, String args)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
final Class<?> klass = VMClassLoader.VM_CLASS_LOADER.loadClass(className);
Method method = lookupMainOrAgentClass(klass, "onLoad", new Class<?>[] {String.class});
Object[] invokeArgs = new Object[1];
invokeArgs[0] = args;
method.invoke(null, invokeArgs);
}
}
private static final VMExtensionJarFileOptionHandler vmExtensionJarFileOptionHandler = new VMExtensionJarFileOptionHandler();
private void loadVMExtensions()
throws IOException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
loadJarFile(vmExtensionOption, vmExtensionJarFileOptionHandler);
}
}