JVM TI技术是JAVA5以后的版本推出的技术,即JVM编程接口,该技术广泛应用于各种开发工具,例如Eclipse等。使用JVM TI可以开发JAVA调试工具,JAVA代码执行监控工具等。同时,了解JVM TI技术也有助于JAVA程序员深入了解JVM的原理,上一篇文章我介绍了采用JVM TI的事件通知技术开发JVM监控工具,但是使用事件通知技术开发应程序性能是非常差劲的,对于源码较多的工程,使用事件通知来捕获JAVA的执行过程,是不可取的,将会对程序的性能造成严重的影响。
在JAVA语言中,可以使用AOP技术修改字节码,著名的框架有Spring,Guice等框架,可以根据程序员的配置对指定的类进行字节码插装,但该技术取决程序员的主观动态,适合在程序的开发过程中使用,如果要实现黑盒的监控过程,是不适用的。例如想知道程序执行过程中调用了哪些类,哪些方法,想通过捕获JAVA调用栈了解程序的执行性能等等,使用JVM TI技术是适用的,商业工具JTest就是采用JVM TI技术实现的,再如QTP等自动化测试工具对JAVA SE程序的界面识别,也是采用JVM TI技术实现。总而言之,采用JVM TI技术,我们可以实现如下功能:
第一步: 本程序我使用VS 2008工具开发和编译,首先使用VS 2008创建一个Win32本地应用程序,详细的步骤请大家参考其他专业文章。
第二步:我们需要使用几个JVM的API类,分别是:agent_util,java_crw_demo两个API,这两个api我们可以在JAVA的例子中找到,其中java_crw_demo是用来插装字节码的,agent_util是agent的工具类
1、agent_util.cpp
/* * * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * -Redistribution of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * -Redistribution in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed, licensed or intended * for use in the design, construction, operation or maintenance of any * nuclear facility. */ // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "stdafx.h" #include <agent_util.h> /* ------------------------------------------------------------------- */ /* Generic C utility functions */ /* Send message to stdout or whatever the data output location is */ void stdout_message(const char * format, ...) { va_list ap; va_start(ap, format); (void)vfprintf(stdout, format, ap); va_end(ap); } /* Send message to stderr or whatever the error output location is and exit */ void fatal_error(const char * format, ...) { va_list ap; va_start(ap, format); (void)vfprintf(stderr, format, ap); (void)fflush(stderr); va_end(ap); exit(3); } /* Get a token from a string (strtok is not MT-safe) * str String to scan * seps Separation characters * buf Place to put results * max Size of buf * Returns NULL if no token available or can't do the scan. */ char * get_token(char *str, char *seps, char *buf, int max) { int len; buf[0] = 0; if ( str==NULL || str[0]==0 ) { return NULL; } str += strspn(str, seps); if ( str[0]==0 ) { return NULL; } len = (int)strcspn(str, seps); if ( len >= max ) { return NULL; } (void)strncpy(buf, str, len); buf[len] = 0; return str+len; } /* Determines if a class/method is specified by a list item * item String that represents a pattern to match * If it starts with a '*', then any class is allowed * If it ends with a '*', then any method is allowed * cname Class name, e.g. "java.lang.Object" * mname Method name, e.g. "<init>" * Returns 1(true) or 0(false). */ static int covered_by_list_item(char *item, char *cname, char *mname) { int len; len = (int)strlen(item); if ( item[0]=='*' ) { if ( strncmp(mname, item+1, len-1)==0 ) { return 1; } } else if ( item[len-1]=='*' ) { if ( strncmp(cname, item, len-1)==0 ) { return 1; } } else { int cname_len; cname_len = (int)strlen(cname); if ( strncmp(cname, item, (len>cname_len?cname_len:len))==0 ) { if ( cname_len >= len ) { /* No method name supplied in item, we must have matched */ return 1; } else { int mname_len; mname_len = (int)strlen(mname); item += cname_len+1; len -= cname_len+1; if ( strncmp(mname, item, (len>mname_len?mname_len:len))==0 ) { return 1; } } } } return 0; } /* Determines if a class/method is specified by this list * list String of comma separated pattern items * cname Class name, e.g. "java.lang.Object" * mname Method name, e.g. "<init>" * Returns 1(true) or 0(false). */ static int covered_by_list(char *list, char *cname, char *mname) { char token[1024]; char *next; if ( list[0] == 0 ) { return 0; } next = get_token(list, ",", token, sizeof(token)); while ( next != NULL ) { if ( covered_by_list_item(token, cname, mname) ) { return 1; } next = get_token(next, ",", token, sizeof(token)); } return 0; } /* Determines which class and methods we are interested in * cname Class name, e.g. "java.lang.Object" * mname Method name, e.g. "<init>" * include_list Empty or an explicit list for inclusion * exclude_list Empty or an explicit list for exclusion * Returns 1(true) or 0(false). */ int interested(char *cname, char *mname, char *include_list, char *exclude_list) { if ( exclude_list!=NULL && exclude_list[0]!=0 && covered_by_list(exclude_list, cname, mname) ) { return 0; } if ( include_list!=NULL && include_list[0]!=0 && !covered_by_list(include_list, cname, mname) ) { return 0; } return 1; } /* ------------------------------------------------------------------- */ /* Generic JVMTI utility functions */ /* Every JVMTI interface returns an error code, which should be checked * to avoid any cascading errors down the line. * The interface GetErrorName() returns the actual enumeration constant * name, making the error messages much easier to understand. */ void check_jvmti_error(jvmtiEnv *jvmti, jvmtiError errnum, const char *str) { if ( errnum != JVMTI_ERROR_NONE ) { char *errnum_str; errnum_str = NULL; (void)(jvmti)->GetErrorName(errnum, &errnum_str); fatal_error("ERROR: JVMTI: %d(%s): %s\n", errnum, (errnum_str==NULL?"Unknown":errnum_str), (str==NULL?"":str)); } } /* All memory allocated by JVMTI must be freed by the JVMTI Deallocate * interface. */ void deallocate1(jvmtiEnv *jvmti, char *ptr) { jvmtiError error; error = (jvmti)->Deallocate((unsigned char*)ptr); check_jvmti_error(jvmti, error, "Cannot deallocate memory"); } /* Allocation of JVMTI managed memory */ void * allocate(jvmtiEnv *jvmti, jint len) { jvmtiError error; void *ptr; error = (jvmti)->Allocate(len, (unsigned char **)&ptr); check_jvmti_error(jvmti, error, "Cannot allocate memory"); return ptr; } /* Add demo jar file to boot class path (the BCI Tracker class must be * in the boot classpath) * * WARNING: This code assumes that the jar file can be found at one of: * ${JAVA_HOME}/demo/jvmti/${DEMO_NAME}/${DEMO_NAME}.jar * ${JAVA_HOME}/../demo/jvmti/${DEMO_NAME}/${DEMO_NAME}.jar * where JAVA_HOME may refer to the jre directory. * Both these values are added to the boot classpath. * These locations are only true for these demos, installed * in the JDK area. Platform specific code could be used to * find the location of the DLL or .so library, and construct a * path name to the jar file, relative to the library location. */ void add_demo_jar_to_bootclasspath(jvmtiEnv *jvmti, char *demo_name) { jvmtiError error; char *file_sep; int max_len; char *java_home; char jar_path[FILENAME_MAX+1]; java_home = NULL; error = (jvmti)->GetSystemProperty("java.home", &java_home); check_jvmti_error(jvmti, error, "Cannot get java.home property value"); if ( java_home == NULL || java_home[0] == 0 ) { fatal_error("ERROR: Java home not found\n"); } #ifdef WIN32 file_sep = "\\"; #else file_sep = "/"; #endif max_len = (int)(strlen(java_home) + strlen(demo_name)*2 + strlen(file_sep)*5 +16 /* ".." "demo" "jvmti" ".jar" NULL */ ); if ( max_len > (int)sizeof(jar_path) ) { fatal_error("ERROR: Path to jar file too long\n"); } (void)strcpy(jar_path, java_home); (void)strcat(jar_path, file_sep); (void)strcat(jar_path, "demo"); (void)strcat(jar_path, file_sep); (void)strcat(jar_path, "jvmti"); (void)strcat(jar_path, file_sep); (void)strcat(jar_path, demo_name); (void)strcat(jar_path, file_sep); (void)strcat(jar_path, demo_name); (void)strcat(jar_path, ".jar"); error = (jvmti)->AddToBootstrapClassLoaderSearch((const char*)jar_path); check_jvmti_error(jvmti, error, "Cannot add to boot classpath"); (void)strcpy(jar_path, java_home); (void)strcat(jar_path, file_sep); (void)strcat(jar_path, ".."); (void)strcat(jar_path, file_sep); (void)strcat(jar_path, "demo"); (void)strcat(jar_path, file_sep); (void)strcat(jar_path, "jvmti"); (void)strcat(jar_path, file_sep); (void)strcat(jar_path, demo_name); (void)strcat(jar_path, file_sep); (void)strcat(jar_path, demo_name); (void)strcat(jar_path, ".jar"); error = (jvmti)->AddToBootstrapClassLoaderSearch((const char*)jar_path); check_jvmti_error(jvmti, error, "Cannot add to boot classpath"); } /* ------------------------------------------------------------------- */
2、java_crw_demo.cpp
/* * @(#)java_crw_demo.c 1.39 06/01/27 * * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * -Redistribution of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * -Redistribution in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed, licensed or intended * for use in the design, construction, operation or maintenance of any * nuclear facility. */ /* Class reader writer (java_crw_demo) for instrumenting bytecodes */ /* * As long as the callbacks allow for it and the class number is unique, * this code is completely re-entrant and any number of classfile * injections can happen at the same time. * * The current logic requires a unique number for this class instance * or (jclass,jobject loader) pair, this is done via the ClassIndex * in hprof, which is passed in as the 'unsigned cnum' to java_crw_demo(). * It's up to the user of this interface if it wants to use this * feature. * * Example Usage: See file test_crw.c. * */ // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "stdafx.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <java_crw_demo.h> /* Get Java and class file and bytecode information. */ #include <jni.h> #include "classfile_constants.h" /* Include our own interface for cross check */ /* Macros over error functions to capture line numbers */ #define CRW_FATAL(ci, message) fatal_error(ci, message, __FILE__, __LINE__) #if defined(DEBUG) || !defined(NDEBUG) #define CRW_ASSERT(ci, cond) \ ((cond)?(void)0:assert_error(ci, #cond, __FILE__, __LINE__)) #else #define CRW_ASSERT(ci, cond) #endif #define CRW_ASSERT_MI(mi) CRW_ASSERT((mi)?(mi)->ci:NULL,(mi)!=NULL) #define CRW_ASSERT_CI(ci) CRW_ASSERT(ci, ( (ci) != NULL && \ (ci)->input_position <= (ci)->input_len && \ (ci)->output_position <= (ci)->output_len) ) /* Typedefs for various integral numbers, just for code clarity */ typedef unsigned ClassOpcode; /* One opcode */ typedef unsigned char ByteCode; /* One byte from bytecodes */ typedef int ByteOffset; /* Byte offset */ typedef int ClassConstant; /* Constant pool kind */ typedef long CrwPosition; /* Position in class image */ typedef unsigned short CrwCpoolIndex; /* Index into constant pool */ /* Misc support macros */ /* Given the position of an opcode, find the next 4byte boundary position */ #define NEXT_4BYTE_BOUNDARY(opcode_pos) (((opcode_pos)+4) & (~3)) #define LARGEST_INJECTION (12*3) /* 3 injections at same site */ #define MAXIMUM_NEW_CPOOL_ENTRIES 64 /* don't add more than 32 entries */ /* Constant Pool Entry (internal table that mirrors pool in file image) */ typedef struct { const char * ptr; /* Pointer to any string */ unsigned short len; /* Length of string */ unsigned int index1; /* 1st 16 bit index or 32bit value. */ unsigned int index2; /* 2nd 16 bit index or 32bit value. */ ClassConstant tag; /* Tag or kind of entry. */ } CrwConstantPoolEntry; struct MethodImage; /* Class file image storage structure */ typedef struct CrwClassImage { /* Unique class number for this class */ unsigned number; /* Name of class, given or gotten out of class image */ const char * name; /* Input and Output class images tracking */ const unsigned char * input; unsigned char * output; CrwPosition input_len; CrwPosition output_len; CrwPosition input_position; CrwPosition output_position; /* Mirrored constant pool */ CrwConstantPoolEntry * cpool; CrwCpoolIndex cpool_max_elements; /* Max count */ CrwCpoolIndex cpool_count_plus_one; /* Input flags about class (e.g. is it a system class) */ int system_class; /* Class access flags gotten from file. */ unsigned access_flags; /* Names of classes and methods. */ char* tclass_name; /* Name of class that has tracker methods. */ char* tclass_sig; /* Signature of class */ char* call_name; /* Method name to call at offset 0 */ char* call_sig; /* Signature of this method */ char* return_name; /* Method name to call before any return */ char* return_sig; /* Signature of this method */ char* obj_init_name; /* Method name to call in Object <init> */ char* obj_init_sig; /* Signature of this method */ char* newarray_name; /* Method name to call after newarray opcodes */ char* newarray_sig; /* Signature of this method */ /* Constant pool index values for new entries */ CrwCpoolIndex tracker_class_index; CrwCpoolIndex object_init_tracker_index; CrwCpoolIndex newarray_tracker_index; CrwCpoolIndex call_tracker_index; CrwCpoolIndex return_tracker_index; CrwCpoolIndex class_number_index; /* Class number in pool */ /* Count of injections made into this class */ int injection_count; /* This class must be the java.lang.Object class */ jboolean is_object_class; /* This class must be the java.lang.Thread class */ jboolean is_thread_class; /* Callback functions */ FatalErrorHandler fatal_error_handler; MethodNumberRegister mnum_callback; /* Table of method names and descr's */ int method_count; const char ** method_name; const char ** method_descr; struct MethodImage * current_mi; } CrwClassImage; /* Injection bytecodes (holds injected bytecodes for each code position) */ typedef struct { ByteCode * code; ByteOffset len; } Injection; /* Method transformation data (allocated/freed as each method is processed) */ typedef struct MethodImage { /* Back reference to Class image data. */ CrwClassImage * ci; /* Unique method number for this class. */ unsigned number; /* Method name and descr */ const char * name; const char * descr; /* Map of input bytecode offsets to output bytecode offsets */ ByteOffset * map; /* Bytecode injections for each input bytecode offset */ Injection * injections; /* Widening setting for each input bytecode offset */ signed char * widening; /* Length of original input bytecodes, and new bytecodes. */ ByteOffset code_len; ByteOffset new_code_len; /* Location in input where bytecodes are located. */ CrwPosition start_of_input_bytecodes; /* Original max_stack and new max stack */ unsigned max_stack; unsigned new_max_stack; jboolean object_init_method; jboolean skip_call_return_sites; /* Method access flags gotten from file. */ unsigned access_flags; } MethodImage; /* ----------------------------------------------------------------- */ /* General support functions (memory and error handling) */ static void fatal_error(CrwClassImage *ci, const char *message, const char *file, int line) { if ( ci != NULL && ci->fatal_error_handler != NULL ) { (*ci->fatal_error_handler)(message, file, line); } else { /* Normal operation should NEVER reach here */ /* NO CRW FATAL ERROR HANDLER! */ (void)fprintf(stderr, "CRW: %s [%s:%d]\n", message, file, line); abort(); } } #if defined(DEBUG) || !defined(NDEBUG) static void assert_error(CrwClassImage *ci, const char *condition, const char *file, int line) { char buf[512]; MethodImage *mi; ByteOffset byte_code_offset; mi = ci->current_mi; if ( mi != NULL ) { byte_code_offset = (ByteOffset)(mi->ci->input_position - mi->start_of_input_bytecodes); } else { byte_code_offset=-1; } (void)sprintf(buf, "CRW ASSERTION FAILURE: %s (%s:%s:%d)", condition, ci->name==0?"?":ci->name, mi->name==0?"?":mi->name, byte_code_offset); fatal_error(ci, buf, file, line); } #endif static void * allocate(CrwClassImage *ci, int nbytes) { void * ptr; if ( nbytes <= 0 ) { CRW_FATAL(ci, "Cannot allocate <= 0 bytes"); } ptr = malloc(nbytes); if ( ptr == NULL ) { CRW_FATAL(ci, "Ran out of malloc memory"); } return ptr; } static void * reallocate(CrwClassImage *ci, void *optr, int nbytes) { void * ptr; if ( optr == NULL ) { CRW_FATAL(ci, "Cannot deallocate NULL"); } if ( nbytes <= 0 ) { CRW_FATAL(ci, "Cannot reallocate <= 0 bytes"); } ptr = realloc(optr, nbytes); if ( ptr == NULL ) { CRW_FATAL(ci, "Ran out of malloc memory"); } return ptr; } static void * allocate_clean(CrwClassImage *ci, int nbytes) { void * ptr; if ( nbytes <= 0 ) { CRW_FATAL(ci, "Cannot allocate <= 0 bytes"); } ptr = calloc(nbytes, 1); if ( ptr == NULL ) { CRW_FATAL(ci, "Ran out of malloc memory"); } return ptr; } static const char * duplicate(CrwClassImage *ci, const char *str, int len) { char *copy; copy = (char*)allocate(ci, len+1); (void)memcpy(copy, str, len); copy[len] = 0; return (const char *)copy; } static void deallocate(CrwClassImage *ci, void *ptr) { if ( ptr == NULL ) { CRW_FATAL(ci, "Cannot deallocate NULL"); } (void)free(ptr); } /* ----------------------------------------------------------------- */ /* Functions for reading/writing bytes to/from the class images */ static unsigned readU1(CrwClassImage *ci) { CRW_ASSERT_CI(ci); return ((unsigned)(ci->input[ci->input_position++])) & 0xFF; } static unsigned readU2(CrwClassImage *ci) { unsigned res; res = readU1(ci); return (res << 8) + readU1(ci); } static signed short readS2(CrwClassImage *ci) { unsigned res; res = readU1(ci); return ((res << 8) + readU1(ci)) & 0xFFFF; } static unsigned readU4(CrwClassImage *ci) { unsigned res; res = readU2(ci); return (res << 16) + readU2(ci); } static void writeU1(CrwClassImage *ci, unsigned val) /* Only writes out lower 8 bits */ { CRW_ASSERT_CI(ci); if ( ci->output != NULL ) { ci->output[ci->output_position++] = val & 0xFF; } } static void writeU2(CrwClassImage *ci, unsigned val) { writeU1(ci, val >> 8); writeU1(ci, val); } static void writeU4(CrwClassImage *ci, unsigned val) { writeU2(ci, val >> 16); writeU2(ci, val); } static unsigned copyU1(CrwClassImage *ci) { unsigned value; value = readU1(ci); writeU1(ci, value); return value; } static unsigned copyU2(CrwClassImage *ci) { unsigned value; value = readU2(ci); writeU2(ci, value); return value; } static unsigned copyU4(CrwClassImage *ci) { unsigned value; value = readU4(ci); writeU4(ci, value); return value; } static void copy(CrwClassImage *ci, unsigned count) { CRW_ASSERT_CI(ci); if ( ci->output != NULL ) { (void)memcpy(ci->output+ci->output_position, ci->input+ci->input_position, count); ci->output_position += count; } ci->input_position += count; CRW_ASSERT_CI(ci); } static void skip(CrwClassImage *ci, unsigned count) { CRW_ASSERT_CI(ci); ci->input_position += count; } static void read_bytes(CrwClassImage *ci, void *bytes, unsigned count) { CRW_ASSERT_CI(ci); CRW_ASSERT(ci, bytes!=NULL); (void)memcpy(bytes, ci->input+ci->input_position, count); ci->input_position += count; } static void write_bytes(CrwClassImage *ci, void *bytes, unsigned count) { CRW_ASSERT_CI(ci); CRW_ASSERT(ci, bytes!=NULL); if ( ci->output != NULL ) { (void)memcpy(ci->output+ci->output_position, bytes, count); ci->output_position += count; } } static void random_writeU2(CrwClassImage *ci, CrwPosition pos, unsigned val) { CrwPosition save_position; CRW_ASSERT_CI(ci); save_position = ci->output_position; ci->output_position = pos; writeU2(ci, val); ci->output_position = save_position; } static void random_writeU4(CrwClassImage *ci, CrwPosition pos, unsigned val) { CrwPosition save_position; CRW_ASSERT_CI(ci); save_position = ci->output_position; ci->output_position = pos; writeU4(ci, val); ci->output_position = save_position; } /* ----------------------------------------------------------------- */ /* Constant Pool handling functions. */ static void fillin_cpool_entry(CrwClassImage *ci, CrwCpoolIndex i, ClassConstant tag, unsigned int index1, unsigned int index2, const char *ptr, int len) { CRW_ASSERT_CI(ci); CRW_ASSERT(ci, i > 0 && i < ci->cpool_count_plus_one); ci->cpool[i].tag = tag; ci->cpool[i].index1 = index1; ci->cpool[i].index2 = index2; ci->cpool[i].ptr = ptr; ci->cpool[i].len = (unsigned short)len; } static CrwCpoolIndex add_new_cpool_entry(CrwClassImage *ci, ClassConstant tag, unsigned int index1, unsigned int index2, const char *str, int len) { CrwCpoolIndex i; char *utf8 = NULL; CRW_ASSERT_CI(ci); i = ci->cpool_count_plus_one++; /* NOTE: This implementation does not automatically expand the * constant pool table beyond the expected number needed * to handle this particular CrwTrackerInterface injections. * See MAXIMUM_NEW_CPOOL_ENTRIES */ CRW_ASSERT(ci, ci->cpool_count_plus_one < ci->cpool_max_elements ); writeU1(ci, tag); switch (tag) { case JVM_CONSTANT_Class: writeU2(ci, index1); break; case JVM_CONSTANT_String: writeU2(ci, index1); break; case JVM_CONSTANT_Fieldref: case JVM_CONSTANT_Methodref: case JVM_CONSTANT_InterfaceMethodref: case JVM_CONSTANT_Integer: case JVM_CONSTANT_Float: case JVM_CONSTANT_NameAndType: writeU2(ci, index1); writeU2(ci, index2); break; case JVM_CONSTANT_Long: case JVM_CONSTANT_Double: writeU4(ci, index1); writeU4(ci, index2); ci->cpool_count_plus_one++; CRW_ASSERT(ci, ci->cpool_count_plus_one < ci->cpool_max_elements ); break; case JVM_CONSTANT_Utf8: CRW_ASSERT(ci, len==(len & 0xFFFF)); writeU2(ci, len); write_bytes(ci, (void*)str, len); utf8 = (char*)duplicate(ci, str, len); break; default: CRW_FATAL(ci, "Unknown constant"); break; } fillin_cpool_entry(ci, i, tag, index1, index2, (const char *)utf8, len); CRW_ASSERT(ci, i > 0 && i < ci->cpool_count_plus_one); return i; } static CrwCpoolIndex add_new_class_cpool_entry(CrwClassImage *ci, const char *class_name) { CrwCpoolIndex name_index; CrwCpoolIndex class_index; int len; CRW_ASSERT_CI(ci); CRW_ASSERT(ci, class_name!=NULL); len = (int)strlen(class_name); name_index = add_new_cpool_entry(ci, JVM_CONSTANT_Utf8, len, 0, class_name, len); class_index = add_new_cpool_entry(ci, JVM_CONSTANT_Class, name_index, 0, NULL, 0); return class_index; } static CrwCpoolIndex add_new_method_cpool_entry(CrwClassImage *ci, CrwCpoolIndex class_index, const char *name, const char *descr) { CrwCpoolIndex name_index; CrwCpoolIndex descr_index; CrwCpoolIndex name_type_index; int len; CRW_ASSERT_CI(ci); CRW_ASSERT(ci, name!=NULL); CRW_ASSERT(ci, descr!=NULL); len = (int)strlen(name); name_index = add_new_cpool_entry(ci, JVM_CONSTANT_Utf8, len, 0, name, len); len = (int)strlen(descr); descr_index = add_new_cpool_entry(ci, JVM_CONSTANT_Utf8, len, 0, descr, len); name_type_index = add_new_cpool_entry(ci, JVM_CONSTANT_NameAndType, name_index, descr_index, NULL, 0); return add_new_cpool_entry(ci, JVM_CONSTANT_Methodref, class_index, name_type_index, NULL, 0); } static CrwConstantPoolEntry cpool_entry(CrwClassImage *ci, CrwCpoolIndex c_index) { CRW_ASSERT_CI(ci); CRW_ASSERT(ci, c_index > 0 && c_index < ci->cpool_count_plus_one); return ci->cpool[c_index]; } static void cpool_setup(CrwClassImage *ci) { CrwCpoolIndex i; CrwPosition cpool_output_position; int count_plus_one; CRW_ASSERT_CI(ci); cpool_output_position = ci->output_position; count_plus_one = copyU2(ci); CRW_ASSERT(ci, count_plus_one>1); ci->cpool_max_elements = count_plus_one+MAXIMUM_NEW_CPOOL_ENTRIES; ci->cpool = (CrwConstantPoolEntry*)allocate_clean(ci, (int)((ci->cpool_max_elements)*sizeof(CrwConstantPoolEntry))); ci->cpool_count_plus_one = (CrwCpoolIndex)count_plus_one; /* Index zero not in class file */ for (i = 1; i < count_plus_one; ++i) { CrwCpoolIndex ipos; ClassConstant tag; unsigned int index1; unsigned int index2; unsigned len; char * utf8; ipos = i; index1 = 0; index2 = 0; len = 0; utf8 = NULL; tag = copyU1(ci); switch (tag) { case JVM_CONSTANT_Class: index1 = copyU2(ci); break; case JVM_CONSTANT_String: index1 = copyU2(ci); break; case JVM_CONSTANT_Fieldref: case JVM_CONSTANT_Methodref: case JVM_CONSTANT_InterfaceMethodref: case JVM_CONSTANT_Integer: case JVM_CONSTANT_Float: case JVM_CONSTANT_NameAndType: index1 = copyU2(ci); index2 = copyU2(ci); break; case JVM_CONSTANT_Long: case JVM_CONSTANT_Double: index1 = copyU4(ci); index2 = copyU4(ci); ++i; /* // these take two CP entries - duh! */ break; case JVM_CONSTANT_Utf8: len = copyU2(ci); index1 = (unsigned short)len; utf8 = (char*)allocate(ci, len+1); read_bytes(ci, (void*)utf8, len); utf8[len] = 0; write_bytes(ci, (void*)utf8, len); break; default: CRW_FATAL(ci, "Unknown constant"); break; } fillin_cpool_entry(ci, ipos, tag, index1, index2, (const char *)utf8, len); } if (ci->call_name != NULL || ci->return_name != NULL) { if ( ci->number != (ci->number & 0x7FFF) ) { ci->class_number_index = add_new_cpool_entry(ci, JVM_CONSTANT_Integer, (ci->number>>16) & 0xFFFF, ci->number & 0xFFFF, NULL, 0); } } if ( ci->tclass_name != NULL ) { ci->tracker_class_index = add_new_class_cpool_entry(ci, ci->tclass_name); } if (ci->obj_init_name != NULL) { ci->object_init_tracker_index = add_new_method_cpool_entry(ci, ci->tracker_class_index, ci->obj_init_name, ci->obj_init_sig); } if (ci->newarray_name != NULL) { ci->newarray_tracker_index = add_new_method_cpool_entry(ci, ci->tracker_class_index, ci->newarray_name, ci->newarray_sig); } if (ci->call_name != NULL) { ci->call_tracker_index = add_new_method_cpool_entry(ci, ci->tracker_class_index, ci->call_name, ci->call_sig); } if (ci->return_name != NULL) { ci->return_tracker_index = add_new_method_cpool_entry(ci, ci->tracker_class_index, ci->return_name, ci->return_sig); } random_writeU2(ci, cpool_output_position, ci->cpool_count_plus_one); } /* ----------------------------------------------------------------- */ /* Functions that create the bytecodes to inject */ static ByteOffset push_pool_constant_bytecodes(ByteCode *bytecodes, CrwCpoolIndex index) { ByteOffset nbytes = 0; if ( index == (index&0x7F) ) { bytecodes[nbytes++] = (ByteCode)JVM_OPC_ldc; } else { bytecodes[nbytes++] = (ByteCode)JVM_OPC_ldc_w; bytecodes[nbytes++] = (ByteCode)((index >> 8) & 0xFF); } bytecodes[nbytes++] = (ByteCode)(index & 0xFF); return nbytes; } static ByteOffset push_short_constant_bytecodes(ByteCode *bytecodes, unsigned number) { ByteOffset nbytes = 0; if ( number <= 5 ) { bytecodes[nbytes++] = (ByteCode)(JVM_OPC_iconst_0+number); } else if ( number == (number&0x7F) ) { bytecodes[nbytes++] = (ByteCode)JVM_OPC_bipush; bytecodes[nbytes++] = (ByteCode)(number & 0xFF); } else { bytecodes[nbytes++] = (ByteCode)JVM_OPC_sipush; bytecodes[nbytes++] = (ByteCode)((number >> 8) & 0xFF); bytecodes[nbytes++] = (ByteCode)(number & 0xFF); } return nbytes; } static ByteOffset injection_template(MethodImage *mi, ByteCode *bytecodes, ByteOffset max_nbytes, CrwCpoolIndex method_index) { CrwClassImage * ci; ByteOffset nbytes = 0; unsigned max_stack; int add_dup; int add_aload; int push_cnum; int push_mnum; ci = mi->ci; CRW_ASSERT(ci, bytecodes!=NULL); if ( method_index == 0 ) { return 0; } if ( method_index == ci->newarray_tracker_index) { max_stack = mi->max_stack + 1; add_dup = JNI_TRUE; add_aload = JNI_FALSE; push_cnum = JNI_FALSE; push_mnum = JNI_FALSE; } else if ( method_index == ci->object_init_tracker_index) { max_stack = mi->max_stack + 1; add_dup = JNI_FALSE; add_aload = JNI_TRUE; push_cnum = JNI_FALSE; push_mnum = JNI_FALSE; } else { max_stack = mi->max_stack + 2; add_dup = JNI_FALSE; add_aload = JNI_FALSE; push_cnum = JNI_TRUE; push_mnum = JNI_TRUE; } if ( add_dup ) { bytecodes[nbytes++] = (ByteCode)JVM_OPC_dup; } if ( add_aload ) { bytecodes[nbytes++] = (ByteCode)JVM_OPC_aload_0; } if ( push_cnum ) { if ( ci->number == (ci->number & 0x7FFF) ) { nbytes += push_short_constant_bytecodes(bytecodes+nbytes, ci->number); } else { CRW_ASSERT(ci, ci->class_number_index!=0); nbytes += push_pool_constant_bytecodes(bytecodes+nbytes, ci->class_number_index); } } if ( push_mnum ) { nbytes += push_short_constant_bytecodes(bytecodes+nbytes, mi->number); } bytecodes[nbytes++] = (ByteCode)JVM_OPC_invokestatic; bytecodes[nbytes++] = (ByteCode)(method_index >> 8); bytecodes[nbytes++] = (ByteCode)method_index; bytecodes[nbytes] = 0; /* bytecodes[nbytes++] = (ByteCode)JVM_OPC_getstatic; bytecodes[nbytes++] = (ByteCode)(ci->trace_field_index >> 8); bytecodes[nbytes++] = (ByteCode)ci->trace_field_index; bytecodes[nbytes++] = (ByteCode)opc_ifne; bytecodes[nbytes++] = (ByteCode)0;*/ //bytecodes[nbytes++]=(ByteCode)opc_getstatic; CRW_ASSERT(ci, nbytes<max_nbytes); /* Make sure the new max_stack is appropriate */ if ( max_stack > mi->new_max_stack ) { mi->new_max_stack = max_stack; } return nbytes; } /* Called to create injection code at entry to a method */ static ByteOffset entry_injection_code(MethodImage *mi, ByteCode *bytecodes, ByteOffset len) { CrwClassImage * ci; ByteOffset nbytes = 0; CRW_ASSERT_MI(mi); ci = mi->ci; if ( mi->object_init_method ) { nbytes = injection_template(mi, bytecodes, len, ci->object_init_tracker_index); } if ( !mi->skip_call_return_sites ) { nbytes += injection_template(mi, bytecodes+nbytes, len-nbytes, ci->call_tracker_index); } return nbytes; } /* Called to create injection code before an opcode */ static ByteOffset before_injection_code(MethodImage *mi, ClassOpcode opcode, ByteCode *bytecodes, ByteOffset len) { ByteOffset nbytes = 0; CRW_ASSERT_MI(mi); switch ( opcode ) { case JVM_OPC_return: case JVM_OPC_ireturn: case JVM_OPC_lreturn: case JVM_OPC_freturn: case JVM_OPC_dreturn: case JVM_OPC_areturn: if ( !mi->skip_call_return_sites ) { nbytes = injection_template(mi, bytecodes, len, mi->ci->return_tracker_index); } break; default: break; } return nbytes; } /* Called to create injection code after an opcode */ static ByteOffset after_injection_code(MethodImage *mi, ClassOpcode opcode, ByteCode *bytecodes, ByteOffset len) { CrwClassImage* ci; ByteOffset nbytes; ci = mi->ci; nbytes = 0; CRW_ASSERT_MI(mi); switch ( opcode ) { case JVM_OPC_new: /* Can't inject here cannot pass around uninitialized object */ break; case JVM_OPC_newarray: case JVM_OPC_anewarray: case JVM_OPC_multianewarray: nbytes = injection_template(mi, bytecodes, len, ci->newarray_tracker_index); break; default: break; } return nbytes; } /* Actually inject the bytecodes */ static void inject_bytecodes(MethodImage *mi, ByteOffset at, ByteCode *bytecodes, ByteOffset len) { Injection injection; CrwClassImage *ci; ci = mi->ci; CRW_ASSERT_MI(mi); CRW_ASSERT(ci, at <= mi->code_len); injection = mi->injections[at]; CRW_ASSERT(ci, len <= LARGEST_INJECTION/2); CRW_ASSERT(ci, injection.len+len <= LARGEST_INJECTION); /* Either start an injection area or concatenate to what is there */ if ( injection.code == NULL ) { CRW_ASSERT(ci, injection.len==0); injection.code = (ByteCode *)allocate_clean(ci, LARGEST_INJECTION+1); } (void)memcpy(injection.code+injection.len, bytecodes, len); injection.len += len; injection.code[injection.len] = 0; mi->injections[at] = injection; ci->injection_count++; } /* ----------------------------------------------------------------- */ /* Method handling functions */ static MethodImage * method_init(CrwClassImage *ci, unsigned mnum, ByteOffset code_len) { MethodImage * mi; ByteOffset i; mi = (MethodImage*)allocate_clean(ci, (int)sizeof(MethodImage)); mi->ci = ci; mi->name = ci->method_name[mnum]; mi->descr = ci->method_descr[mnum]; mi->code_len = code_len; mi->map = (ByteOffset*)allocate_clean(ci, (int)((code_len+1)*sizeof(ByteOffset))); for(i=0; i<=code_len; i++) { mi->map[i] = i; } mi->widening = (signed char*)allocate_clean(ci, code_len+1); mi->injections = (Injection *)allocate_clean(ci, (int)((code_len+1)*sizeof(Injection))); mi->number = mnum; ci->current_mi = mi; return mi; } static void method_term(MethodImage *mi) { CrwClassImage *ci; ci = mi->ci; CRW_ASSERT_MI(mi); if ( mi->map != NULL ) { deallocate(ci, (void*)mi->map); mi->map = NULL; } if ( mi->widening != NULL ) { deallocate(ci, (void*)mi->widening); mi->widening = NULL; } if ( mi->injections != NULL ) { ByteOffset i; for(i=0; i<= mi->code_len; i++) { if ( mi->injections[i].code != NULL ) { deallocate(ci, (void*)mi->injections[i].code); mi->injections[i].code = NULL; } } deallocate(ci, (void*)mi->injections); mi->injections = NULL; } ci->current_mi = NULL; deallocate(ci, (void*)mi); } static ByteOffset input_code_offset(MethodImage *mi) { CRW_ASSERT_MI(mi); return (ByteOffset)(mi->ci->input_position - mi->start_of_input_bytecodes); } static void rewind_to_beginning_of_input_bytecodes(MethodImage *mi) { CRW_ASSERT_MI(mi); mi->ci->input_position = mi->start_of_input_bytecodes; } /* Starting at original byte position 'at', add 'offset' to it's new * location. This may be a negative value. * NOTE: That this map is not the new bytecode location of the opcode * but the new bytecode location that should be used when * a goto or jump instruction was targeting the old bytecode * location. */ static void adjust_map(MethodImage *mi, ByteOffset at, ByteOffset offset) { ByteOffset i; CRW_ASSERT_MI(mi); for (i = at; i <= mi->code_len; ++i) { mi->map[i] += offset; } } static void widen(MethodImage *mi, ByteOffset at, ByteOffset len) { int delta; CRW_ASSERT(mi->ci, at <= mi->code_len); delta = len - mi->widening[at]; /* Adjust everything from the current input location by delta */ adjust_map(mi, input_code_offset(mi), delta); /* Mark at beginning of instruction */ mi->widening[at] = (signed char)len; } static void verify_opc_wide(CrwClassImage *ci, ClassOpcode wopcode) { switch (wopcode) { case JVM_OPC_aload: case JVM_OPC_astore: case JVM_OPC_fload: case JVM_OPC_fstore: case JVM_OPC_iload: case JVM_OPC_istore: case JVM_OPC_lload: case JVM_OPC_lstore: case JVM_OPC_dload: case JVM_OPC_dstore: case JVM_OPC_ret: case JVM_OPC_iinc: break; default: CRW_FATAL(ci, "Invalid opcode supplied to wide opcode"); break; } } static unsigned opcode_length(CrwClassImage *ci, ClassOpcode opcode) { /* Define array that holds length of an opcode */ static unsigned char _opcode_length[JVM_OPC_MAX+1] = JVM_OPCODE_LENGTH_INITIALIZER; if ( opcode > JVM_OPC_MAX ) { CRW_FATAL(ci, "Invalid opcode supplied to opcode_length()"); } return _opcode_length[opcode]; } /* Walk one instruction and inject instrumentation */ static void inject_for_opcode(MethodImage *mi) { CrwClassImage * ci; ClassOpcode opcode; int pos; CRW_ASSERT_MI(mi); ci = mi->ci; pos = input_code_offset(mi); opcode = readU1(ci); if (opcode == JVM_OPC_wide) { ClassOpcode wopcode; wopcode = readU1(ci); /* lvIndex not used */ (void)readU2(ci); verify_opc_wide(ci, wopcode); if ( wopcode==JVM_OPC_iinc ) { (void)readU1(ci); (void)readU1(ci); } } else { ByteCode bytecodes[LARGEST_INJECTION+1]; int header; int instr_len; int low; int high; int npairs; ByteOffset len; /* Get bytecodes to inject before this opcode */ len = before_injection_code(mi, opcode, bytecodes, (int)sizeof(bytecodes)); if ( len > 0 ) { inject_bytecodes(mi, pos, bytecodes, len); /* Adjust map after processing this opcode */ } /* Process this opcode */ switch (opcode) { case JVM_OPC_tableswitch: header = NEXT_4BYTE_BOUNDARY(pos); skip(ci, header - (pos+1)); (void)readU4(ci); low = readU4(ci); high = readU4(ci); skip(ci, (high+1-low) * 4); break; case JVM_OPC_lookupswitch: header = NEXT_4BYTE_BOUNDARY(pos); skip(ci, header - (pos+1)); (void)readU4(ci); npairs = readU4(ci); skip(ci, npairs * 8); break; default: instr_len = opcode_length(ci, opcode); skip(ci, instr_len-1); break; } /* Get position after this opcode is processed */ pos = input_code_offset(mi); /* Adjust for any before_injection_code() */ if ( len > 0 ) { /* Adjust everything past this opcode. * Why past it? Because we want any jumps to this bytecode loc * to go to the injected code, not where the opcode * was moved too. * Consider a 'return' opcode that is jumped too. * NOTE: This may not be correct in all cases, but will * when we are only dealing with non-variable opcodes * like the return opcodes. Be careful if the * before_injection_code() changes to include other * opcodes that have variable length. */ adjust_map(mi, pos, len); } /* Get bytecodes to inject after this opcode */ len = after_injection_code(mi, opcode, bytecodes, (int)sizeof(bytecodes)); if ( len > 0 ) { inject_bytecodes(mi, pos, bytecodes, len); /* Adjust for any after_injection_code() */ adjust_map(mi, pos, len); } } } /* Map original bytecode location to it's new location. (See adjust_map()). */ static ByteOffset method_code_map(MethodImage *mi, ByteOffset pos) { CRW_ASSERT_MI(mi); CRW_ASSERT(mi->ci, pos <= mi->code_len); return mi->map[pos]; } static int adjust_instruction(MethodImage *mi) { CrwClassImage * ci; ClassOpcode opcode; int pos; int new_pos; CRW_ASSERT_MI(mi); ci = mi->ci; pos = input_code_offset(mi); new_pos = method_code_map(mi,pos); opcode = readU1(ci); if (opcode == JVM_OPC_wide) { ClassOpcode wopcode; wopcode = readU1(ci); /* lvIndex not used */ (void)readU2(ci); verify_opc_wide(ci, wopcode); if ( wopcode==JVM_OPC_iinc ) { (void)readU1(ci); (void)readU1(ci); } } else { int widened; int header; int newHeader; int low; int high; int new_pad; int old_pad; int delta; int new_delta; int delta_pad; int npairs; int instr_len; switch (opcode) { case JVM_OPC_tableswitch: widened = mi->widening[pos]; header = NEXT_4BYTE_BOUNDARY(pos); newHeader = NEXT_4BYTE_BOUNDARY(new_pos); skip(ci, header - (pos+1)); delta = readU4(ci); low = readU4(ci); high = readU4(ci); skip(ci, (high+1-low) * 4); new_pad = newHeader - new_pos; old_pad = header - pos; delta_pad = new_pad - old_pad; if (widened != delta_pad) { widen(mi, pos, delta_pad); return 0; } break; case JVM_OPC_lookupswitch: widened = mi->widening[pos]; header = NEXT_4BYTE_BOUNDARY(pos); newHeader = NEXT_4BYTE_BOUNDARY(new_pos); skip(ci, header - (pos+1)); delta = readU4(ci); npairs = readU4(ci); skip(ci, npairs * 8); new_pad = newHeader - new_pos; old_pad = header - pos; delta_pad = new_pad - old_pad; if (widened != delta_pad) { widen(mi, pos, delta_pad); return 0; } break; case JVM_OPC_jsr: case JVM_OPC_goto: case JVM_OPC_ifeq: case JVM_OPC_ifge: case JVM_OPC_ifgt: case JVM_OPC_ifle: case JVM_OPC_iflt: case JVM_OPC_ifne: case JVM_OPC_if_icmpeq: case JVM_OPC_if_icmpne: case JVM_OPC_if_icmpge: case JVM_OPC_if_icmpgt: case JVM_OPC_if_icmple: case JVM_OPC_if_icmplt: case JVM_OPC_if_acmpeq: case JVM_OPC_if_acmpne: case JVM_OPC_ifnull: case JVM_OPC_ifnonnull: widened = mi->widening[pos]; delta = readS2(ci); if (widened == 0) { new_delta = method_code_map(mi,pos+delta) - new_pos; if ((new_delta < -32768) || (new_delta > 32767)) { switch (opcode) { case JVM_OPC_jsr: case JVM_OPC_goto: widen(mi, pos, 2); break; default: widen(mi, pos, 5); break; } return 0; } } break; case JVM_OPC_jsr_w: case JVM_OPC_goto_w: (void)readU4(ci); break; default: instr_len = opcode_length(ci, opcode); skip(ci, instr_len-1); break; } } return 1; } static void write_instruction(MethodImage *mi) { CrwClassImage * ci; ClassOpcode opcode; ByteOffset new_code_len; int pos; int new_pos; CRW_ASSERT_MI(mi); ci = mi->ci; pos = input_code_offset(mi); new_pos = method_code_map(mi,pos); new_code_len = mi->injections[pos].len; if (new_code_len > 0) { write_bytes(ci, (void*)mi->injections[pos].code, new_code_len); } opcode = readU1(ci); if (opcode == JVM_OPC_wide) { ClassOpcode wopcode; writeU1(ci, opcode); wopcode = copyU1(ci); /* lvIndex not used */ (void)copyU2(ci); verify_opc_wide(ci, wopcode); if ( wopcode==JVM_OPC_iinc ) { (void)copyU1(ci); (void)copyU1(ci); } } else { ClassOpcode new_opcode; int header; int newHeader; int low; int high; int i; int npairs; int widened; int instr_len; int delta; int new_delta; switch (opcode) { case JVM_OPC_tableswitch: header = NEXT_4BYTE_BOUNDARY(pos); newHeader = NEXT_4BYTE_BOUNDARY(new_pos); skip(ci, header - (pos+1)); delta = readU4(ci); new_delta = method_code_map(mi,pos+delta) - new_pos; low = readU4(ci); high = readU4(ci); writeU1(ci, opcode); for (i = new_pos+1; i < newHeader; ++i) { writeU1(ci, 0); } writeU4(ci, new_delta); writeU4(ci, low); writeU4(ci, high); for (i = low; i <= high; ++i) { delta = readU4(ci); new_delta = method_code_map(mi,pos+delta) - new_pos; writeU4(ci, new_delta); } break; case JVM_OPC_lookupswitch: header = NEXT_4BYTE_BOUNDARY(pos); newHeader = NEXT_4BYTE_BOUNDARY(new_pos); skip(ci, header - (pos+1)); delta = readU4(ci); new_delta = method_code_map(mi,pos+delta) - new_pos; npairs = readU4(ci); writeU1(ci, opcode); for (i = new_pos+1; i < newHeader; ++i) { writeU1(ci, 0); } writeU4(ci, new_delta); writeU4(ci, npairs); for (i = 0; i< npairs; ++i) { unsigned match = readU4(ci); delta = readU4(ci); new_delta = method_code_map(mi,pos+delta) - new_pos; writeU4(ci, match); writeU4(ci, new_delta); } break; case JVM_OPC_jsr: case JVM_OPC_goto: case JVM_OPC_ifeq: case JVM_OPC_ifge: case JVM_OPC_ifgt: case JVM_OPC_ifle: case JVM_OPC_iflt: case JVM_OPC_ifne: case JVM_OPC_if_icmpeq: case JVM_OPC_if_icmpne: case JVM_OPC_if_icmpge: case JVM_OPC_if_icmpgt: case JVM_OPC_if_icmple: case JVM_OPC_if_icmplt: case JVM_OPC_if_acmpeq: case JVM_OPC_if_acmpne: case JVM_OPC_ifnull: case JVM_OPC_ifnonnull: widened = mi->widening[pos]; delta = readS2(ci); new_delta = method_code_map(mi,pos+delta) - new_pos; new_opcode = opcode; if (widened == 0) { writeU1(ci, opcode); writeU2(ci, new_delta); } else if (widened == 2) { switch (opcode) { case JVM_OPC_jsr: new_opcode = JVM_OPC_jsr_w; break; case JVM_OPC_goto: new_opcode = JVM_OPC_goto_w; break; default: CRW_FATAL(ci, "unexpected opcode"); break; } writeU1(ci, new_opcode); writeU4(ci, new_delta); } else if (widened == 5) { switch (opcode) { case JVM_OPC_ifeq: new_opcode = JVM_OPC_ifne; break; case JVM_OPC_ifge: new_opcode = JVM_OPC_iflt; break; case JVM_OPC_ifgt: new_opcode = JVM_OPC_ifle; break; case JVM_OPC_ifle: new_opcode = JVM_OPC_ifgt; break; case JVM_OPC_iflt: new_opcode = JVM_OPC_ifge; break; case JVM_OPC_ifne: new_opcode = JVM_OPC_ifeq; break; case JVM_OPC_if_icmpeq: new_opcode = JVM_OPC_if_icmpne; break; case JVM_OPC_if_icmpne: new_opcode = JVM_OPC_if_icmpeq; break; case JVM_OPC_if_icmpge: new_opcode = JVM_OPC_if_icmplt; break; case JVM_OPC_if_icmpgt: new_opcode = JVM_OPC_if_icmple; break; case JVM_OPC_if_icmple: new_opcode = JVM_OPC_if_icmpgt; break; case JVM_OPC_if_icmplt: new_opcode = JVM_OPC_if_icmpge; break; case JVM_OPC_if_acmpeq: new_opcode = JVM_OPC_if_acmpne; break; case JVM_OPC_if_acmpne: new_opcode = JVM_OPC_if_acmpeq; break; case JVM_OPC_ifnull: new_opcode = JVM_OPC_ifnonnull; break; case JVM_OPC_ifnonnull: new_opcode = JVM_OPC_ifnull; break; default: CRW_FATAL(ci, "Unexpected opcode"); break; } writeU1(ci, new_opcode); /* write inverse branch */ writeU2(ci, 3 + 5); /* beyond if and goto_w */ writeU1(ci, JVM_OPC_goto_w); /* add a goto_w */ writeU4(ci, new_delta-3); /* write new and wide delta */ } else { CRW_FATAL(ci, "Unexpected widening"); } break; case JVM_OPC_jsr_w: case JVM_OPC_goto_w: delta = readU4(ci); new_delta = method_code_map(mi,pos+delta) - new_pos; writeU1(ci, opcode); writeU4(ci, new_delta); break; default: instr_len = opcode_length(ci, opcode); writeU1(ci, opcode); copy(ci, instr_len-1); break; } } } static void method_inject_and_write_code(MethodImage *mi) { ByteCode bytecodes[LARGEST_INJECTION+1]; ByteOffset len; CRW_ASSERT_MI(mi); /* Do injections */ rewind_to_beginning_of_input_bytecodes(mi); len = entry_injection_code(mi, bytecodes, (int)sizeof(bytecodes)); if ( len > 0 ) { int pos; pos = 0; inject_bytecodes(mi, pos, bytecodes, len); /* Adjust pos 0 to map to new pos 0, you never want to * jump into this entry code injection. So the new pos 0 * will be past this entry_injection_code(). */ adjust_map(mi, pos, len); /* Inject before behavior */ } while (input_code_offset(mi) < mi->code_len) { inject_for_opcode(mi); } /* Adjust instructions */ rewind_to_beginning_of_input_bytecodes(mi); while (input_code_offset(mi) < mi->code_len) { if (!adjust_instruction(mi)) { rewind_to_beginning_of_input_bytecodes(mi); } } /* Write new instructions */ rewind_to_beginning_of_input_bytecodes(mi); while (input_code_offset(mi) < mi->code_len) { write_instruction(mi); } } static void copy_attribute(CrwClassImage *ci) { int len; (void)copyU2(ci); len = copyU4(ci); copy(ci, len); } static void copy_attributes(CrwClassImage *ci) { unsigned i; unsigned count; count = copyU2(ci); for (i = 0; i < count; ++i) { copy_attribute(ci); } } static void copy_all_fields(CrwClassImage *ci) { unsigned i; unsigned count; count = copyU2(ci); for (i = 0; i < count; ++i) { /* access, name, descriptor */ copy(ci, 6); copy_attributes(ci); } } static void write_line_table(MethodImage *mi) { unsigned i; unsigned count; CrwClassImage * ci; CRW_ASSERT_MI(mi); ci = mi->ci; (void)copyU4(ci); count = copyU2(ci); for(i=0; i<count; i++) { ByteOffset start_pc; ByteOffset new_start_pc; start_pc = readU2(ci); if ( start_pc == 0 ) { new_start_pc = 0; /* Don't skip entry injection code. */ } else { new_start_pc = method_code_map(mi, start_pc); } writeU2(ci, new_start_pc); (void)copyU2(ci); } } /* Used for LocalVariableTable and LocalVariableTypeTable attributes */ static void write_var_table(MethodImage *mi) { unsigned i; unsigned count; CrwClassImage * ci; CRW_ASSERT_MI(mi); ci = mi->ci; (void)copyU4(ci); count = copyU2(ci); for(i=0; i<count; i++) { ByteOffset start_pc; ByteOffset new_start_pc; ByteOffset length; ByteOffset new_length; ByteOffset end_pc; ByteOffset new_end_pc; start_pc = readU2(ci); length = readU2(ci); if ( start_pc == 0 ) { new_start_pc = 0; /* Don't skip entry injection code. */ } else { new_start_pc = method_code_map(mi, start_pc); } end_pc = start_pc + length; new_end_pc = method_code_map(mi, end_pc); new_length = new_end_pc - new_start_pc; writeU2(ci, new_start_pc); writeU2(ci, new_length); (void)copyU2(ci); (void)copyU2(ci); (void)copyU2(ci); } } /* The uoffset field is u2 or u4 depending on the code_len. * Note that the code_len is likely changing, so be careful here. */ static unsigned readUoffset(MethodImage *mi) { if ( mi->code_len > 65535 ) { return readU4(mi->ci); } return readU2(mi->ci); } static void writeUoffset(MethodImage *mi, unsigned val) { if ( mi->new_code_len > 65535 ) { writeU4(mi->ci, val); } writeU2(mi->ci, val); } static unsigned copyUoffset(MethodImage *mi) { unsigned uoffset; uoffset = readUoffset(mi); writeUoffset(mi, uoffset); return uoffset; } /* Copy over verification_type_info structure */ static void copy_verification_types(MethodImage *mi, int ntypes) { /* If there were ntypes, we just copy that over, no changes */ if ( ntypes > 0 ) { int j; for ( j = 0 ; j < ntypes ; j++ ) { unsigned tag; tag = copyU1(mi->ci); switch ( tag ) { case JVM_ITEM_Object: (void)copyU2(mi->ci); /* Constant pool entry */ break; case JVM_ITEM_Uninitialized: /* Code offset for 'new' opcode is for this object */ writeUoffset(mi, method_code_map(mi, readUoffset(mi))); break; } } } } /* Process the StackMapTable attribute. We didn't add any basic blocks * so the frame count remains the same but we may need to process the * frame types due to offset changes putting things out of range. */ static void write_stackmap_table(MethodImage *mi) { CrwClassImage *ci; CrwPosition save_position; ByteOffset last_pc; ByteOffset last_new_pc; unsigned i; unsigned attr_len; unsigned new_attr_len; unsigned count; unsigned delta_adj; CRW_ASSERT_MI(mi); ci = mi->ci; /* Save the position of the attribute length so we can fix it later */ save_position = ci->output_position; attr_len = copyU4(ci); count = copyUoffset(mi); /* uoffset: number_of_entries */ if ( count == 0 ) { CRW_ASSERT(ci, attr_len==2); return; } /* Process entire stackmap */ last_pc = 0; last_new_pc = 0; delta_adj = 0; for ( i = 0 ; i < count ; i++ ) { ByteOffset new_pc; /* new pc in instrumented code */ unsigned ft; /* frame_type */ int delta; /* pc delta */ int new_delta; /* new pc delta */ ft = readU1(ci); if ( ft <= 63 ) { /* Frame Type: same_frame ([0,63]) */ unsigned new_ft; /* new frame_type */ delta = (delta_adj + ft); new_pc = method_code_map(mi, last_pc + delta); new_delta = new_pc - last_new_pc; new_ft = (new_delta - delta_adj); if ( new_ft > 63 ) { /* Change to same_frame_extended (251) */ new_ft = 251; writeU1(ci, new_ft); writeUoffset(mi, (new_delta - delta_adj)); } else { writeU1(ci, new_ft); } } else if ( ft >= 64 && ft <= 127 ) { /* Frame Type: same_locals_1_stack_item_frame ([64,127]) */ unsigned new_ft; /* new frame_type */ delta = (delta_adj + ft - 64); new_pc = method_code_map(mi, last_pc + delta); new_delta = new_pc - last_new_pc; if ( (new_delta - delta_adj) > 63 ) { /* Change to same_locals_1_stack_item_frame_extended (247) */ new_ft = 247; writeU1(ci, new_ft); writeUoffset(mi, (new_delta - delta_adj)); } else { new_ft = (new_delta - delta_adj) + 64; writeU1(ci, new_ft); } copy_verification_types(mi, 1); } else if ( ft >= 128 && ft <= 246 ) { /* Frame Type: reserved_for_future_use ([128,246]) */ CRW_FATAL(ci, "Unknown frame type in StackMapTable attribute"); } else if ( ft == 247 ) { /* Frame Type: same_locals_1_stack_item_frame_extended (247) */ delta = (delta_adj + readUoffset(mi)); new_pc = method_code_map(mi, last_pc + delta); new_delta = new_pc - last_new_pc; writeU1(ci, ft); writeUoffset(mi, (new_delta - delta_adj)); copy_verification_types(mi, 1); } else if ( ft >= 248 && ft <= 250 ) { /* Frame Type: chop_frame ([248,250]) */ delta = (delta_adj + readUoffset(mi)); new_pc = method_code_map(mi, last_pc + delta); new_delta = new_pc - last_new_pc; writeU1(ci, ft); writeUoffset(mi, (new_delta - delta_adj)); } else if ( ft == 251 ) { /* Frame Type: same_frame_extended (251) */ delta = (delta_adj + readUoffset(mi)); new_pc = method_code_map(mi, last_pc + delta); new_delta = new_pc - last_new_pc; writeU1(ci, ft); writeUoffset(mi, (new_delta - delta_adj)); } else if ( ft >= 252 && ft <= 254 ) { /* Frame Type: append_frame ([252,254]) */ delta = (delta_adj + readUoffset(mi)); new_pc = method_code_map(mi, last_pc + delta); new_delta = new_pc - last_new_pc; writeU1(ci, ft); writeUoffset(mi, (new_delta - delta_adj)); copy_verification_types(mi, (ft - 251)); } else if ( ft == 255 ) { unsigned ntypes; /* Frame Type: full_frame (255) */ delta = (delta_adj + readUoffset(mi)); new_pc = method_code_map(mi, last_pc + delta); new_delta = new_pc - last_new_pc; writeU1(ci, ft); writeUoffset(mi, (new_delta - delta_adj)); ntypes = copyU2(ci); /* ulocalvar */ copy_verification_types(mi, ntypes); ntypes = copyU2(ci); /* ustack */ copy_verification_types(mi, ntypes); } /* Update last_pc and last_new_pc (save on calls to method_code_map) */ CRW_ASSERT(ci, delta >= 0); CRW_ASSERT(ci, new_delta >= 0); last_pc += delta; last_new_pc = new_pc; CRW_ASSERT(ci, last_pc <= mi->code_len); CRW_ASSERT(ci, last_new_pc <= mi->new_code_len); /* Delta adjustment, all deltas are -1 now in attribute */ delta_adj = 1; } /* Update the attribute length */ new_attr_len = ci->output_position - (save_position + 4); CRW_ASSERT(ci, new_attr_len >= attr_len); random_writeU4(ci, save_position, new_attr_len); } /* Process the CLDC StackMap attribute. We didn't add any basic blocks * so the frame count remains the same but we may need to process the * frame types due to offset changes putting things out of range. */ static void write_cldc_stackmap_table(MethodImage *mi) { CrwClassImage *ci; CrwPosition save_position; unsigned i; unsigned attr_len; unsigned new_attr_len; unsigned count; CRW_ASSERT_MI(mi); ci = mi->ci; /* Save the position of the attribute length so we can fix it later */ save_position = ci->output_position; attr_len = copyU4(ci); count = copyUoffset(mi); /* uoffset: number_of_entries */ if ( count == 0 ) { CRW_ASSERT(ci, attr_len==2); return; } /* Process entire stackmap */ for ( i = 0 ; i < count ; i++ ) { unsigned ntypes; writeUoffset(mi, method_code_map(mi, readUoffset(mi))); ntypes = copyU2(ci); /* ulocalvar */ copy_verification_types(mi, ntypes); ntypes = copyU2(ci); /* ustack */ copy_verification_types(mi, ntypes); } /* Update the attribute length */ new_attr_len = ci->output_position - (save_position + 4); CRW_ASSERT(ci, new_attr_len >= attr_len); random_writeU4(ci, save_position, new_attr_len); } static void method_write_exception_table(MethodImage *mi) { unsigned i; unsigned count; CrwClassImage * ci; CRW_ASSERT_MI(mi); ci = mi->ci; count = copyU2(ci); for(i=0; i<count; i++) { ByteOffset start_pc; ByteOffset new_start_pc; ByteOffset end_pc; ByteOffset new_end_pc; ByteOffset handler_pc; ByteOffset new_handler_pc; start_pc = readU2(ci); end_pc = readU2(ci); handler_pc = readU2(ci); new_start_pc = method_code_map(mi, start_pc); new_end_pc = method_code_map(mi, end_pc); new_handler_pc = method_code_map(mi, handler_pc); writeU2(ci, new_start_pc); writeU2(ci, new_end_pc); writeU2(ci, new_handler_pc); (void)copyU2(ci); } } static int attribute_match(CrwClassImage *ci, CrwCpoolIndex name_index, const char *name) { CrwConstantPoolEntry cs; int len; CRW_ASSERT_CI(ci); CRW_ASSERT(ci, name!=NULL); len = (int)strlen(name); cs = cpool_entry(ci, name_index); if ( cs.len==len && strncmp(cs.ptr, name, len)==0) { return 1; } return 0; } static void method_write_code_attribute(MethodImage *mi) { CrwClassImage * ci; CrwCpoolIndex name_index; CRW_ASSERT_MI(mi); ci = mi->ci; name_index = copyU2(ci); if ( attribute_match(ci, name_index, "LineNumberTable") ) { write_line_table(mi); } else if ( attribute_match(ci, name_index, "LocalVariableTable") ) { write_var_table(mi); } else if ( attribute_match(ci, name_index, "LocalVariableTypeTable") ) { write_var_table(mi); /* Exact same format as the LocalVariableTable */ } else if ( attribute_match(ci, name_index, "StackMapTable") ) { write_stackmap_table(mi); } else if ( attribute_match(ci, name_index, "StackMap") ) { write_cldc_stackmap_table(mi); } else { unsigned len; len = copyU4(ci); copy(ci, len); } } static int is_init_method(const char *name) { if ( name!=NULL && strcmp(name,"<init>")==0 ) { return JNI_TRUE; } return JNI_FALSE; } static int is_clinit_method(const char *name) { if ( name!=NULL && strcmp(name,"<clinit>")==0 ) { return JNI_TRUE; } return JNI_FALSE; } static int is_finalize_method(const char *name) { if ( name!=NULL && strcmp(name,"finalize")==0 ) { return JNI_TRUE; } return JNI_FALSE; } static int skip_method(CrwClassImage *ci, const char *name, unsigned access_flags, ByteOffset code_len, int system_class, jboolean *pskip_call_return_sites) { *pskip_call_return_sites = JNI_FALSE; if ( system_class ) { if ( code_len == 1 && is_init_method(name) ) { return JNI_TRUE; } else if ( code_len == 1 && is_finalize_method(name) ) { return JNI_TRUE; } else if ( is_clinit_method(name) ) { return JNI_TRUE; } else if ( ci->is_thread_class && strcmp(name,"currentThread")==0 ) { return JNI_TRUE; } /* if ( access_flags & JVM_ACC_PRIVATE ) { *pskip_call_return_sites = JNI_TRUE; } */ } return JNI_FALSE; } /* Process all code attributes */ static void method_write_bytecodes(CrwClassImage *ci, unsigned mnum, unsigned access_flags) { CrwPosition output_attr_len_position; CrwPosition output_max_stack_position; CrwPosition output_code_len_position; CrwPosition start_of_output_bytecodes; unsigned i; unsigned attr_len; unsigned max_stack; ByteOffset code_len; unsigned attr_count; unsigned new_attr_len; MethodImage * mi; jboolean object_init_method; jboolean skip_call_return_sites; CRW_ASSERT_CI(ci); /* Attribute Length */ output_attr_len_position = ci->output_position; attr_len = copyU4(ci); /* Max Stack */ output_max_stack_position = ci->output_position; max_stack = copyU2(ci); /* Max Locals */ (void)copyU2(ci); /* Code Length */ output_code_len_position = ci->output_position; code_len = copyU4(ci); start_of_output_bytecodes = ci->output_position; /* Some methods should not be instrumented */ object_init_method = JNI_FALSE; skip_call_return_sites = JNI_FALSE; if ( ci->is_object_class && is_init_method(ci->method_name[mnum]) && strcmp(ci->method_descr[mnum],"()V")==0 ) { object_init_method = JNI_TRUE; skip_call_return_sites = JNI_TRUE; } else if ( skip_method(ci, ci->method_name[mnum], access_flags, code_len, ci->system_class, &skip_call_return_sites) ) { /* Copy remainder minus already copied, the U2 max_stack, * U2 max_locals, and U4 code_length fields have already * been processed. */ copy(ci, attr_len - (2+2+4)); return; } /* Start Injection */ mi = method_init(ci, mnum, code_len); mi->object_init_method = object_init_method; mi->access_flags = access_flags; mi->skip_call_return_sites = skip_call_return_sites; /* Save the current position as the start of the input bytecodes */ mi->start_of_input_bytecodes = ci->input_position; /* The max stack may increase */ mi->max_stack = max_stack; mi->new_max_stack = max_stack; /* Adjust all code offsets */ method_inject_and_write_code(mi); /* Fix up code length (save new_code_len for later attribute processing) */ mi->new_code_len = (int)(ci->output_position - start_of_output_bytecodes); random_writeU4(ci, output_code_len_position, mi->new_code_len); /* Fixup max stack */ CRW_ASSERT(ci, mi->new_max_stack <= 0xFFFF); random_writeU2(ci, output_max_stack_position, mi->new_max_stack); /* Copy exception table */ method_write_exception_table(mi); /* Copy code attributes (needs mi->new_code_len) */ attr_count = copyU2(ci); for (i = 0; i < attr_count; ++i) { method_write_code_attribute(mi); } /* Fix up attribute length */ new_attr_len = (int)(ci->output_position - (output_attr_len_position + 4)); random_writeU4(ci, output_attr_len_position, new_attr_len); /* Free method data */ method_term(mi); mi = NULL; } static void method_write(CrwClassImage *ci, unsigned mnum) { unsigned i; unsigned access_flags; CrwCpoolIndex name_index; CrwCpoolIndex descr_index; unsigned attr_count; access_flags = copyU2(ci); name_index = copyU2(ci); ci->method_name[mnum] = cpool_entry(ci, name_index).ptr; descr_index = copyU2(ci); ci->method_descr[mnum] = cpool_entry(ci, descr_index).ptr; attr_count = copyU2(ci); for (i = 0; i < attr_count; ++i) { CrwCpoolIndex name_index; name_index = copyU2(ci); if ( attribute_match(ci, name_index, "Code") ) { method_write_bytecodes(ci, mnum, access_flags); } else { unsigned len; len = copyU4(ci); copy(ci, len); } } } static void method_write_all(CrwClassImage *ci) { unsigned i; unsigned count; count = copyU2(ci); ci->method_count = count; if ( count > 0 ) { ci->method_name = (const char **)allocate_clean(ci, count*(int)sizeof(const char*)); ci->method_descr = (const char **)allocate_clean(ci, count*(int)sizeof(const char*)); } for (i = 0; i < count; ++i) { method_write(ci, i); } if ( ci->mnum_callback != NULL ) { (*(ci->mnum_callback))(ci->number, ci->method_name, ci->method_descr, count); } } /* ------------------------------------------------------------------- */ /* Cleanup function. */ static void cleanup(CrwClassImage *ci) { CRW_ASSERT_CI(ci); if ( ci->name != NULL ) { deallocate(ci, (void*)ci->name); ci->name = NULL; } if ( ci->method_name != NULL ) { deallocate(ci, (void*)ci->method_name); ci->method_name = NULL; } if ( ci->method_descr != NULL ) { deallocate(ci, (void*)ci->method_descr); ci->method_descr = NULL; } if ( ci->cpool != NULL ) { CrwCpoolIndex i; for(i=0; i<ci->cpool_count_plus_one; i++) { if ( ci->cpool[i].ptr != NULL ) { deallocate(ci, (void*)(ci->cpool[i].ptr)); ci->cpool[i].ptr = NULL; } } deallocate(ci, (void*)ci->cpool); ci->cpool = NULL; } } static jboolean skip_class(unsigned access_flags) { if ( access_flags & JVM_ACC_INTERFACE ) { return JNI_TRUE; } return JNI_FALSE; } static long inject_class(struct CrwClassImage *ci, int system_class, char* tclass_name, char* tclass_sig, char* call_name, char* call_sig, char* return_name, char* return_sig, char* obj_init_name, char* obj_init_sig, char* newarray_name, char* newarray_sig, unsigned char *buf, long buf_len) { CrwConstantPoolEntry cs; CrwCpoolIndex this_class; CrwCpoolIndex super_class; unsigned magic; unsigned classfileVersion; unsigned interface_count; CRW_ASSERT_CI(ci); CRW_ASSERT(ci, buf!=NULL); CRW_ASSERT(ci, buf_len!=0); CRW_ASSERT(ci, strchr(tclass_name,'.')==NULL); /* internal qualified name */ ci->injection_count = 0; ci->system_class = system_class; ci->tclass_name = tclass_name; ci->tclass_sig = tclass_sig; ci->call_name = call_name; ci->call_sig = call_sig; ci->return_name = return_name; ci->return_sig = return_sig; ci->obj_init_name = obj_init_name; ci->obj_init_sig = obj_init_sig; ci->newarray_name = newarray_name; ci->newarray_sig = newarray_sig; ci->output = buf; ci->output_len = buf_len; magic = copyU4(ci); CRW_ASSERT(ci, magic==0xCAFEBABE); if ( magic != 0xCAFEBABE ) { return (long)0; } /* minor version number not used */ (void)copyU2(ci); /* major version number not used */ classfileVersion = copyU2(ci); CRW_ASSERT(ci, classfileVersion <= 50); /* Mustang class files or less */ cpool_setup(ci); ci->access_flags = copyU2(ci); if ( skip_class(ci->access_flags) ) { return (long)0; } this_class = copyU2(ci); cs = cpool_entry(ci, (CrwCpoolIndex)(cpool_entry(ci, this_class).index1)); if ( ci->name == NULL ) { ci->name = duplicate(ci, cs.ptr, cs.len); CRW_ASSERT(ci, strchr(ci->name,'.')==NULL); /* internal qualified name */ } CRW_ASSERT(ci, (int)strlen(ci->name)==cs.len && strncmp(ci->name, cs.ptr, cs.len)==0); super_class = copyU2(ci); if ( super_class == 0 ) { ci->is_object_class = JNI_TRUE; CRW_ASSERT(ci, strcmp(ci->name,"java/lang/Object")==0); } interface_count = copyU2(ci); copy(ci, interface_count * 2); copy_all_fields(ci); method_write_all(ci); if ( ci->injection_count == 0 ) { return (long)0; } copy_attributes(ci); return (long)ci->output_position; } /* ------------------------------------------------------------------- */ /* Exported interfaces */ JNIEXPORT void JNICALL java_crw_demo(unsigned class_number, const char *name, const unsigned char *file_image, long file_len, int system_class, char* tclass_name, /* Name of class that has tracker methods. */ char* tclass_sig, /* Signature of tclass */ char* call_name, /* Method name to call at offset 0 */ char* call_sig, /* Signature of this method */ char* return_name, /* Method name to call before any return */ char* return_sig, /* Signature of this method */ char* obj_init_name, /* Method name to call in Object <init> */ char* obj_init_sig, /* Signature of this method */ char* newarray_name, /* Method name to call after newarray opcodes */ char* newarray_sig, /* Signature of this method */ unsigned char **pnew_file_image, long *pnew_file_len, FatalErrorHandler fatal_error_handler, MethodNumberRegister mnum_callback) { CrwClassImage ci; long max_length; long new_length; void *new_image; int len; /* Initial setup of the CrwClassImage structure */ (void)memset(&ci, 0, (int)sizeof(CrwClassImage)); ci.fatal_error_handler = fatal_error_handler; ci.mnum_callback = mnum_callback; /* Do some interface error checks */ if ( pnew_file_image==NULL ) { CRW_FATAL(&ci, "pnew_file_image==NULL"); } if ( pnew_file_len==NULL ) { CRW_FATAL(&ci, "pnew_file_len==NULL"); } /* No file length means do nothing */ *pnew_file_image = NULL; *pnew_file_len = 0; if ( file_len==0 ) { return; } /* Do some more interface error checks */ if ( file_image == NULL ) { CRW_FATAL(&ci, "file_image == NULL"); } if ( file_len < 0 ) { CRW_FATAL(&ci, "file_len < 0"); } if ( system_class != 0 && system_class != 1 ) { CRW_FATAL(&ci, "system_class is not 0 or 1"); } if ( tclass_name == NULL ) { CRW_FATAL(&ci, "tclass_name == NULL"); } if ( tclass_sig == NULL || tclass_sig[0]!='L' ) { CRW_FATAL(&ci, "tclass_sig is not a valid class signature"); } len = (int)strlen(tclass_sig); if ( tclass_sig[len-1]!=';' ) { CRW_FATAL(&ci, "tclass_sig is not a valid class signature"); } if ( call_name != NULL ) { if ( call_sig == NULL || strcmp(call_sig, "(II)V") != 0 ) { CRW_FATAL(&ci, "call_sig is not (II)V"); } } if ( return_name != NULL ) { if ( return_sig == NULL || strcmp(return_sig, "(II)V") != 0 ) { CRW_FATAL(&ci, "return_sig is not (II)V"); } } if ( obj_init_name != NULL ) { if ( obj_init_sig == NULL || strcmp(obj_init_sig, "(Ljava/lang/Object;)V") != 0 ) { CRW_FATAL(&ci, "obj_init_sig is not (Ljava/lang/Object;)V"); } } if ( newarray_name != NULL ) { if ( newarray_sig == NULL || strcmp(newarray_sig, "(Ljava/lang/Object;)V") != 0 ) { CRW_FATAL(&ci, "newarray_sig is not (Ljava/lang/Object;)V"); } } /* Finish setup the CrwClassImage structure */ ci.is_thread_class = JNI_FALSE; if ( name != NULL ) { CRW_ASSERT(&ci, strchr(name,'.')==NULL); /* internal qualified name */ ci.name = duplicate(&ci, name, (int)strlen(name)); if ( strcmp(name, "java/lang/Thread")==0 ) { ci.is_thread_class = JNI_TRUE; } } ci.number = class_number; ci.input = file_image; ci.input_len = file_len; /* Do the injection */ max_length = file_len*2 + 512; /* Twice as big + 512 */ new_image = allocate(&ci, (int)max_length); new_length = inject_class(&ci, system_class, tclass_name, tclass_sig, call_name, call_sig, return_name, return_sig, obj_init_name, obj_init_sig, newarray_name, newarray_sig, (unsigned char*)new_image, max_length); /* Dispose or shrink the space to be returned. */ if ( new_length == 0 ) { deallocate(&ci, (void*)new_image); new_image = NULL; } else { new_image = (void*)reallocate(&ci, (void*)new_image, (int)new_length); } /* Return the new class image */ *pnew_file_image = (unsigned char *)new_image; *pnew_file_len = (long)new_length; /* Cleanup before we leave. */ cleanup(&ci); } /* Return the classname for this class which is inside the classfile image. */ JNIEXPORT char * JNICALL java_crw_demo_classname(const unsigned char *file_image, long file_len, FatalErrorHandler fatal_error_handler) { CrwClassImage ci; CrwConstantPoolEntry cs; CrwCpoolIndex this_class; unsigned magic; char * name; name = NULL; if ( file_len==0 || file_image==NULL ) { return name; } /* The only fields we need filled in are the image pointer and the error * handler. * By not adding an output buffer pointer, no output is created. */ (void)memset(&ci, 0, (int)sizeof(CrwClassImage)); ci.input = file_image; ci.input_len = file_len; ci.fatal_error_handler = fatal_error_handler; /* Read out the bytes from the classfile image */ magic = readU4(&ci); /* magic number */ CRW_ASSERT(&ci, magic==0xCAFEBABE); if ( magic != 0xCAFEBABE ) { return name; } (void)readU2(&ci); /* minor version number */ (void)readU2(&ci); /* major version number */ /* Read in constant pool. Since no output setup, writes are NOP's */ cpool_setup(&ci); (void)readU2(&ci); /* access flags */ this_class = readU2(&ci); /* 'this' class */ /* Get 'this' constant pool entry */ cs = cpool_entry(&ci, (CrwCpoolIndex)(cpool_entry(&ci, this_class).index1)); /* Duplicate the name */ name = (char *)duplicate(&ci, cs.ptr, cs.len); /* Cleanup before we leave. */ cleanup(&ci); /* Return malloc space */ return name; }
3、test.cpp
/* * @(#)minst.c 1.1 06/01/28 * * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * -Redistribution of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * -Redistribution in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed, licensed or intended * for use in the design, construction, operation or maintenance of any * nuclear facility. */ // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "stdafx.h" #include "stdlib.h" #include <jni_md.h> #include <jvmti.h> #include "java_crw_demo.h" #include <agent_util.h> /* ------------------------------------------------------------------- */ /* Some constant maximum sizes */ #define MAX_TOKEN_LENGTH 80 #define MAX_METHOD_NAME_LENGTH 256 /* Some constant names that tie to Java class/method names. * We assume the Java class whose static methods we will be calling * looks like: * * public class Minst { * private static int engaged; * private static native void _method_entry(Object thr, int cnum, int mnum); * public static void method_entry(int cnum, int mnum) * { * ... * } * } * */ #define MINST_class Minst /* Name of class we are using */ #define MINST_entry method_entry /* Name of java entry method */ #define MINST_engaged engaged /* Name of java static field */ /* C macros to create strings from tokens */ #define _STRING(s) #s #define STRING(s) _STRING(s) /* ------------------------------------------------------------------- */ /* Global agent data structure */ typedef struct { /* JVMTI Environment */ jvmtiEnv *jvmti; jboolean vm_is_dead; jboolean vm_is_started; /* Data access Lock */ jrawMonitorID lock; /* Options */ char *include; char *exclude; /* Class Count/ID */ jint ccount; } GlobalAgentData; static GlobalAgentData *gdata; /* Enter a critical section by doing a JVMTI Raw Monitor Enter */ static void enter_critical_section(jvmtiEnv *jvmti) { jvmtiError error; error = (jvmti)->RawMonitorEnter(gdata->lock); check_jvmti_error(jvmti, error, "Cannot enter with raw monitor"); } /* Exit a critical section by doing a JVMTI Raw Monitor Exit */ static void exit_critical_section(jvmtiEnv *jvmti) { jvmtiError error; error = (jvmti)->RawMonitorExit(gdata->lock); check_jvmti_error(jvmti, error, "Cannot exit with raw monitor"); } /* Callback for JVMTI_EVENT_VM_START */ static void JNICALL cbVMStart(jvmtiEnv *jvmti, JNIEnv *env) { enter_critical_section(jvmti); { /* Indicate VM has started */ gdata->vm_is_started = JNI_TRUE; } exit_critical_section(jvmti); } /* Callback for JVMTI_EVENT_VM_INIT */ static void JNICALL cbVMInit(jvmtiEnv *jvmti, JNIEnv *env, jthread thread) { enter_critical_section(jvmti); { jclass klass; jfieldID field; /* Register Natives for class whose methods we use */ klass = (env)->FindClass(STRING(MINST_class)); if ( klass == NULL ) { fatal_error("ERROR: JNI: Cannot find %s with FindClass\n", STRING(MINST_class)); } /* Engage calls. */ field = (env)->GetStaticFieldID(klass, STRING(MINST_engaged), "I"); if ( field == NULL ) { fatal_error("ERROR: JNI: Cannot get field from %s\n", STRING(MINST_class)); } (env)->SetStaticIntField(klass, field, 1); } exit_critical_section(jvmti); } /* Callback for JVMTI_EVENT_VM_DEATH */ static void JNICALL cbVMDeath(jvmtiEnv *jvmti, JNIEnv *env) { enter_critical_section(jvmti); { jclass klass; jfieldID field; /* The VM has died. */ stdout_message("VMDeath\n"); /* Disengage calls in MINST_class. */ klass = (env)->FindClass(STRING(MINST_class)); if ( klass == NULL ) { fatal_error("ERROR: JNI: Cannot find %s with FindClass\n", STRING(MINST_class)); } field = (env)->GetStaticFieldID(klass, STRING(MINST_engaged), "I"); if ( field == NULL ) { fatal_error("ERROR: JNI: Cannot get field from %s\n", STRING(MINST_class)); } (env)->SetStaticIntField(klass, field, -1); /* The critical section here is important to hold back the VM death * until all other callbacks have completed. */ /* Since this critical section could be holding up other threads * in other event callbacks, we need to indicate that the VM is * dead so that the other callbacks can short circuit their work. * We don't expect any further events after VmDeath but we do need * to be careful that existing threads might be in our own agent * callback code. */ gdata->vm_is_dead = JNI_TRUE; } exit_critical_section(jvmti); } /* Callback for JVMTI_EVENT_CLASS_FILE_LOAD_HOOK */ static void JNICALL cbClassFileLoadHook(jvmtiEnv *jvmti, JNIEnv* env, jclass class_being_redefined, jobject loader, const char* name, jobject protection_domain, jint class_data_len, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) { enter_critical_section(jvmti); { /* It's possible we get here right after VmDeath event, be careful */ if ( !gdata->vm_is_dead ) { const char *classname; /* Name could be NULL */ if ( name == NULL ) { classname = java_crw_demo_classname(class_data, class_data_len,NULL); if ( classname == NULL ) { fatal_error("ERROR: No classname inside classfile\n"); } } else { classname = strdup(name); if ( classname == NULL ) { fatal_error("ERROR: Out of malloc memory\n"); } } *new_class_data_len = 0; *new_class_data = NULL; /* The tracker class itself? */ if ( interested((char*)classname, "", gdata->include, gdata->exclude) && strcmp(classname, STRING(MINST_class)) != 0 ) { jint cnum; int system_class; unsigned char *new_image; long new_length; /* Get unique number for every class file image loaded */ cnum = gdata->ccount++; /* Is it a system class? If the class load is before VmStart * then we will consider it a system class that should * be treated carefully. (See java_crw_demo) */ system_class = 0; if ( !gdata->vm_is_started ) { system_class = 1; } new_image = NULL; new_length = 0; /* Call the class file reader/write demo code */ java_crw_demo(cnum, classname, class_data, class_data_len, system_class, STRING(MINST_class), "L" STRING(MINST_class) ";", STRING(MINST_entry), "(II)V", NULL, NULL, NULL, NULL, NULL, NULL, &new_image, &new_length, NULL, NULL); /* If we got back a new class image, return it back as "the" * new class image. This must be JVMTI Allocate space. */ if ( new_length > 0 ) { unsigned char *jvmti_space; jvmti_space = (unsigned char *)allocate(jvmti, (jint)new_length); (void)memcpy((void*)jvmti_space, (void*)new_image, (int)new_length); *new_class_data_len = (jint)new_length; *new_class_data = jvmti_space; /* VM will deallocate */ } /* Always free up the space we get from java_crw_demo() */ if ( new_image != NULL ) { (void)free((void*)new_image); /* Free malloc() space with free() */ } } (void)free((void*)classname); } } exit_critical_section(jvmti); } /* Parse the options for this minst agent */ static void parse_agent_options(char *options) { char token[MAX_TOKEN_LENGTH]; char *next; /* Parse options and set flags in gdata */ if ( options==NULL ) { return; } /* Get the first token from the options string. */ next = get_token(options, ",=", token, sizeof(token)); /* While not at the end of the options string, process this option. */ while ( next != NULL ) { if ( strcmp(token,"help")==0 ) { stdout_message("The minst JVMTI demo agent\n"); stdout_message("\n"); stdout_message(" java -agent:minst[=options] ...\n"); stdout_message("\n"); stdout_message("The options are comma separated:\n"); stdout_message("\t help\t\t\t Print help information\n"); stdout_message("\t include=item\t\t Only these classes/methods\n"); stdout_message("\t exclude=item\t\t Exclude these classes/methods\n"); stdout_message("\n"); stdout_message("item\t Qualified class and/or method names\n"); stdout_message("\t\t e.g. (*.<init>;Foobar.method;sun.*)\n"); stdout_message("\n"); exit(0); } else if ( strcmp(token,"include")==0 ) { int used; int maxlen; maxlen = MAX_METHOD_NAME_LENGTH; if ( gdata->include == NULL ) { gdata->include = (char*)calloc(maxlen+1, 1); used = 0; } else { used = (int)strlen(gdata->include); gdata->include[used++] = ','; gdata->include[used] = 0; gdata->include = (char*)realloc((void*)gdata->include, used+maxlen+1); } if ( gdata->include == NULL ) { fatal_error("ERROR: Out of malloc memory\n"); } /* Add this item to the list */ next = get_token(next, ",=", gdata->include+used, maxlen); printf("下一个规则%s",next); /* Check for token scan error */ if ( next==NULL ) { fatal_error("ERROR: include option error\n"); } } else if ( strcmp(token,"exclude")==0 ) { int used; int maxlen; maxlen = MAX_METHOD_NAME_LENGTH; if ( gdata->exclude == NULL ) { gdata->exclude = (char*)calloc(maxlen+1, 1); used = 0; } else { used = (int)strlen(gdata->exclude); gdata->exclude[used++] = ','; gdata->exclude[used] = 0; gdata->exclude = (char*) realloc((void*)gdata->exclude, used+maxlen+1); } if ( gdata->exclude == NULL ) { fatal_error("ERROR: Out of malloc memory\n"); } /* Add this item to the list */ next = get_token(next, ",=", gdata->exclude+used, maxlen); /* Check for token scan error */ if ( next==NULL ) { fatal_error("ERROR: exclude option error\n"); } } else if ( token[0]!=0 ) { /* We got a non-empty token and we don't know what it is. */ fatal_error("ERROR: Unknown option: %s\n", token); } /* Get the next token (returns NULL if there are no more) */ next = get_token(next, ",=", token, sizeof(token)); } } /* Agent_OnLoad: This is called immediately after the shared library is * loaded. This is the first code executed. */ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { static GlobalAgentData data; jvmtiEnv *jvmti; jvmtiError error; jint res; jvmtiCapabilities capabilities; jvmtiEventCallbacks callbacks; /* Setup initial global agent data area * Use of static/extern data should be handled carefully here. * We need to make sure that we are able to cleanup after ourselves * so anything allocated in this library needs to be freed in * the Agent_OnUnload() function. */ (void)memset((void*)&data, 0, sizeof(data)); gdata = &data; /* First thing we need to do is get the jvmtiEnv* or JVMTI environment */ res = (vm)->GetEnv((void **)&jvmti, JVMTI_VERSION_1); if (res != JNI_OK) { /* This means that the VM was unable to obtain this version of the * JVMTI interface, this is a fatal error. */ fatal_error("ERROR: Unable to access JVMTI Version 1 (0x%x)," " is your JDK a 5.0 or newer version?" " JNIEnv's GetEnv() returned %d\n", JVMTI_VERSION_1, res); } /* Here we save the jvmtiEnv* for Agent_OnUnload(). */ gdata->jvmti = jvmti; /* Parse any options supplied on java command line */ parse_agent_options(options); /* Immediately after getting the jvmtiEnv* we need to ask for the * capabilities this agent will need. In this case we need to make * sure that we can get all class load hooks. */ (void)memset(&capabilities,0, sizeof(capabilities)); capabilities.can_generate_all_class_hook_events = 1; error = (jvmti)->AddCapabilities(&capabilities); check_jvmti_error(jvmti, error, "Unable to get necessary JVMTI capabilities."); /* Next we need to provide the pointers to the callback functions to * to this jvmtiEnv* */ (void)memset(&callbacks,0, sizeof(callbacks)); /* JVMTI_EVENT_VM_START */ callbacks.VMStart = &cbVMStart; /* JVMTI_EVENT_VM_INIT */ callbacks.VMInit = &cbVMInit; /* JVMTI_EVENT_VM_DEATH */ callbacks.VMDeath = &cbVMDeath; /* JVMTI_EVENT_CLASS_FILE_LOAD_HOOK */ callbacks.ClassFileLoadHook = &cbClassFileLoadHook; error = (jvmti)->SetEventCallbacks(&callbacks, (jint)sizeof(callbacks)); check_jvmti_error(jvmti, error, "Cannot set jvmti callbacks"); /* At first the only initial events we are interested in are VM * initialization, VM death, and Class File Loads. * Once the VM is initialized we will request more events. */ error = (jvmti)->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_START, (jthread)NULL); check_jvmti_error(jvmti, error, "Cannot set event notification"); error = (jvmti)->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, (jthread)NULL); check_jvmti_error(jvmti, error, "Cannot set event notification"); error = (jvmti)->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, (jthread)NULL); check_jvmti_error(jvmti, error, "Cannot set event notification"); error = (jvmti)->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, (jthread)NULL); check_jvmti_error(jvmti, error, "Cannot set event notification"); /* Here we create a raw monitor for our use in this agent to * protect critical sections of code. */ error = (jvmti)->CreateRawMonitor("agent data", &(gdata->lock)); check_jvmti_error(jvmti, error, "Cannot create raw monitor"); /* Add demo jar file to boot classpath */ add_demo_jar_to_bootclasspath(jvmti, "minst"); /* We return JNI_OK to signify success */ return JNI_OK; } /* Agent_OnUnload: This is called immediately before the shared library is * unloaded. This is the last code executed. */ JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) { /* Make sure all malloc/calloc/strdup space is freed */ if ( gdata->include != NULL ) { (void)free((void*)gdata->include); gdata->include = NULL; } if ( gdata->exclude != NULL ) { (void)free((void*)gdata->exclude); gdata->exclude = NULL; } }
4、JAVA程序
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.management.ManagementFactory; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; import java.util.List; import java.util.Properties; import javax.net.ssl.ManagerFactoryParameters; import com.unittest.conf.ConfigModel; import com.unittest.util.ki; import com.unittest.util.sd; /* *作者:山人 * * 输出JVM代码执行记录 * */ public class JVMUnitTest { /* Master switch that activates methods. */ private static int engaged = 0; public static void method_entry(int cnum, int mnum) { Class x = JVMUnitTest.class; synchronized (x) { if (engaged > 0) { engaged = 0; String className = "Unknown"; String methodName = "Unknown"; int lineNumber=0; Exception exp = new Exception(); StackTraceElement[] stack = exp.getStackTrace(); if (stack.length > 1) { StringBuffer sb = new StringBuffer(); StackTraceElement location = stack[1]; className = location.getClassName(); methodName = location.getMethodName(); lineNumber=location.getLineNumber(); Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); String execDate = sdf.format(date); ConfigModel cm = JVMUnitTest.readerConfig(); List<String> vmArgment = ManagementFactory.getRuntimeMXBean().getInputArguments(); String agentLibPath = null; String datFilePath = null; for (int i = 0; i < vmArgment.size(); i++) { String agentLib = vmArgment.get(i); if (agentLib != null && agentLib.indexOf("-agentlib") != -1) { agentLibPath = agentLib; break; } } if (agentLibPath != null) { int firstIndex=agentLibPath.indexOf(":"); int lastIndex=agentLibPath.lastIndexOf("\\"); if(firstIndex!=-1&&lastIndex!=-1) { datFilePath=agentLibPath.substring(firstIndex+1, lastIndex); } } File file = new File(datFilePath + "/" + cm.getDevName() + "-" + cm.getProjectName() + "-" + cm.getVersion() + ".dat"); if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } sb.append("[" + execDate + "] [" + cm.getProjectName() + "] [" + cm.getAuth() + "] " + "[" + className + "] [" + methodName + "]\r\n"); try { String text=sd.byte2hex(sb.toString().getBytes()); ki.method1(file,text); } catch (Exception e) { e.printStackTrace(); } } engaged++; } } } private static ConfigModel readerConfig() { System.getenv(); ConfigModel configMoel = new ConfigModel(); try { configMoel.setAuth(System.getProperty("auth")); configMoel.setVersion(System.getProperty("version")); configMoel.setProjectName(System.getProperty("project")); configMoel.setDevName(System.getProperty("devName")); } catch (Exception e) { e.printStackTrace(); } return configMoel; } }
完整的程序请到我的空间里下载