/* * Copyright (c) 2003, 2008, 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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. */ /* * Copyright 2003 Wily Technology, Inc. */ #include #include #include "jni.h" #include "Utilities.h" #include "JPLISAssert.h" #include "JPLISAgent.h" #include "JavaExceptions.h" #include "EncodingSupport.h" #include "FileSystemSupport.h" #include "JarFacade.h" #include "PathCharsValidator.h" /** * This module contains the direct interface points with the JVMTI. * The OnLoad handler is here, along with the various event handlers. */ static int appendClassPath(JPLISAgent* agent, const char* jarfile); static void appendBootClassPath(JPLISAgent* agent, const char* jarfile, const char* pathList); /* * Parse -javaagent tail, of the form name[=options], into name * and options. Returned values are heap allocated and options maybe * NULL. Returns 0 if parse succeeds, -1 if allocation fails. */ static int parseArgumentTail(char* tail, char** name, char** options) { int len; char* pos; pos = strchr(tail, '='); len = (pos == NULL) ? (int)strlen(tail) : (int)(pos - tail); *name = (char*)malloc(len+1); if (*name == NULL) { return -1; } memcpy(*name, tail, len); (*name)[len] = '\0'; if (pos == NULL) { *options = NULL; } else { char * str = (char*)malloc( (int)strlen(pos + 1) + 1 ); if (str == NULL) { free(*name); return -1; } strcpy(str, pos +1); *options = str; } return 0; } /* * Get the value of an attribute in an attribute list. Returns NULL * if attribute not found. */ jboolean getBooleanAttribute(const jarAttribute* attributes, const char* name) { char* attributeValue = getAttribute(attributes, name); return attributeValue != NULL && strcasecmp(attributeValue, "true") == 0; } /* * Parse any capability settings in the JAR manifest and * convert them to JVM TI capabilities. */ void convertCapabilityAtrributes(const jarAttribute* attributes, JPLISAgent* agent) { /* set redefineClasses capability */ if (getBooleanAttribute(attributes, "Can-Redefine-Classes")) { addRedefineClassesCapability(agent); } /* create an environment which has the retransformClasses capability */ if (getBooleanAttribute(attributes, "Can-Retransform-Classes")) { retransformableEnvironment(agent); } /* set setNativeMethodPrefix capability */ if (getBooleanAttribute(attributes, "Can-Set-Native-Method-Prefix")) { addNativeMethodPrefixCapability(agent); } /* for retransformClasses testing, set capability to use original method order */ if (getBooleanAttribute(attributes, "Can-Maintain-Original-Method-Order")) { addOriginalMethodOrderCapability(agent); } } /* * This will be called once for every -javaagent on the command line. * Each call to Agent_OnLoad will create its own agent and agent data. * * The argument tail string provided to Agent_OnLoad will be of form * [=]. The tail string is split into the jarfile and * options components. The jarfile manifest is parsed and the value of the * Premain-Class attribute will become the agent's premain class. The jar * file is then added to the system class path, and if the Boot-Class-Path * attribute is present then all relative URLs in the value are processed * to create boot class path segments to append to the boot class path. */ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) { JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE; jint result = JNI_OK; JPLISAgent * agent = NULL; initerror = createNewJPLISAgent(vm, &agent); if ( initerror == JPLIS_INIT_ERROR_NONE ) { int oldLen, newLen; char * jarfile; char * options; jarAttribute* attributes; char * premainClass; char * bootClassPath; /* * Parse [=options] into jarfile and options */ if (parseArgumentTail(tail, &jarfile, &options) != 0) { fprintf(stderr, "-javaagent: memory allocation failure.\n"); return JNI_ERR; } /* * Agent_OnLoad is specified to provide the agent options * argument tail in modified UTF8. However for 1.5.0 this is * actually in the platform encoding - see 5049313. * * Open zip/jar file and parse archive. If can't be opened or * not a zip file return error. Also if Premain-Class attribute * isn't present we return an error. */ attributes = readAttributes(jarfile); if (attributes == NULL) { fprintf(stderr, "Error opening zip file or JAR manifest missing : %s\n", jarfile); free(jarfile); if (options != NULL) free(options); return JNI_ERR; } premainClass = getAttribute(attributes, "Premain-Class"); if (premainClass == NULL) { fprintf(stderr, "Failed to find Premain-Class manifest attribute in %s\n", jarfile); free(jarfile); if (options != NULL) free(options); freeAttributes(attributes); return JNI_ERR; } /* * Add to the jarfile */ appendClassPath(agent, jarfile); /* * The value of the Premain-Class attribute becomes the agent * class name. The manifest is in UTF8 so need to convert to * modified UTF8 (see JNI spec). */ oldLen = (int)strlen(premainClass); newLen = modifiedUtf8LengthOfUtf8(premainClass, oldLen); if (newLen == oldLen) { premainClass = strdup(premainClass); } else { char* str = (char*)malloc( newLen+1 ); if (str != NULL) { convertUtf8ToModifiedUtf8(premainClass, oldLen, str, newLen); } premainClass = str; } if (premainClass == NULL) { fprintf(stderr, "-javaagent: memory allocation failed\n"); free(jarfile); if (options != NULL) free(options); freeAttributes(attributes); return JNI_ERR; } /* * If the Boot-Class-Path attribute is specified then we process * each relative URL and add it to the bootclasspath. */ bootClassPath = getAttribute(attributes, "Boot-Class-Path"); if (bootClassPath != NULL) { appendBootClassPath(agent, jarfile, bootClassPath); } /* * Convert JAR attributes into agent capabilities */ convertCapabilityAtrributes(attributes, agent); /* * Track (record) the agent class name and options data */ initerror = recordCommandLineData(agent, premainClass, options); /* * Clean-up */ free(jarfile); if (options != NULL) free(options); freeAttributes(attributes); free(premainClass); } switch (initerror) { case JPLIS_INIT_ERROR_NONE: result = JNI_OK; break; case JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT: result = JNI_ERR; fprintf(stderr, "java.lang.instrument/-javaagent: cannot create native agent.\n"); break; case JPLIS_INIT_ERROR_FAILURE: result = JNI_ERR; fprintf(stderr, "java.lang.instrument/-javaagent: initialization of native agent failed.\n"); break; case JPLIS_INIT_ERROR_ALLOCATION_FAILURE: result = JNI_ERR; fprintf(stderr, "java.lang.instrument/-javaagent: allocation failure.\n"); break; case JPLIS_INIT_ERROR_AGENT_CLASS_NOT_SPECIFIED: result = JNI_ERR; fprintf(stderr, "-javaagent: agent class not specified.\n"); break; default: result = JNI_ERR; fprintf(stderr, "java.lang.instrument/-javaagent: unknown error\n"); break; } return result; } /* * Agent_OnAttach returns a jint. 0/JNI_OK indicates success and non-0 * indicates an error. To allow the attach mechanism throw an * AgentInitializationException with a reasonable exception message we define * a few specific errors here. */ #define AGENT_ERROR_BADJAR ((jint)100) /* Agent JAR not found or no Agent-Class attribute */ #define AGENT_ERROR_NOTONCP ((jint)101) /* Unable to add JAR file to system class path */ #define AGENT_ERROR_STARTFAIL ((jint)102) /* No agentmain method or agentmain failed */ /* * This will be called once each time a tool attaches to the VM and loads * the JPLIS library. */ JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char *args, void * reserved) { JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE; jint result = JNI_OK; JPLISAgent * agent = NULL; JNIEnv * jni_env = NULL; /* * Need JNIEnv - guaranteed to be called from thread that is already * attached to VM */ result = (*vm)->GetEnv(vm, (void**)&jni_env, JNI_VERSION_1_2); jplis_assert(result==JNI_OK); initerror = createNewJPLISAgent(vm, &agent); if ( initerror == JPLIS_INIT_ERROR_NONE ) { int oldLen, newLen; char * jarfile; char * options; jarAttribute* attributes; char * agentClass; char * bootClassPath; jboolean success; /* * Parse [=options] into jarfile and options */ if (parseArgumentTail(args, &jarfile, &options) != 0) { return JNI_ENOMEM; } /* * Open the JAR file and parse the manifest */ attributes = readAttributes( jarfile ); if (attributes == NULL) { fprintf(stderr, "Error opening zip file or JAR manifest missing: %s\n", jarfile); free(jarfile); if (options != NULL) free(options); return AGENT_ERROR_BADJAR; } agentClass = getAttribute(attributes, "Agent-Class"); if (agentClass == NULL) { fprintf(stderr, "Failed to find Agent-Class manifest attribute from %s\n", jarfile); free(jarfile); if (options != NULL) free(options); freeAttributes(attributes); return AGENT_ERROR_BADJAR; } /* * Add the jarfile to the system class path */ if (appendClassPath(agent, jarfile)) { fprintf(stderr, "Unable to add %s to system class path " "- not supported by system class loader or configuration error!\n", jarfile); free(jarfile); if (options != NULL) free(options); freeAttributes(attributes); return AGENT_ERROR_NOTONCP; } /* * The value of the Agent-Class attribute becomes the agent * class name. The manifest is in UTF8 so need to convert to * modified UTF8 (see JNI spec). */ oldLen = (int)strlen(agentClass); newLen = modifiedUtf8LengthOfUtf8(agentClass, oldLen); if (newLen == oldLen) { agentClass = strdup(agentClass); } else { char* str = (char*)malloc( newLen+1 ); if (str != NULL) { convertUtf8ToModifiedUtf8(agentClass, oldLen, str, newLen); } agentClass = str; } if (agentClass == NULL) { free(jarfile); if (options != NULL) free(options); freeAttributes(attributes); return JNI_ENOMEM; } /* * If the Boot-Class-Path attribute is specified then we process * each URL - in the live phase only JAR files will be added. */ bootClassPath = getAttribute(attributes, "Boot-Class-Path"); if (bootClassPath != NULL) { appendBootClassPath(agent, jarfile, bootClassPath); } /* * Convert JAR attributes into agent capabilities */ convertCapabilityAtrributes(attributes, agent); /* * Create the java.lang.instrument.Instrumentation instance */ success = createInstrumentationImpl(jni_env, agent); jplis_assert(success); /* * Turn on the ClassFileLoadHook. */ if (success) { success = setLivePhaseEventHandlers(agent); jplis_assert(success); } /* * Start the agent */ if (success) { success = startJavaAgent(agent, jni_env, agentClass, options, agent->mAgentmainCaller); } if (!success) { fprintf(stderr, "Agent failed to start!\n"); result = AGENT_ERROR_STARTFAIL; } /* * Clean-up */ free(jarfile); if (options != NULL) free(options); free(agentClass); freeAttributes(attributes); } return result; } JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) { } /* * JVMTI callback support * * We have two "stages" of callback support. * At OnLoad time, we install a VMInit handler. * When the VMInit handler runs, we remove the VMInit handler and install a * ClassFileLoadHook handler. */ void JNICALL eventHandlerVMInit( jvmtiEnv * jvmtienv, JNIEnv * jnienv, jthread thread) { JPLISEnvironment * environment = NULL; jboolean success = JNI_FALSE; environment = getJPLISEnvironment(jvmtienv); /* process the premain calls on the all the JPL agents */ if ( environment != NULL ) { jthrowable outstandingException = preserveThrowable(jnienv); success = processJavaStart( environment->mAgent, jnienv); restoreThrowable(jnienv, outstandingException); } /* if we fail to start cleanly, bring down the JVM */ if ( !success ) { abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART); } } void JNICALL eventHandlerClassFileLoadHook( jvmtiEnv * jvmtienv, JNIEnv * jnienv, jclass class_being_redefined, jobject loader, const char* name, jobject protectionDomain, jint class_data_len, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) { JPLISEnvironment * environment = NULL; environment = getJPLISEnvironment(jvmtienv); /* if something is internally inconsistent (no agent), just silently return without touching the buffer */ if ( environment != NULL ) { jthrowable outstandingException = preserveThrowable(jnienv); transformClassFile( environment->mAgent, jnienv, loader, name, class_being_redefined, protectionDomain, class_data_len, class_data, new_class_data_len, new_class_data, environment->mIsRetransformer); restoreThrowable(jnienv, outstandingException); } } /* * URLs in Boot-Class-Path attributes are separated by one or more spaces. * This function splits the attribute value into a list of path segments. * The attribute value is in UTF8 but cannot contain NUL. Also non US-ASCII * characters must be escaped (URI syntax) so safe to iterate through the * value as a C string. */ static void splitPathList(const char* str, int* pathCount, char*** paths) { int count = 0; char** segments = NULL; char* c = (char*) str; while (*c != '\0') { while (*c == ' ') c++; /* skip leading spaces */ if (*c == '\0') { break; } if (segments == NULL) { segments = (char**)malloc( sizeof(char**) ); } else { segments = (char**)realloc( segments, (count+1)*sizeof(char**) ); } jplis_assert(segments != (char**)NULL); segments[count++] = c; c = strchr(c, ' '); if (c == NULL) { break; } *c = '\0'; c++; } *pathCount = count; *paths = segments; } /* URI path decoding - ported from src/share/classes/java/net/URI.java */ static int decodeNibble(char c) { if ((c >= '0') && (c <= '9')) return c - '0'; if ((c >= 'a') && (c <= 'f')) return c - 'a' + 10; if ((c >= 'A') && (c <= 'F')) return c - 'A' + 10; return -1; } static int decodeByte(char c1, char c2) { return (((decodeNibble(c1) & 0xf) << 4) | ((decodeNibble(c2) & 0xf) << 0)); } /* * Evaluates all escapes in s. Assumes that escapes are well-formed * syntactically, i.e., of the form %XX. * If the path does not require decoding the the original path is * returned. Otherwise the decoded path (heap allocated) is returned, * along with the length of the decoded path. Note that the return * string will not be null terminated after decoding. */ static char *decodePath(const char *s, int* decodedLen) { int n; char *result; char *resultp; int c; int i; n = (int)strlen(s); if (n == 0) { *decodedLen = 0; return (char*)s; } if (strchr(s, '%') == NULL) { *decodedLen = n; return (char*)s; /* no escapes, we are done */ } resultp = result = calloc(n+1, 1); c = s[0]; for (i = 0; i < n;) { if (c != '%') { *resultp++ = c; if (++i >= n) break; c = s[i]; continue; } for (;;) { char b1 = s[++i]; char b2 = s[++i]; int decoded = decodeByte(b1, b2); *resultp++ = decoded; if (++i >= n) break; c = s[i]; if (c != '%') break; } } *decodedLen = (int)(resultp - result); return result; // not null terminated. } /* * Append the given jar file to the system class path. This should succeed in the * onload phase but may fail in the live phase if the system class loader doesn't * support appending to the class path. */ static int appendClassPath( JPLISAgent* agent, const char* jarfile ) { jvmtiEnv* jvmtienv = jvmti(agent); jvmtiError jvmtierr; jvmtierr = (*jvmtienv)->AddToSystemClassLoaderSearch(jvmtienv, jarfile); check_phase_ret_1(jvmtierr); if (jvmtierr == JVMTI_ERROR_NONE) { return 0; } else { jvmtiPhase phase; jvmtiError err; err = (*jvmtienv)->GetPhase(jvmtienv, &phase); /* can be called from any phase */ jplis_assert(err == JVMTI_ERROR_NONE); if (phase == JVMTI_PHASE_LIVE) { switch (jvmtierr) { case JVMTI_ERROR_CLASS_LOADER_UNSUPPORTED : fprintf(stderr, "System class loader does not support adding " "JAR file to system class path during the live phase!\n"); break; default: fprintf(stderr, "Unexpected error (%d) returned by " "AddToSystemClassLoaderSearch\n", jvmtierr); break; } return -1; } jplis_assert(0); } return -2; } /* * res = func, free'ing the previous value of 'res' if function * returns a new result. */ #define TRANSFORM(res,func) { \ char* tmp = func; \ if (tmp != res) { \ free(res); \ res = tmp; \ } \ jplis_assert((void*)res != (void*)NULL); \ } /** * Convert a pathname to canonical form. * This method is exported from libjava. */ extern int Canonicalize(JNIEnv *unused, char *orig, char *out, int len); /* * This function takes the value of the Boot-Class-Path attribute, * splits it into the individual path segments, and then combines it * with the path to the jar file to create the path to be added * to the bootclasspath. * * Each individual path segment starts out as a UTF8 string. Additionally * as the path is specified to use URI path syntax all non US-ASCII * characters are escaped. Once the URI path is decoded we get a UTF8 * string which must then be converted to the platform encoding (as it * will be combined with the platform path of the jar file). Once * converted it is then normalized (remove duplicate slashes, etc.). * If the resulting path is an absolute path (starts with a slash for * example) then the path will be added to the bootclasspath. Otherwise * if it's not absolute then we get the canoncial path of the agent jar * file and then resolve the path in the context of the base path of * the agent jar. */ static void appendBootClassPath( JPLISAgent* agent, const char* jarfile, const char* pathList ) { char canonicalPath[MAXPATHLEN]; char *parent = NULL; int haveBasePath = 0; int count, i; char **paths; jvmtiEnv* jvmtienv = jvmti(agent); jvmtiError jvmtierr; /* * Split the attribute value into the individual path segments * and process each in sequence */ splitPathList(pathList, &count, &paths); for (i=0; iAddToBootstrapClassLoaderSearch(jvmtienv, path); } else { char* resolved; if (!haveBasePath) { /* Use NULL as the JNIEnv since we know that Canonicalize does not use it. */ if (Canonicalize(NULL, (char*)jarfile, canonicalPath, sizeof(canonicalPath)) != 0) { fprintf(stderr, "WARNING: unable to canonicalize %s\n", jarfile); free(path); continue; } parent = basePath(canonicalPath); jplis_assert(parent != (char*)NULL); haveBasePath = 1; } resolved = resolve(parent, path); jvmtierr = (*jvmtienv)->AddToBootstrapClassLoaderSearch(jvmtienv, resolved); } /* print warning if boot class path not updated */ if (jvmtierr != JVMTI_ERROR_NONE) { check_phase_blob_ret(jvmtierr, free(path)); fprintf(stderr, "WARNING: %s not added to bootstrap class loader search: ", path); switch (jvmtierr) { case JVMTI_ERROR_ILLEGAL_ARGUMENT : fprintf(stderr, "Illegal argument or not JAR file\n"); break; default: fprintf(stderr, "Unexpected error: %d\n", jvmtierr); } } /* finished with the path */ free(path); } /* clean-up */ if (haveBasePath && parent != canonicalPath) { free(parent); } }