001/*
002 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003 *
004 *  This file is licensed to You under the Eclipse Public License (EPL);
005 *  You may not use this file except in compliance with the License. You
006 *  may obtain a copy of the License at
007 *
008 *      http://www.opensource.org/licenses/eclipse-1.0.php
009 *
010 *  See the COPYRIGHT.txt file distributed with this work for information
011 *  regarding copyright ownership.
012 */
013package org.jikesrvm.scheduler;
014
015import static org.jikesrvm.runtime.ExitStatus.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG;
016
017import java.lang.instrument.Instrumentation;
018import java.lang.reflect.InvocationTargetException;
019import java.lang.reflect.Method;
020import java.util.jar.JarFile;
021import java.util.jar.Manifest;
022
023import org.jikesrvm.VM;
024import org.jikesrvm.classloader.Atom;
025import org.jikesrvm.classloader.RVMClass;
026import org.jikesrvm.classloader.RVMClassLoader;
027import org.jikesrvm.classloader.RVMMethod;
028import org.jikesrvm.classloader.TypeReference;
029import org.jikesrvm.runtime.Callbacks;
030import org.jikesrvm.runtime.CommandLineArgs;
031import org.jikesrvm.runtime.Reflection;
032import org.vmmagic.pragma.Entrypoint;
033
034/**
035 * Thread in which user's "main" program runs.
036 */
037public final class MainThread extends Thread {
038  private final String[] args;
039  private final String[] agents;
040  private RVMMethod mainMethod;
041  protected boolean launched = false;
042
043  private static final boolean dbg = false;
044
045  /**
046   * Create "main" thread.
047   * @param args {@code args[0]}: name of class containing "main" method;
048   *  {@code args[1..N]}: parameters to pass to "main" method
049   */
050  public MainThread(String[] args) {
051    super("MainThread");
052    setDaemon(false); // NB otherwise we inherit the boot threads daemon status
053    this.agents = CommandLineArgs.getJavaAgentArgs();
054    this.args = args;
055    if (dbg) {
056      VM.sysWriteln("MainThread(args.length == ", args.length, "): constructor done");
057    }
058  }
059
060  private void runAgents(ClassLoader cl) {
061    if (agents.length > 0) {
062      Instrumentation instrumenter = null;
063      if (VM.BuildForGnuClasspath) {
064        try {
065          instrumenter = (Instrumentation)Class.forName("gnu.java.lang.JikesRVMSupport")
066            .getMethod("createInstrumentation").invoke(null);
067          java.lang.JikesRVMSupport.initializeInstrumentation(instrumenter);
068        } catch (Exception e) {
069        }
070      }
071      for (String agent : agents) {
072        /*
073         * Parse agent string according to the form
074         * given in the java.lang.instrumentation package
075         * documentation:
076         * jarpath[=options]
077         *
078         * (The -javaagent: part of the agent options has
079         *  already been stripped)
080         */
081        int equalsIndex = agent.indexOf('=');
082        String agentJar;
083        String agentOptions;
084        if (equalsIndex != -1) {
085          agentJar = agent.substring(0, equalsIndex);
086          agentOptions = agent.substring(equalsIndex + 1);
087        } else {
088          agentJar = agent;
089          agentOptions = "";
090        }
091        runAgent(instrumenter, cl, agentJar, agentOptions);
092      }
093    }
094  }
095
096  private static void runAgent(Instrumentation instrumenter, ClassLoader cl, String agentJar, String agentOptions) {
097    Manifest mf = null;
098    try {
099      JarFile jf = new JarFile(agentJar);
100      mf = jf.getManifest();
101    } catch (Exception e) {
102      VM.sysWriteln("vm: IO Exception opening JAR file ", agentJar, ": ", e.getMessage());
103      VM.sysExit(EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
104    }
105    if (mf == null) {
106      VM.sysWriteln("The jar file is missing the manifest: ", agentJar);
107      VM.sysExit(EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
108    }
109    String agentClassName = mf.getMainAttributes().getValue("Premain-Class");
110    if (agentClassName == null) {
111      VM.sysWriteln("The jar file is missing the Premain-Class manifest entry for the agent class: ", agentJar);
112      VM.sysExit(EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
113    }
114    //TODO: By this stage all agent jars and classes they reference via their manifest
115    try {
116      Class<?> agentClass = cl.loadClass(agentClassName);
117      Method agentPremainMethod = agentClass.getMethod("premain", new Class<?>[]{String.class, Instrumentation.class});
118      agentPremainMethod.invoke(null, new Object[]{agentOptions, instrumenter});
119    } catch (InvocationTargetException e) {
120      // According to the spec, exceptions from premain() can be ignored
121    } catch (Throwable e) {
122      VM.sysWriteln("Failed to run the agent's premain: " + e.getMessage());
123      e.printStackTrace();
124      System.exit(0);
125    }
126  }
127
128  RVMMethod getMainMethod() {
129    return mainMethod;
130  }
131
132  /**
133   * Run "main" thread.
134   * <p>
135   * This code could be made a little shorter by relying on Reflection
136   * to do the classloading and compilation.  We intentionally do it here
137   * to give us a chance to provide error messages that are specific to
138   * not being able to find the main class the user wants to run.
139   * This may be a little silly, since it results in code duplication
140   * just to provide debug messages in a place where very little is actually
141   * likely to go wrong, but there you have it....
142   */
143  @Override
144  @Entrypoint
145  public void run() {
146    launched = true;
147
148    if (dbg) VM.sysWriteln("MainThread.run() starting ");
149
150    // Set up application class loader
151    ClassLoader cl = RVMClassLoader.getApplicationClassLoader();
152    setContextClassLoader(cl);
153
154    runAgents(cl);
155
156    if (dbg) VM.sysWrite("[MainThread.run() loading class to run... ");
157    // find method to run
158    // load class specified by args[0]
159    RVMClass cls = null;
160    try {
161      Atom mainAtom = Atom.findOrCreateUnicodeAtom(args[0]);
162      TypeReference mainClass = TypeReference.findOrCreate(cl, mainAtom.descriptorFromClassName());
163      cls = mainClass.resolve().asClass();
164      cls.resolve();
165      cls.instantiate();
166      cls.initialize();
167    } catch (NoClassDefFoundError e) {
168      if (dbg) VM.sysWrite("failed.]");
169      // no such class
170      VM.sysWrite(e + "\n");
171      return;
172    }
173    if (dbg) VM.sysWriteln("loaded.]");
174
175    // find "main" method
176    //
177    mainMethod = cls.findMainMethod();
178    if (mainMethod == null) {
179      // no such method
180      VM.sysWrite(cls + " doesn't have a \"public static void main(String[])\" method to execute\n");
181      return;
182    }
183
184    if (dbg) VM.sysWrite("[MainThread.run() making arg list... ");
185    // create "main" argument list
186    //
187    String[] mainArgs = new String[args.length - 1];
188    for (int i = 0, n = mainArgs.length; i < n; ++i) {
189      mainArgs[i] = args[i + 1];
190    }
191    if (dbg) VM.sysWriteln("made.]");
192
193    if (dbg) VM.sysWrite("[MainThread.run() compiling main(String[])... ");
194    mainMethod.compile();
195    if (dbg) VM.sysWriteln("compiled.]");
196
197    // Notify other clients that the startup is complete.
198    //
199    Callbacks.notifyStartup();
200
201    if (dbg) VM.sysWriteln("[MainThread.run() invoking \"main\" method... ");
202    // invoke "main" method with argument list
203    Reflection.invoke(mainMethod, null, null, new Object[]{mainArgs}, true);
204    if (dbg) VM.sysWriteln("  MainThread.run(): \"main\" method completed.]");
205  }
206}