/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Command-line invocation of the Dalvik VM. */ #include "jni.h" #include <stdlib.h> #include <stdio.h> #include <string.h> #include <signal.h> #include <assert.h> /* * We want failed write() calls to just return with an error. */ static void blockSigpipe() { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGPIPE); if (sigprocmask(SIG_BLOCK, &mask, NULL) != 0) fprintf(stderr, "WARNING: SIGPIPE not blocked\n"); } /* * Create a String[] and populate it with the contents of argv. */ static jobjectArray createStringArray(JNIEnv* env, char* const argv[], int argc) { jclass stringClass = NULL; jobjectArray strArray = NULL; jobjectArray result = NULL; int i; stringClass = (*env)->FindClass(env, "java/lang/String"); if ((*env)->ExceptionCheck(env)) { fprintf(stderr, "Got exception while finding class String\n"); goto bail; } assert(stringClass != NULL); strArray = (*env)->NewObjectArray(env, argc, stringClass, NULL); if ((*env)->ExceptionCheck(env)) { fprintf(stderr, "Got exception while creating String array\n"); goto bail; } assert(strArray != NULL); for (i = 0; i < argc; i++) { jstring argStr; argStr = (*env)->NewStringUTF(env, argv[i]); if ((*env)->ExceptionCheck(env)) { fprintf(stderr, "Got exception while allocating Strings\n"); goto bail; } assert(argStr != NULL); (*env)->SetObjectArrayElement(env, strArray, i, argStr); (*env)->DeleteLocalRef(env, argStr); } /* return the array, and ensure we don't delete the local ref to it */ result = strArray; strArray = NULL; bail: (*env)->DeleteLocalRef(env, stringClass); (*env)->DeleteLocalRef(env, strArray); return result; } /* * Determine whether or not the specified method is public. * * Returns JNI_TRUE on success, JNI_FALSE on failure. */ static int methodIsPublic(JNIEnv* env, jclass clazz, jmethodID methodId) { static const int PUBLIC = 0x0001; // java.lang.reflect.Modifiers.PUBLIC jobject refMethod = NULL; jclass methodClass = NULL; jmethodID getModifiersId; int modifiers; int result = JNI_FALSE; refMethod = (*env)->ToReflectedMethod(env, clazz, methodId, JNI_FALSE); if (refMethod == NULL) { fprintf(stderr, "Dalvik VM unable to get reflected method\n"); goto bail; } /* * We now have a Method instance. We need to call * its getModifiers() method. */ methodClass = (*env)->FindClass(env, "java/lang/reflect/Method"); if (methodClass == NULL) { fprintf(stderr, "Dalvik VM unable to find class Method\n"); goto bail; } getModifiersId = (*env)->GetMethodID(env, methodClass, "getModifiers", "()I"); if (methodClass == NULL) { fprintf(stderr, "Dalvik VM unable to find reflect.Method.getModifiers\n"); goto bail; } modifiers = (*env)->CallIntMethod(env, refMethod, getModifiersId); if ((modifiers & PUBLIC) == 0) { fprintf(stderr, "Dalvik VM: main() is not public\n"); goto bail; } result = JNI_TRUE; bail: (*env)->DeleteLocalRef(env, refMethod); (*env)->DeleteLocalRef(env, methodClass); return result; } /* * Parse arguments. Most of it just gets passed through to the VM. The * JNI spec defines a handful of standard arguments. */ int main(int argc, char* const argv[]) { JavaVM* vm = NULL; JNIEnv* env = NULL; JavaVMInitArgs initArgs; JavaVMOption* options = NULL; char* slashClass = NULL; int optionCount, curOpt, i, argIdx; int needExtra = JNI_FALSE; int result = 1; setvbuf(stdout, NULL, _IONBF, 0); /* ignore argv[0] */ argv++; argc--; /* * If we're adding any additional stuff, e.g. function hook specifiers, * add them to the count here. * * We're over-allocating, because this includes the options to the VM * plus the options to the program. */ optionCount = argc; options = (JavaVMOption*) malloc(sizeof(JavaVMOption) * optionCount); memset(options, 0, sizeof(JavaVMOption) * optionCount); /* * Copy options over. Everything up to the name of the class starts * with a '-' (the function hook stuff is strictly internal). * * [Do we need to catch & handle "-jar" here?] */ for (curOpt = argIdx = 0; argIdx < argc; argIdx++) { if (argv[argIdx][0] != '-' && !needExtra) break; options[curOpt++].optionString = strdup(argv[argIdx]); /* some options require an additional arg */ needExtra = JNI_FALSE; if (strcmp(argv[argIdx], "-classpath") == 0 || strcmp(argv[argIdx], "-cp") == 0) /* others? */ { needExtra = JNI_TRUE; } } if (needExtra) { fprintf(stderr, "Dalvik VM requires value after last option flag\n"); goto bail; } /* insert additional internal options here */ assert(curOpt <= optionCount); initArgs.version = JNI_VERSION_1_4; initArgs.options = options; initArgs.nOptions = curOpt; initArgs.ignoreUnrecognized = JNI_FALSE; //printf("nOptions = %d\n", initArgs.nOptions); blockSigpipe(); /* * Start VM. The current thread becomes the main thread of the VM. */ if (JNI_CreateJavaVM(&vm, &env, &initArgs) < 0) { fprintf(stderr, "Dalvik VM init failed (check log file)\n"); goto bail; } /* * Make sure they provided a class name. We do this after VM init * so that things like "-Xrunjdwp:help" have the opportunity to emit * a usage statement. */ if (argIdx == argc) { fprintf(stderr, "Dalvik VM requires a class name\n"); goto bail; } /* * We want to call main() with a String array with our arguments in it. * Create an array and populate it. Note argv[0] is not included. */ jobjectArray strArray; strArray = createStringArray(env, &argv[argIdx+1], argc-argIdx-1); if (strArray == NULL) goto bail; /* * Find [class].main(String[]). */ jclass startClass; jmethodID startMeth; char* cp; /* convert "com.android.Blah" to "com/android/Blah" */ slashClass = strdup(argv[argIdx]); for (cp = slashClass; *cp != '\0'; cp++) if (*cp == '.') *cp = '/'; startClass = (*env)->FindClass(env, slashClass); if (startClass == NULL) { fprintf(stderr, "Dalvik VM unable to locate class '%s'\n", slashClass); goto bail; } startMeth = (*env)->GetStaticMethodID(env, startClass, "main", "([Ljava/lang/String;)V"); if (startMeth == NULL) { fprintf(stderr, "Dalvik VM unable to find static main(String[]) in '%s'\n", slashClass); goto bail; } /* * Make sure the method is public. JNI doesn't prevent us from calling * a private method, so we have to check it explicitly. */ if (!methodIsPublic(env, startClass, startMeth)) goto bail; /* * Invoke main(). */ (*env)->CallStaticVoidMethod(env, startClass, startMeth, strArray); if (!(*env)->ExceptionCheck(env)) result = 0; bail: /*printf("Shutting down Dalvik VM\n");*/ if (vm != NULL) { /* * This allows join() and isAlive() on the main thread to work * correctly, and also provides uncaught exception handling. */ if ((*vm)->DetachCurrentThread(vm) != JNI_OK) { fprintf(stderr, "Warning: unable to detach main thread\n"); result = 1; } if ((*vm)->DestroyJavaVM(vm) != 0) fprintf(stderr, "Warning: Dalvik VM did not shut down cleanly\n"); /*printf("\nDalvik VM has exited\n");*/ } for (i = 0; i < optionCount; i++) free((char*) options[i].optionString); free(options); free(slashClass); /*printf("--- VM is down, process exiting\n");*/ return result; }