Skip to content
Snippets Groups Projects
Commit 31681180 authored by Nadav Har'El's avatar Nadav Har'El
Browse files

Overhaul java.so command line

Java.so used to correctly support the "-jar" option, but did not fully
allow the other "mode" of running Java: specifying a class name which is
supposed to be searched in the class path. The biggest problem was that
it only know to find class files, but not a class inside a jar in the class
path - even if the classpath was correctly set.

Unfortunately, fixing this C code was impossible, as JNI's FindClass()
simply doesn't know to look in Jars.

So this patch overhauls java.so: Java.so now only runs a fixed class,
/java/RunJava.class. This class, in turn, is the one that parses the
command line arguments, sets the class path, finds the jar or class to
run, etc.. The code is now much easier to understand, and actually works
as expected :-) It also fixes the bug we had with SpecJVM2008's "compiler.*"
benchmarks, which forced us to tweak the class path manually.

The new code supports running a class from the classpath, and also the
"-classpath" option to set the class path. Like the "java" command line
tool in Linux, this one also recognizes wildcard classpaths. For example,
to run Jetty, whose code is in a dozen jars in /jetty, one can do:

        run.py -e "java.so -classpath /jetty/* org.eclipse.jetty.xml.XmlConfiguration jetty.xml"
parent 0b9ec899
No related branches found
No related tags found
No related merge requests found
......@@ -94,7 +94,7 @@
/testrunner.so: ./tests/testrunner.so
/java/Hello.class: ./tests/hello/Hello.class
/java.so: java/java.so
/java/RunJar.class: java/RunJar.class
/java/RunJava.class: java/RunJava.class
/java/bench.jar: tests/bench/bench.jar
/java/cloudius.jar: java/cloudius.jar
/java/cli.jar: java/cli.jar
......
......@@ -124,7 +124,7 @@ tests += tests/tst-ctxsw.so
tests/hello/Hello.class: javabase=tests/hello
java/RunJar.class: javabase=java
java/RunJava.class: javabase=java
all: loader.img loader.bin usr.img
......@@ -399,7 +399,7 @@ jni = java/jni/balloon.so java/jni/elf-loader.so java/jni/networking.so \
$(jni): INCLUDES += -I /usr/lib/jvm/java/include -I /usr/lib/jvm/java/include/linux/
bootfs.bin: scripts/mkbootfs.py bootfs.manifest $(tests) $(jni) \
tests/testrunner.so java/java.so java/RunJar.class
tests/testrunner.so java/java.so java/RunJava.class
$(call quiet, $(src)/scripts/mkbootfs.py -o $@ -d $@.d -m $(src)/bootfs.manifest \
-D jdkbase=$(jdkbase) -D gccbase=$(gccbase) -D \
glibcbase=$(glibcbase) -D miscbase=$(miscbase), MKBOOTFS $@)
......
import java.util.*;
import java.util.jar.*;
import java.net.*;
import java.lang.reflect.*;
public class RunJar {
public static void main(String[] args) {
try {
String jarfile = args[0];
args = java.util.Arrays.copyOfRange(args, 1, args.length);
JarFile jar = new JarFile(jarfile);
Manifest mf = jar.getManifest();
String mainClass = mf.getMainAttributes().getValue("Main-Class");
URLClassLoader ucl = new URLClassLoader(
new URL[] { new URL("file:///" + jarfile) },
new Object().getClass().getClassLoader());
Thread.currentThread().setContextClassLoader(ucl);
Class klass = ucl.loadClass(mainClass);
Method main = klass.getMethod("main", String[].class);
try {
main.invoke(null, new Object[] { args });
} catch (InvocationTargetException ex) {
throw ex.getCause();
}
} catch (Throwable ex) {
ex.printStackTrace();
}
}
}
\ No newline at end of file
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
public class RunJava {
public static void main(String[] args) {
try {
parseArgs(args);
} catch (Throwable ex) {
System.err.println("Uncaught Java exception:");
ex.printStackTrace();
}
}
static void parseArgs(String[] args) throws Throwable {
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-jar")) {
if (i+1 >= args.length) {
System.err.println("RunJava: Missing jar name after '-jar'.");
return;
}
runJar(args[i+1], java.util.Arrays.copyOfRange(args, i+2, args.length));
return;
} else if (args[i].equals("-classpath") || args[i].equals("-cp")) {
if (i+1 >= args.length) {
System.err.println("RunJava: Missing parameter after '"+args[i]+"'");
return;
}
setClassPath(expandClassPath(args[i+1]));
i++;
} else if (args[i].startsWith("-D")) {
int eq = args[i].indexOf('=');
if (eq<0) {
System.err.println("RunJava: Missing '=' in parameter '"+args[i]+"'");
return;
}
String key = args[i].substring(2, eq);
String value = args[i].substring(eq+1, args[i].length());
System.setProperty(key, value);
} else if (!args[i].startsWith("-")) {
runClass(args[i], java.util.Arrays.copyOfRange(args, i+1, args.length));
return;
} else {
System.err.println("RunJava: Unknown parameter '"+args[i]+"'");
return;
}
}
System.err.println("RunJava: No jar or class specified to run.");
}
static void runJar(String jarname, String[] args) throws Throwable {
File jarfile = new File(jarname);
JarFile jar = new JarFile(jarfile);
Manifest mf = jar.getManifest();
jar.close();
String mainClass = mf.getMainAttributes().getValue("Main-Class");
setClassPath(jarname);
runMain(loadClass(mainClass), args);
}
static void runClass(String mainClass, String[] args) throws Throwable {
runMain(loadClass(mainClass), args);
}
static void runMain(Class<?> klass, String[] args) throws Throwable {
Method main = klass.getMethod("main", String[].class);
try {
main.invoke(null, new Object[] { args });
} catch (InvocationTargetException ex) {
throw ex.getCause();
}
}
static void setClassPath(Iterable<String> jars) throws MalformedURLException {
ArrayList<URL> urls = new ArrayList<URL>();
for (String jar : jars) {
urls.add(new URL("file:///" + jar));
}
URL[] urlArray = urls.toArray(new URL[urls.size()]);
URLClassLoader ucl = new URLClassLoader(urlArray,
ClassLoader.getSystemClassLoader());
Thread.currentThread().setContextClassLoader(ucl);
// Also update the java.class.path property
StringBuilder sb = new StringBuilder();
boolean first = true;
for (String jar : jars) {
if (!first) {
sb.append(":");
}
first = false;
sb.append(jar);
}
System.setProperty("java.class.path", sb.toString());
}
static void setClassPath(String jar) throws MalformedURLException {
setClassPath(java.util.Collections.singleton(jar));
}
static Class<?> loadClass(String name) throws ClassNotFoundException {
return Thread.currentThread().getContextClassLoader().loadClass(name);
}
// Expand classpath, as given in the "-classpath" option, to a list of
// jars or directories. As in the traditional "java" command-line
// launcher, components of the class path are separated by ":", and
// we also support the traditional (but awkward) Java wildcard syntax,
// where "dir/*" adds to the classpath all jar files in the given
// directory.
static Iterable<String> expandClassPath(String classpath) {
ArrayList<String> ret = new ArrayList<String>();
for (String component : classpath.split(":")) {
if (component.endsWith("/*")) {
File dir = new File(
component.substring(0, component.length()-2));
if (dir.isDirectory()) {
for (File file : dir.listFiles()) {
String filename = file.getPath();
if (filename.endsWith(".jar")) {
ret.add(filename);
}
}
continue; // handled this path component
}
}
ret.add(component);
}
return ret;
}
}
......@@ -3,9 +3,20 @@
#include <string.h>
#include "debug.hh"
// java.so is similar to the standard "java" command line launcher in Linux.
//
// This program does very little - basically it starts the JVM and asks it
// to run a fixed class, /java/RunJava.class, which parses the command line
// parameters, sets up the class path, and runs the jar or class specified
// in these parameters. Unfortunately, we cannot do this here in C++ code
// because FindClass() has a known bug where it cannot find a class inside a
// .jar, just a class in a directory.
extern elf::program* prog;
#define JVM_PATH "/usr/lib/jvm/jre/lib/amd64/server/libjvm.so"
#define RUNJAVA_DIR "/java"
#define RUNJAVA "RunJava"
JavaVMOption mkoption(const char* s)
{
......@@ -14,7 +25,8 @@ JavaVMOption mkoption(const char* s)
return opt;
}
extern "C" int main(int ac, char **av)
extern "C"
int main(int argc, char **argv)
{
prog->add_object(JVM_PATH);
......@@ -23,51 +35,43 @@ extern "C" int main(int ac, char **av)
JavaVMInitArgs vm_args = {};
vm_args.version = JNI_VERSION_1_6;
JNI_GetDefaultJavaVMInitArgs(&vm_args);
std::vector<JavaVMOption> options;
options.push_back(mkoption("-Djava.class.path=/java"));
while (ac > 0 && av[0][0] == '-' && av[0] != std::string("-jar")) {
options.push_back(mkoption(av[0]));
++av, --ac;
}
if (ac < 1) {
debug("java.so: No class or jar given in command line. Aborting.\n");
abort();
}
std::string mainclassname;
if (std::string(av[0]) == "-jar") {
mainclassname = "RunJar";
} else {
mainclassname = av[0];
}
++av, --ac;
options.push_back(mkoption("-Djava.class.path=" RUNJAVA_DIR));
vm_args.nOptions = options.size();
vm_args.options = options.data();
auto JNI_CreateJavaVM
= prog->lookup_function<jint (JavaVM**, JNIEnv**, void*)>("JNI_CreateJavaVM");
if (!JNI_CreateJavaVM) {
debug("java.so: failed looking up JNI_CreateJavaVM()\n");
abort();
}
JavaVM* jvm = nullptr;
JNIEnv *env;
auto ret = JNI_CreateJavaVM(&jvm, &env, &vm_args);
assert(ret == 0);
auto mainclass = env->FindClass(mainclassname.c_str());
if (JNI_CreateJavaVM(&jvm, &env, &vm_args) != 0) {
debug("java.so: Can't create VM.\n");
abort();
}
auto mainclass = env->FindClass(RUNJAVA);
if (!mainclass) {
debug("java.so: Can't find class %s.\n", mainclassname);
debug("java.so: Can't find class %s in %s.\n", RUNJAVA, RUNJAVA_DIR);
abort();
}
auto mainmethod = env->GetStaticMethodID(mainclass, "main", "([Ljava/lang/String;)V");
auto stringclass = env->FindClass("java/lang/String");
std::vector<std::string> newargs;
for (auto i = 0; i < ac; ++i) {
newargs.push_back(av[i]);
if (!mainmethod) {
debug("java.so: Can't find main() in class %s.\n", RUNJAVA);
abort();
}
auto args = env->NewObjectArray(newargs.size(), stringclass, nullptr);
for (auto i = 0u; i < newargs.size(); ++i) {
env->SetObjectArrayElement(args, i, env->NewStringUTF(newargs[i].c_str()));
auto stringclass = env->FindClass("java/lang/String");
auto args = env->NewObjectArray(argc, stringclass, nullptr);
for (int i = 0; i < argc; ++i) {
env->SetObjectArrayElement(args, i, env->NewStringUTF(argv[i]));
}
env->CallStaticVoidMethod(mainclass, mainmethod, args);
return 0;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment