├── README.md ├── heapTagging ├── .gitignore ├── Makefile ├── pom.xml ├── resources │ └── META-INF │ │ └── MANIFEST.MF └── src │ ├── main │ ├── c │ │ └── tagger.cpp │ └── java │ │ └── net │ │ └── jonbell │ │ └── examples │ │ └── jvmti │ │ └── tagging │ │ ├── inst │ │ ├── MetadataTagAddingCV.java │ │ └── PreMain.java │ │ └── runtime │ │ ├── Tagged.java │ │ └── Tagger.java │ └── test │ └── java │ └── net │ └── jonbell │ └── examples │ └── jvmti │ └── tagging │ └── TaggingITCase.java ├── heapWalking ├── .gitignore ├── Makefile ├── pom.xml └── src │ ├── main │ ├── c │ │ └── walker.cpp │ └── java │ │ └── net │ │ └── jonbell │ │ └── examples │ │ └── jvmti │ │ └── walking │ │ └── runtime │ │ └── HeapWalker.java │ └── test │ └── java │ └── net │ └── jonbell │ └── examples │ └── jvmti │ └── tagging │ └── WalkingITCase.java ├── methodCoverage ├── .gitignore ├── pom.xml ├── resources │ └── META-INF │ │ └── MANIFEST.MF └── src │ ├── main │ └── java │ │ └── net │ │ └── jonbell │ │ └── examples │ │ └── methodprof │ │ ├── PreMain.java │ │ ├── ProfileLogger.java │ │ └── inst │ │ └── MethodProfilingCV.java │ └── test │ └── java │ └── net │ └── jonbell │ └── examples │ └── methodprof │ └── MethodCovIT.java ├── nativeWrapping ├── Makefile ├── pom.xml ├── resources │ └── META-INF │ │ └── MANIFEST.MF └── src │ ├── main │ ├── c │ │ └── tagger.cpp │ └── java │ │ └── net │ │ └── jonbell │ │ └── examples │ │ └── jvmti │ │ └── nativeWrapping │ │ ├── inst │ │ ├── NativeWrappingCV.java │ │ └── PreMain.java │ │ └── runtime │ │ └── NativeLogger.java │ └── test │ └── java │ └── net │ └── jonbell │ └── examples │ └── jvmti │ └── nativeWrapping │ └── NativeWrappingITCase.java ├── staticInstrumenter ├── .gitignore ├── pom.xml ├── resources │ └── META-INF │ │ └── MANIFEST.MF └── src │ └── main │ └── java │ └── net │ └── jonbell │ └── examples │ └── bytecode │ └── instrumenting │ ├── ClassCoverageCV.java │ ├── ClassCoverageClassFileTransformer.java │ ├── CoverageLogger.java │ ├── Instrumenter.java │ ├── InstrumenterMojo.java │ └── PreMain.java └── staticInstrumenterUsage ├── .gitignore ├── pom.xml └── src ├── main └── java │ └── net │ └── jonbell │ └── examples │ └── bytecode │ └── instrument │ └── demo │ └── FooClass.java └── test └── java └── net └── jonbell └── examples └── bytecode └── instrument └── demo └── ClassCoverageIT.java /README.md: -------------------------------------------------------------------------------- 1 | # Java Bytecode and JVMTI Examples 2 | 3 | A lot of my research has involved Java bytecode instrumentation with [ASM](http://asm.ow2.org/) and more recently, also [JVMTI](http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html) development. For instance, with [VMVM](https://github.com/Programming-Systems-Lab/vmvm), we instrumented bytecode to enable efficient java class reinitialization. With [Phosphor](https://github.com/Programming-Systems-Lab/phosphor), we instrumented bytecode to track metadata with every single variable, enabling performant and portable dynamic taint tracking. In [ElectricTest](http://jonbell.net/publications/electrictest), we combined bytecode instrumentation with JVMTI to efficiently detect data dependencies between test cases running within the same JVM. 4 | 5 | As I built each of these projects, I leaned heavily on code snippets that I found across the internet – while I had done a lot of Java development before starting these projects, I’d done nothing with bytecode instrumentation. I found it particularly difficult to find examples of how to use JVMTI (aside from the basic man-pages, and a few excellent blog posts by Kelly O’Hair [[1](https://blogs.oracle.com/kto/entry/using_vm_agents), [2](http://www.oracle.com/technetwork/articles/java/jvmpitransition-138768.html), [3](https://weblogs.java.net/blog/2005/05/13/bytecode-instrumentation-bci)]). 6 | 7 | To try to make it easier for others to use the same tools to build their own systems, I’m compiling some examples that I think might be useful here, along with some brief tutorials. I don’t intend for this to serve as a beginner’s resource – there are [plenty of bytecode instrumentation](https://www.google.com/search?q=java+bytecode+instrumentation+tutorial) tutorials out there – instead, I plan to collect some interesting examples (mostly related to JVMTI), that I think would be useful. If you have any particular requests, please let me know (as a comment on my [blog post](http://jonbell.net/2015/10/java-bytecode-and-jvmti-examples/), or via [email](mailto:jbell@cs.columbia.edu), or [twitter](https://twitter.com/_jon_bell_)). 8 | 9 | Byte code rewriting can be used to change and insert instructions in code, and JVMTI can be used to interact with low level events in the JVM (such as objects being freed, garbage collection beginning, and allows you to assign a relatively efficient tag to any reference type (object or array). Each one of these examples has some interesting trick though, that I thought was worthy to share. Each one is a maven project, and you can build and run the tests with mvn verify (the JVMTI projects should work on Mac OS X and Linux, but are not configured to build on Windows – it’s possible to do but the scripts aren’t there). To import them in eclipse, first run mvn eclipse:eclipse in the project to generate eclipse project configuration files. 10 | 11 | 1. [Method Coverage recording](https://github.com/jon-bell/bytecode-examples/tree/master/methodCoverage) – efficiently records per-method coverage information (e.g. which methods in an application under test are executed by each test). Byte code is instrumented dynamically as its loaded into the JVM (using a java agent). There is a local cache within each class that records whether a method was hit during the current test case, and a global collection that stores them too. This local + global cache is much more performant than just keeping a global cache, because when each method is executed we can first check a local boolean field (which is easily optimized by the JIT compiler), and if it hasn’t been hit, THEN we store the fact that the method was executed in a global set (which is relatively much more expensive). 12 | 2. [Static Instrumenter](https://github.com/jon-bell/bytecode-examples/tree/master/staticInstrumenter) – applies byte code instrumentation statically, rather than at load time. This technique is needed if you want to instrument various core JRE classes that would be loaded already (and immutable) after your javaagent gets called. 13 | 3. [Heap Tagging](https://github.com/jon-bell/bytecode-examples/tree/master/heapTagging) – uses JVMTI and byte code instrumentation to allow you to apply an arbitrary object “label” to every reference type (objects or arrays). Doing this for many instances of classes (objects) is trivial: we just add a field to each class to store the tag, and generate some code to set and fetch it for each class (every class is made to implement the interface [Tagged](https://github.com/jon-bell/bytecode-examples/blob/master/heapTagging/src/main/java/net/jonbell/examples/jvmti/tagging/runtime/Tagged.java)). However, you can’t do this for all classes – the constant pool offsets for some fields of some classes (like Object, Long, Byte, etc.); plus you can’t do this for arrays (which aren’t instances of classes). For this, we use JVMTI’s [getTag](http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#GetTag) and [setTag](http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#SetTag) functions. [Tagger](https://github.com/jon-bell/bytecode-examples/blob/master/heapTagging/src/main/java/net/jonbell/examples/jvmti/tagging/runtime/Tagger.java) provides an abstraction to get and set the label of an object. The JVMTI [code implementation](https://github.com/jon-bell/bytecode-examples/blob/master/heapTagging/src/main/c/tagger.cpp) is mostly book-keeping that makes sure that we don’t leak memory from these object labels. The JVMTI code is largely inspired by [another excellent example by Kelly O’Hair](https://blogs.oracle.com/kto/entry/using_vm_agents). 14 | 4. [Heap Walking](https://github.com/jon-bell/bytecode-examples/tree/master/heapWalking) – uses JVMTI for a slightly contrived (but still somewhat interesting) example of heap walking and tagging. It crawls the heap (using [FollowReferences](http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#FollowReferences)), and for every object, builds a list of the static fields that can reach that object. After crawling, the library can return the list of static fields that point (perhaps indirectly) to the requested object. This example also shows off how to calculate the internal JVM field offsets for classes (which was a pain to write out my first time...). 15 | Let me know ([email](mailto:jbell@cs.columbia.edu), [twitter](https://twitter.com/_jon_bell_), or [comment on my blog post](http://jonbell.net/2015/10/java-bytecode-and-jvmti-examples/)) if you have any questions or requests. -------------------------------------------------------------------------------- /heapTagging/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /heapTagging/Makefile: -------------------------------------------------------------------------------- 1 | UNAME := $(shell uname) 2 | CCFLAGS = -o target/taggingExample.o -I${JAVA_HOME}/include -c -fPIC -fpermissive 3 | ifeq ($(UNAME), Linux) 4 | CCFLAGS += -I${JAVA_HOME}/include/linux 5 | LINKFLAGS = -z defs -static-libgcc -shared -o target/libtaggingExample.so -lc 6 | endif 7 | ifeq ($(UNAME), Darwin) 8 | CCFLAGS += -I${JAVA_HOME}/include/darwin 9 | LINKFLAGS += -dynamiclib -o target/libtaggingExample.so 10 | endif 11 | 12 | libtaggingExample.dylib: 13 | gcc ${CCFLAGS} src/main/c/tagger.cpp 14 | g++ ${LINKFLAGS} target/taggingExample.o 15 | clean: 16 | rm -rf target/libtaggingExample.o target/libtaggingExample.dylib target/libtaggingExample.so 17 | -------------------------------------------------------------------------------- /heapTagging/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | net.jonbell.examples.jvmti 5 | HeapTaggingExample 6 | 0.0.1-SNAPSHOT 7 | jar 8 | 9 | 10 | 11 | org.apache.maven.plugins 12 | maven-jar-plugin 13 | 2.3.1 14 | 15 | 16 | resources/META-INF/MANIFEST.MF 17 | 18 | 19 | 20 | 21 | maven-compiler-plugin 22 | 3.1 23 | 24 | 1.7 25 | 1.7 26 | 27 | 28 | 29 | org.codehaus.mojo 30 | exec-maven-plugin 31 | 1.1 32 | 33 | 34 | exe 35 | compile 36 | 37 | exec 38 | 39 | 40 | make 41 | 42 | 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-failsafe-plugin 48 | 2.18.1 49 | 50 | 51 | 52 | integration-test 53 | verify 54 | 55 | 56 | 57 | 58 | -Xbootclasspath/p:${project.build.directory}/${project.build.finalName}.jar 59 | -agentpath:${project.build.directory}/libtaggingExample.so 60 | -javaagent:${project.build.directory}/${project.build.finalName}.jar 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-shade-plugin 66 | 2.3 67 | 68 | 69 | package 70 | 71 | shade 72 | 73 | 74 | 75 | 76 | org.objectweb.asm 77 | net.jonbell.examples.jvmti.org.objectweb.asm 78 | 79 | 80 | false 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | junit 91 | junit 92 | 4.11 93 | test 94 | 95 | 96 | 97 | org.ow2.asm 98 | asm-all 99 | 5.0.3 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /heapTagging/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: 1.6.0_06 (Sun Microsystems Inc.) 3 | Premain-Class: net.jonbell.examples.jvmti.tagging.inst.PreMain 4 | 5 | -------------------------------------------------------------------------------- /heapTagging/src/main/c/tagger.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "jvmti.h" 6 | #include "jni.h" 7 | #pragma GCC diagnostic ignored "-Wwrite-strings" 8 | 9 | typedef struct { 10 | /* JVMTI Environment */ 11 | jvmtiEnv *jvmti; 12 | JNIEnv * jni; 13 | jboolean vm_is_started; 14 | jboolean vmDead; 15 | 16 | /* Data access Lock */ 17 | jrawMonitorID lock; 18 | JavaVM* jvm; 19 | } GlobalAgentData; 20 | typedef struct DeleteQueue { 21 | jobject obj; 22 | DeleteQueue * next; 23 | } DeleteQueue; 24 | 25 | //Queue of global references that need to be cleaned up 26 | static DeleteQueue * deleteQueue = NULL; 27 | static jrawMonitorID deleteQueueLock; 28 | 29 | static GlobalAgentData *gdata; 30 | 31 | void fatal_error(const char * format, ...) { 32 | va_list ap; 33 | 34 | va_start(ap, format); 35 | (void) vfprintf(stderr, format, ap); 36 | (void) fflush(stderr); 37 | va_end(ap); 38 | exit(3); 39 | } 40 | 41 | static void check_jvmti_error(jvmtiEnv *jvmti, jvmtiError errnum, 42 | const char *str) { 43 | if (errnum != JVMTI_ERROR_NONE) { 44 | char *errnum_str; 45 | 46 | errnum_str = NULL; 47 | (void) jvmti->GetErrorName(errnum, &errnum_str); 48 | 49 | printf("ERROR: JVMTI: %d(%s): %s\n", errnum, 50 | (errnum_str == NULL ? "Unknown" : errnum_str), 51 | (str == NULL ? "" : str)); 52 | } 53 | } 54 | /* Enter a critical section by doing a JVMTI Raw Monitor Enter */ 55 | static void enter_critical_section(jvmtiEnv *jvmti) { 56 | jvmtiError error; 57 | 58 | error = jvmti->RawMonitorEnter(gdata->lock); 59 | check_jvmti_error(jvmti, error, "Cannot enter with raw monitor"); 60 | } 61 | 62 | /* Exit a critical section by doing a JVMTI Raw Monitor Exit */ 63 | static void exit_critical_section(jvmtiEnv *jvmti) { 64 | jvmtiError error; 65 | 66 | error = jvmti->RawMonitorExit(gdata->lock); 67 | check_jvmti_error(jvmti, error, "Cannot exit with raw monitor"); 68 | } 69 | 70 | /* 71 | * Implementation of _setTag JNI function. 72 | */ 73 | JNIEXPORT static void JNICALL setObjExpression(JNIEnv *env, jclass klass, 74 | jobject o, jobject expr) { 75 | if (gdata->vmDead) { 76 | return; 77 | } 78 | if(!o) 79 | { 80 | return; 81 | } 82 | jvmtiError error; 83 | jlong tag; 84 | if (expr) { 85 | //First see if there's already something set here 86 | error =gdata->jvmti->GetTag(o,&tag); 87 | if(tag) 88 | { 89 | //Delete reference to old thing 90 | env->DeleteGlobalRef((jobject)(ptrdiff_t) tag); 91 | } 92 | //Set the tag, make a new global reference to it 93 | error = gdata->jvmti->SetTag(o, (jlong) (ptrdiff_t) (void*) env->NewGlobalRef(expr)); 94 | } else { 95 | error = gdata->jvmti->SetTag(o, 0); 96 | } 97 | if(error == JVMTI_ERROR_WRONG_PHASE) 98 | return; 99 | check_jvmti_error(gdata->jvmti, error, "Cannot set object tag"); 100 | } 101 | /* 102 | * Implementation of _getTag JNI function 103 | */ 104 | JNIEXPORT static jobject JNICALL getObjExpression(JNIEnv *env, jclass klass, 105 | jobject o) { 106 | if (gdata->vmDead) { 107 | return NULL; 108 | } 109 | jvmtiError error; 110 | jlong tag; 111 | error = gdata->jvmti->GetTag(o, &tag); 112 | if(error == JVMTI_ERROR_WRONG_PHASE) 113 | return NULL; 114 | check_jvmti_error(gdata->jvmti, error, "Cannot get object tag"); 115 | if(tag) 116 | { 117 | return (jobject) (ptrdiff_t) tag; 118 | } 119 | return NULL; 120 | } 121 | 122 | /* 123 | * Since we create a global reference to whatever we tag an object with, we need to clean this up 124 | * when the tagged object is garbage collected - otherwise tags wouldn't ever be garbage collected. 125 | * When a tagged object is GC'ed, we add its tag to a deletion queue. We will process the queue at the next GC. 126 | */ 127 | static void JNICALL 128 | cbObjectFree(jvmtiEnv *jvmti_env, jlong tag) { 129 | if (gdata->vmDead) { 130 | return; 131 | } 132 | jvmtiError error; 133 | if (tag) { 134 | error = gdata->jvmti->RawMonitorEnter(deleteQueueLock); 135 | check_jvmti_error(jvmti_env, error, "raw monitor enter"); 136 | DeleteQueue* tmp = deleteQueue; 137 | deleteQueue = new DeleteQueue(); 138 | deleteQueue->next = tmp; 139 | deleteQueue->obj = (jobject) (ptrdiff_t) tag; 140 | error = gdata->jvmti->RawMonitorExit(deleteQueueLock); 141 | check_jvmti_error(jvmti_env, error, "raw monitor exit"); 142 | } 143 | } 144 | static jrawMonitorID gcLock; 145 | static int gc_count; 146 | 147 | /* 148 | * Garbage collection worker thread that will asynchronously free tags 149 | */ 150 | static void JNICALL 151 | gcWorker(jvmtiEnv* jvmti, JNIEnv* jni, void *p) 152 | { 153 | jvmtiError err; 154 | for (;;) { 155 | err = jvmti->RawMonitorEnter(gcLock); 156 | check_jvmti_error(jvmti, err, "raw monitor enter"); 157 | while (gc_count == 0) { 158 | err = jvmti->RawMonitorWait(gcLock, 0); 159 | if (err != JVMTI_ERROR_NONE) { 160 | err = jvmti->RawMonitorExit(gcLock); 161 | check_jvmti_error(jvmti, err, "raw monitor wait"); 162 | return; 163 | } 164 | } 165 | gc_count = 0; 166 | 167 | err = jvmti->RawMonitorExit(gcLock); 168 | check_jvmti_error(jvmti, err, "raw monitor exit"); 169 | 170 | DeleteQueue * tmp; 171 | while(deleteQueue) 172 | { 173 | err = jvmti->RawMonitorEnter(deleteQueueLock); 174 | check_jvmti_error(jvmti, err, "raw monitor enter"); 175 | 176 | tmp = deleteQueue; 177 | deleteQueue = deleteQueue->next; 178 | err = jvmti->RawMonitorExit(deleteQueueLock); 179 | check_jvmti_error(jvmti, err, "raw monitor exit"); 180 | jni->DeleteGlobalRef(tmp->obj); 181 | 182 | free(tmp); 183 | } 184 | } 185 | } 186 | 187 | /* 188 | * Callback to notify us when a GC finishes. When a GC finishes, 189 | * we wake up our GC thread and free all tags that need to be freed. 190 | */ 191 | static void JNICALL 192 | gc_finish(jvmtiEnv* jvmti_env) 193 | { 194 | jvmtiError err; 195 | err = gdata->jvmti->RawMonitorEnter(gcLock); 196 | check_jvmti_error(gdata->jvmti, err, "raw monitor enter"); 197 | gc_count++; 198 | err = gdata->jvmti->RawMonitorNotify(gcLock); 199 | check_jvmti_error(gdata->jvmti, err, "raw monitor notify"); 200 | err = gdata->jvmti->RawMonitorExit(gcLock); 201 | check_jvmti_error(gdata->jvmti, err, "raw monitor exit"); 202 | } 203 | /* 204 | * Create a new java.lang.Thread 205 | */ 206 | static jthread alloc_thread(JNIEnv *env) { 207 | jclass thrClass; 208 | jmethodID cid; 209 | jthread res; 210 | 211 | thrClass = env->FindClass("java/lang/Thread"); 212 | if (thrClass == NULL) { 213 | fatal_error("Cannot find Thread class\n"); 214 | } 215 | cid = env->GetMethodID(thrClass, "", "()V"); 216 | if (cid == NULL) { 217 | fatal_error("Cannot find Thread constructor method\n"); 218 | } 219 | res = env->NewObject(thrClass, cid); 220 | if (res == NULL) { 221 | fatal_error("Cannot create new Thread object\n"); 222 | } 223 | return res; 224 | } 225 | 226 | /* 227 | * Callback we get when the JVM is initialized. We use this time to setup our GC thread 228 | */ 229 | static void JNICALL callbackVMInit(jvmtiEnv * jvmti, JNIEnv * env, jthread thread) 230 | { 231 | jvmtiError err; 232 | 233 | err = jvmti->RunAgentThread(alloc_thread(env), &gcWorker, NULL, 234 | JVMTI_THREAD_MAX_PRIORITY); 235 | check_jvmti_error(jvmti, err, "Unable to run agent cleanup thread"); 236 | } 237 | /* 238 | * Callback we receive when the JVM terminates - no more functions can be called after this 239 | */ 240 | static void JNICALL callbackVMDeath(jvmtiEnv *jvmti_env, JNIEnv* jni_env) { 241 | gdata->vmDead = JNI_TRUE; 242 | } 243 | 244 | /* 245 | * Callback we get when the JVM starts up, but before its initialized. 246 | * Sets up the JNI calls. 247 | */ 248 | static void JNICALL cbVMStart(jvmtiEnv *jvmti, JNIEnv *env) { 249 | 250 | enter_critical_section(jvmti); 251 | { 252 | jclass klass; 253 | jfieldID field; 254 | jint rc; 255 | 256 | static JNINativeMethod registry[2] = { {"_setTag", 257 | "(Ljava/lang/Object;Ljava/lang/Object;)V", 258 | (void*) &setObjExpression}, {"_getTag", 259 | "(Ljava/lang/Object;)Ljava/lang/Object;", 260 | (void*) &getObjExpression}}; 261 | /* Register Natives for class whose methods we use */ 262 | klass = env->FindClass("net/jonbell/examples/jvmti/tagging/runtime/Tagger"); 263 | if (klass == NULL) { 264 | fatal_error( 265 | "ERROR: JNI: Cannot find Tagger with FindClass\n"); 266 | } 267 | rc = env->RegisterNatives(klass, registry, 2); 268 | if (rc != 0) { 269 | fatal_error( 270 | "ERROR: JNI: Cannot register natives for Tagger\n"); 271 | } 272 | /* Engage calls. */ 273 | field = env->GetStaticFieldID(klass, "engaged", "I"); 274 | if (field == NULL) { 275 | fatal_error("ERROR: JNI: Cannot get field\n" 276 | ); 277 | } 278 | env->SetStaticIntField(klass, field, 1); 279 | 280 | /* Indicate VM has started */ 281 | gdata->vm_is_started = JNI_TRUE; 282 | 283 | } 284 | exit_critical_section(jvmti); 285 | } 286 | 287 | /* 288 | * Callback that is notified when our agent is loaded. Registers for event 289 | * notifications. 290 | */ 291 | JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, 292 | void *reserved) { 293 | static GlobalAgentData data; 294 | jvmtiError error; 295 | jint res; 296 | jvmtiEventCallbacks callbacks; 297 | jvmtiEnv *jvmti = NULL; 298 | jvmtiCapabilities capa; 299 | 300 | (void) memset((void*) &data, 0, sizeof(data)); 301 | gdata = &data; 302 | gdata->jvm = jvm; 303 | res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_0); 304 | 305 | if (res != JNI_OK || jvmti == NULL) { 306 | /* This means that the VM was unable to obtain this version of the 307 | * JVMTI interface, this is a fatal error. 308 | */ 309 | printf("ERROR: Unable to access JVMTI Version 1 (0x%x)," 310 | " is your J2SE a 1.5 or newer version?" 311 | " JNIEnv's GetEnv() returned %d\n", JVMTI_VERSION_1, res); 312 | 313 | } 314 | //save jvmti for later 315 | gdata->jvmti = jvmti; 316 | 317 | //Register our capabilities 318 | (void) memset(&capa, 0, sizeof(jvmtiCapabilities)); 319 | capa.can_signal_thread = 1; 320 | capa.can_generate_object_free_events = 1; 321 | capa.can_tag_objects = 1; 322 | capa.can_generate_garbage_collection_events = 1; 323 | 324 | error = jvmti->AddCapabilities(&capa); 325 | check_jvmti_error(jvmti, error, 326 | "Unable to get necessary JVMTI capabilities."); 327 | 328 | //Register callbacks 329 | (void) memset(&callbacks, 0, sizeof(callbacks)); 330 | callbacks.VMInit = &callbackVMInit; 331 | callbacks.VMDeath = &callbackVMDeath; 332 | callbacks.VMStart = &cbVMStart; 333 | callbacks.ObjectFree = &cbObjectFree; 334 | callbacks.GarbageCollectionFinish = &gc_finish; 335 | 336 | error = jvmti->SetEventCallbacks(&callbacks, (jint) sizeof(callbacks)); 337 | check_jvmti_error(jvmti, error, "Cannot set jvmti callbacks"); 338 | 339 | //Register for events 340 | error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_START, 341 | (jthread) NULL); 342 | check_jvmti_error(jvmti, error, "Cannot set event notification"); 343 | error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, 344 | JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, (jthread) NULL); 345 | check_jvmti_error(jvmti, error, "Cannot set event notification"); 346 | error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, 347 | (jthread) NULL); 348 | check_jvmti_error(jvmti, error, "Cannot set event notification"); 349 | error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, 350 | (jthread) NULL); 351 | check_jvmti_error(jvmti, error, "Cannot set event notification"); 352 | error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, 353 | JVMTI_EVENT_OBJECT_FREE, (jthread) NULL); 354 | check_jvmti_error(jvmti, error, "Cannot set event notification"); 355 | 356 | 357 | //Set up a few locks 358 | error = jvmti->CreateRawMonitor("agent data", &(gdata->lock)); 359 | check_jvmti_error(jvmti, error, "Cannot create raw monitor"); 360 | 361 | error = jvmti->CreateRawMonitor("agent gc lock", &(gcLock)); 362 | check_jvmti_error(jvmti, error, "Cannot create raw monitor"); 363 | 364 | error = jvmti->CreateRawMonitor("agent gc queue", &(deleteQueueLock)); 365 | check_jvmti_error(jvmti, error, "Cannot create raw monitor"); 366 | 367 | return JNI_OK; 368 | } 369 | -------------------------------------------------------------------------------- /heapTagging/src/main/java/net/jonbell/examples/jvmti/tagging/inst/MetadataTagAddingCV.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.jvmti.tagging.inst; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.objectweb.asm.ClassVisitor; 6 | import org.objectweb.asm.MethodVisitor; 7 | import org.objectweb.asm.Opcodes; 8 | 9 | public class MetadataTagAddingCV extends ClassVisitor { 10 | 11 | static String METADATA_FIELD_NAME = "jonbellmetadatafield"; 12 | 13 | public MetadataTagAddingCV(ClassVisitor cv) { 14 | super(Opcodes.ASM5, cv); 15 | } 16 | 17 | boolean addField = false; 18 | boolean isClass; 19 | String className; 20 | 21 | @Override 22 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 23 | isClass = (access & Opcodes.ACC_ENUM) == 0 && (access & Opcodes.ACC_INTERFACE) == 0; 24 | addField = isClass && PreMain.isIgnoredClass(superName); 25 | className = name; 26 | 27 | if (isClass) { 28 | //Add the interface declaration 29 | String[] newInterfaces = new String[interfaces.length + 1]; 30 | System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length); 31 | newInterfaces[interfaces.length] = "net/jonbell/examples/jvmti/tagging/runtime/Tagged"; 32 | interfaces = newInterfaces; 33 | } 34 | super.visit(version, access, name, signature, superName, interfaces); 35 | } 36 | 37 | @Override 38 | public void visitEnd() { 39 | if (isClass) { 40 | //Add method to retrieve and set tag 41 | MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, "getMetadataTag", "()Ljava/lang/Object;", null, null); 42 | mv.visitCode(); 43 | mv.visitVarInsn(Opcodes.ALOAD, 0); 44 | mv.visitFieldInsn(Opcodes.GETFIELD, className, METADATA_FIELD_NAME, "Ljava/lang/Object;"); 45 | mv.visitInsn(Opcodes.ARETURN); 46 | mv.visitMaxs(0, 0); 47 | mv.visitEnd(); 48 | 49 | mv = super.visitMethod(Opcodes.ACC_PUBLIC, "setMetadataTag", "(Ljava/lang/Object;)V", null, null); 50 | mv.visitCode(); 51 | mv.visitVarInsn(Opcodes.ALOAD, 0); 52 | mv.visitVarInsn(Opcodes.ALOAD, 1); 53 | mv.visitFieldInsn(Opcodes.PUTFIELD, className, METADATA_FIELD_NAME, "Ljava/lang/Object;"); 54 | mv.visitInsn(Opcodes.RETURN); 55 | mv.visitMaxs(0, 0); 56 | mv.visitEnd(); 57 | 58 | if (addField) { 59 | //Add the field itself 60 | super.visitField(Opcodes.ACC_PUBLIC, METADATA_FIELD_NAME, "Ljava/lang/Object;", null, null); 61 | } 62 | } 63 | super.visitEnd(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /heapTagging/src/main/java/net/jonbell/examples/jvmti/tagging/inst/PreMain.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.jvmti.tagging.inst; 2 | 3 | import java.lang.instrument.ClassFileTransformer; 4 | import java.lang.instrument.IllegalClassFormatException; 5 | import java.lang.instrument.Instrumentation; 6 | import java.security.ProtectionDomain; 7 | 8 | import org.objectweb.asm.ClassReader; 9 | import org.objectweb.asm.ClassWriter; 10 | 11 | public class PreMain { 12 | public static void premain(String args, Instrumentation inst) { 13 | inst.addTransformer(new ClassFileTransformer() { 14 | @Override 15 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 16 | if(isIgnoredClass(className)) 17 | return classfileBuffer; 18 | try { 19 | ClassReader cr = new ClassReader(classfileBuffer); 20 | 21 | ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); 22 | cr.accept(new MetadataTagAddingCV(cw), 0); 23 | return cw.toByteArray(); 24 | } catch (Throwable t) { 25 | //Make sure that an exception in instrumentation gets printed, rather than squelched 26 | t.printStackTrace(); 27 | return null; 28 | } 29 | } 30 | }); 31 | } 32 | 33 | public static boolean isIgnoredClass(String owner) { 34 | //For one reason or another, we can't muck with these classes with ease. 35 | //Instances of these classes that you want to be tagged will be tagged with JVMTI 36 | return owner.startsWith("java/lang/Object") || owner.startsWith("java/lang/Number") || owner.startsWith("java/lang/Comparable") || owner.startsWith("java/lang/ref/SoftReference") 37 | || owner.startsWith("java/lang/ref/Reference") || owner.startsWith("java/lang/ref/FinalizerReference") || owner.startsWith("java/lang/Boolean") 38 | || owner.startsWith("java/lang/Character") || owner.startsWith("java/lang/Float") || owner.startsWith("java/lang/Byte") || owner.startsWith("java/lang/Short") 39 | || owner.startsWith("java/lang/Integer") || owner.startsWith("java/lang/StackTraceElement") || (owner.startsWith("edu/columbia/cs/psl/testdepends")) 40 | || owner.startsWith("sun/awt/image/codec/") 41 | || (owner.startsWith("sun/reflect/Reflection")) 42 | || owner.equals("java/lang/reflect/Proxy") 43 | || owner.startsWith("sun/reflection/annotation/AnnotationParser") 44 | || owner.startsWith("sun/reflect/MethodAccessor") 45 | || owner.startsWith("sun/reflect/ConstructorAccessor") 46 | || owner.startsWith("sun/reflect/SerializationConstructorAccessor") 47 | || owner.startsWith("sun/reflect/GeneratedMethodAccessor") || owner.startsWith("sun/reflect/GeneratedConstructorAccessor") 48 | || owner.startsWith("sun/reflect/GeneratedSerializationConstructor"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /heapTagging/src/main/java/net/jonbell/examples/jvmti/tagging/runtime/Tagged.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.jvmti.tagging.runtime; 2 | 3 | /** 4 | * Interface applied to all instrumented classes. 5 | * Implementation is automatically generated by java agent at load time 6 | */ 7 | public interface Tagged { 8 | public Object getMetadataTag(); 9 | public void setMetadataTag(Object tag); 10 | } -------------------------------------------------------------------------------- /heapTagging/src/main/java/net/jonbell/examples/jvmti/tagging/runtime/Tagger.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.jvmti.tagging.runtime; 2 | 3 | public class Tagger { 4 | /** 5 | * Flag set by the JVMTI agent to indicate that it was successfully loaded 6 | */ 7 | public static int engaged = 0; 8 | 9 | private static native Object _getTag(Object obj); 10 | 11 | private static native void _setTag(Object obj, Object t); 12 | 13 | /** 14 | * Get the tag currently assigned to an object. If the reference is to an 15 | * object, and its class has been instrumented, then we do this entirely 16 | * within the JVM. If the reference is to an array, or an instance of a 17 | * class that was NOT instrumented, then we use JNI/JVMTI and make a native 18 | * call. 19 | * 20 | * @param obj 21 | * @return 22 | */ 23 | public static Object getTag(Object obj) { 24 | if (obj instanceof Tagged) 25 | return ((Tagged) obj).getMetadataTag(); 26 | if (engaged == 0) 27 | throw new IllegalStateException("Attempting to use JVMTI features, but native agent not loaded"); 28 | if (obj == null) 29 | return null; 30 | return _getTag(obj); 31 | } 32 | 33 | /** 34 | * Set the tag on an object. If the reference is to an object, and its class 35 | * has been instrumented, then we do this entirely within the JVM. If the 36 | * reference is to an array, or an instance of a class that was NOT 37 | * instrumented, then we use JNI/JVMTI and make a native call. 38 | * 39 | * @param obj 40 | * @param t 41 | */ 42 | public static void setTag(Object obj, Object t) { 43 | if (obj instanceof Tagged) { 44 | ((Tagged) obj).setMetadataTag(t); 45 | return; 46 | } 47 | if (engaged == 0) 48 | throw new IllegalStateException("Attempting to use JVMTI features, but native agent not loaded"); 49 | if (obj == null) 50 | return; 51 | _setTag(obj, t); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /heapTagging/src/test/java/net/jonbell/examples/jvmti/tagging/TaggingITCase.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.jvmti.tagging; 2 | 3 | import static org.junit.Assert.*; 4 | import net.jonbell.examples.jvmti.tagging.runtime.Tagged; 5 | import net.jonbell.examples.jvmti.tagging.runtime.Tagger; 6 | 7 | import org.junit.Test; 8 | 9 | public class TaggingITCase { 10 | @Test 11 | public void testObject() throws Exception { 12 | Object o = new Object(); 13 | Object tag = "foo"; 14 | Tagger.setTag(o, tag); 15 | assertEquals(tag, Tagger.getTag(o)); 16 | } 17 | 18 | @Test 19 | public void testInstrumented() throws Exception { 20 | InnerClass ic = new InnerClass(); 21 | assertTrue(ic instanceof Tagged); 22 | Object tag = "foobar"; 23 | Tagger.setTag(ic, tag); 24 | assertEquals(tag, Tagger.getTag(ic)); 25 | } 26 | 27 | static class InnerClass { 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /heapWalking/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .project 3 | .classpath 4 | .settings 5 | dependency-reduced-pom.xml 6 | -------------------------------------------------------------------------------- /heapWalking/Makefile: -------------------------------------------------------------------------------- 1 | UNAME := $(shell uname) 2 | CCFLAGS = -o target/walkingExample.o -I${JAVA_HOME}/include -c -fPIC -fpermissive 3 | ifeq ($(UNAME), Linux) 4 | CCFLAGS += -I${JAVA_HOME}/include/linux 5 | LINKFLAGS = -z defs -static-libgcc -shared -o target/libwalkingExample.so -lc 6 | endif 7 | ifeq ($(UNAME), Darwin) 8 | CCFLAGS += -I${JAVA_HOME}/include/darwin 9 | LINKFLAGS += -dynamiclib -o target/libwalkingExample.so 10 | endif 11 | 12 | libtaggingExample.dylib: 13 | gcc ${CCFLAGS} src/main/c/walker.cpp 14 | g++ ${LINKFLAGS} target/walkingExample.o 15 | clean: 16 | rm -rf target/libwalkingExample.o target/libwalkingExample.dylib target/libwalkingExample.so 17 | -------------------------------------------------------------------------------- /heapWalking/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | net.jonbell.examples.jvmti 5 | HeapWalkingExample 6 | 0.0.1-SNAPSHOT 7 | jar 8 | 9 | 10 | 11 | maven-compiler-plugin 12 | 3.1 13 | 14 | 1.7 15 | 1.7 16 | 17 | 18 | 19 | org.codehaus.mojo 20 | exec-maven-plugin 21 | 1.1 22 | 23 | 24 | exe 25 | compile 26 | 27 | exec 28 | 29 | 30 | make 31 | 32 | 33 | 34 | 35 | 36 | org.apache.maven.plugins 37 | maven-failsafe-plugin 38 | 2.18.1 39 | 40 | 41 | 42 | integration-test 43 | verify 44 | 45 | 46 | 47 | 48 | -Xbootclasspath/p:${project.build.directory}/${project.build.finalName}.jar 49 | -agentpath:${project.build.directory}/libwalkingExample.so 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | junit 58 | junit 59 | 4.11 60 | test 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /heapWalking/src/main/c/walker.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "jvmti.h" 6 | #include "jni.h" 7 | #pragma GCC diagnostic ignored "-Wwrite-strings" 8 | #pragma GCC diagnostic ignored "-Wmissing-declarations" 9 | 10 | typedef struct { 11 | /* JVMTI Environment */ 12 | jvmtiEnv *jvmti; 13 | JNIEnv * jni; 14 | jboolean vm_is_started; 15 | jboolean vmDead; 16 | 17 | /* Data access Lock */ 18 | jrawMonitorID lock; 19 | JavaVM* jvm; 20 | } GlobalAgentData; 21 | 22 | struct Field; 23 | /** 24 | * We will need to cache information about each java class to be able to reference back to their fields later 25 | */ 26 | typedef struct Clazz { 27 | char *name; 28 | jclass clazz; 29 | Field **fields; 30 | int nFields; 31 | bool fieldsCalculated; 32 | Clazz * super; 33 | Clazz ** intfcs; 34 | int nIntfc; 35 | int fieldOffsetFromParent; 36 | int fieldOffsetFromInterfaces; 37 | bool enqueued; 38 | } Clazz; 39 | typedef struct Field { 40 | Clazz *clazz; 41 | char *name; 42 | jfieldID fieldID; 43 | }; 44 | typedef struct FieldList { 45 | Field * car; 46 | struct FieldList * cdr; 47 | } FieldList; 48 | struct Tag; 49 | typedef struct PointedToList { 50 | Tag * car; 51 | struct PointedToList * cdr; 52 | }; 53 | /** 54 | * The datastructure that we will associate each object with 55 | */ 56 | typedef struct Tag { 57 | FieldList * directFieldList; 58 | PointedToList * pointedTo; 59 | Clazz * clazz; 60 | bool visited; 61 | }; 62 | typedef struct ClassList { 63 | Clazz * car; 64 | struct ClassList *cdr; 65 | } ClassList; 66 | static Clazz ** classCache; 67 | 68 | static int sizeOfClassCache; 69 | static GlobalAgentData *gdata; 70 | 71 | void fatal_error(const char * format, ...) { 72 | va_list ap; 73 | 74 | va_start(ap, format); 75 | (void) vfprintf(stderr, format, ap); 76 | (void) fflush(stderr); 77 | va_end(ap); 78 | exit(3); 79 | } 80 | 81 | static void check_jvmti_error(jvmtiEnv *jvmti, jvmtiError errnum, 82 | const char *str) { 83 | if (errnum != JVMTI_ERROR_NONE) { 84 | char *errnum_str; 85 | 86 | errnum_str = NULL; 87 | (void) jvmti->GetErrorName(errnum, &errnum_str); 88 | 89 | printf("ERROR: JVMTI: %d(%s): %s\n", errnum, 90 | (errnum_str == NULL ? "Unknown" : errnum_str), 91 | (str == NULL ? "" : str)); 92 | } 93 | } 94 | /* Enter a critical section by doing a JVMTI Raw Monitor Enter */ 95 | static void enter_critical_section(jvmtiEnv *jvmti) { 96 | jvmtiError error; 97 | 98 | error = jvmti->RawMonitorEnter(gdata->lock); 99 | check_jvmti_error(jvmti, error, "Cannot enter with raw monitor"); 100 | } 101 | 102 | /* Exit a critical section by doing a JVMTI Raw Monitor Exit */ 103 | static void exit_critical_section(jvmtiEnv *jvmti) { 104 | jvmtiError error; 105 | 106 | error = jvmti->RawMonitorExit(gdata->lock); 107 | check_jvmti_error(jvmti, error, "Cannot exit with raw monitor"); 108 | } 109 | /** 110 | * Compute the offset of a class' fields from its super class, 111 | * following the spec here: http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#jvmtiHeapReferenceInfoField 112 | */ 113 | static int computeFieldOffsetsFromParent(Clazz * c) { 114 | if (!c->super) { 115 | if (c->fieldOffsetFromParent < 0) 116 | c->fieldOffsetFromParent = 0; 117 | return c->fieldOffsetFromParent; 118 | } 119 | if (c->fieldOffsetFromParent < 0) { 120 | c->fieldOffsetFromParent = computeFieldOffsetsFromParent(c->super); 121 | return c->fieldOffsetFromParent + c->nFields; 122 | } 123 | return c->fieldOffsetFromParent + c->nFields; 124 | } 125 | /** 126 | * Collect all of the interfaces that a class implements (including super interfaces, parent classes, etc) 127 | * Needed to calculate field offsets. 128 | */ 129 | static void collectAllInterfaces(Clazz *c, ClassList *lst) { 130 | int i; 131 | for (i = 0; i < c->nIntfc; i++) { 132 | if (c->intfcs[i] && !c->intfcs[i]->enqueued) { 133 | c->intfcs[i]->enqueued = true; 134 | lst->cdr = new ClassList(); 135 | lst->cdr->car = c->intfcs[i]; 136 | lst->cdr->cdr = NULL; 137 | collectAllInterfaces(c->intfcs[i], lst); 138 | } 139 | } 140 | if (c->super) 141 | collectAllInterfaces(c->super, lst); 142 | } 143 | /** 144 | * Compute the offset of a class' fields from its parent interfaces, 145 | * following the spec here: http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#jvmtiHeapReferenceInfoField 146 | */ 147 | static void computeFieldOffsetsFromInterfaces(Clazz * c) { 148 | if (c->fieldOffsetFromInterfaces < 0) { 149 | //Collect all interfaces 150 | ClassList lst; 151 | lst.cdr = NULL; 152 | collectAllInterfaces(c, &lst); 153 | ClassList * p; 154 | p = lst.cdr; 155 | c->fieldOffsetFromInterfaces = 0; 156 | while (p && p->car) { 157 | c->fieldOffsetFromInterfaces += p->car->nFields; 158 | p->car->enqueued = false; 159 | p = p->cdr; 160 | } 161 | } 162 | } 163 | /** 164 | * Given a field index (as returned as a jvmtiHeapReferenceInfoField), find the actual field that it represents 165 | * See http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#jvmtiHeapReferenceInfoField 166 | */ 167 | static Field* getField(Clazz * c, int rawIdx) { 168 | if (c->fieldOffsetFromInterfaces < 0) 169 | computeFieldOffsetsFromParent(c); 170 | if (c->fieldOffsetFromInterfaces < 0) 171 | computeFieldOffsetsFromInterfaces(c); 172 | int idx = rawIdx - c->fieldOffsetFromInterfaces; 173 | if (idx >= c->fieldOffsetFromParent) 174 | idx = idx - c->fieldOffsetFromParent; 175 | else 176 | return getField(c->super, rawIdx); 177 | if (idx < 0 || idx >= c->nFields) 178 | return NULL; 179 | return c->fields[idx]; 180 | } 181 | /** 182 | * Free the tag structure that we associate with objects. 183 | * Typically will just delete pointer information, unless freeFully is specified, in which case 184 | * the tag will be freed completely. 185 | */ 186 | static void freeTag(Tag *tag, bool freeFully) { 187 | if (tag) { 188 | tag->visited = 0; 189 | if (tag->clazz && freeFully) { 190 | Clazz * c = tag->clazz; 191 | if (c->name) 192 | gdata->jvmti->Deallocate((unsigned char*) (void*) c->name); 193 | if (c->fields) { 194 | int i; 195 | for (i = 0; i < c->nFields; i++) { 196 | if (c->fields[i]->name) { 197 | gdata->jvmti->Deallocate( 198 | (unsigned char*) (void*) c->fields[i]->name); 199 | } 200 | delete (c->fields[i]); 201 | } 202 | delete (c->fields); 203 | } 204 | if (c->intfcs) 205 | delete (c->intfcs); 206 | } 207 | if (tag->directFieldList) { 208 | FieldList *t; 209 | while (tag->directFieldList) { 210 | t = tag->directFieldList->cdr; 211 | free(tag->directFieldList); 212 | tag->directFieldList = t; 213 | } 214 | } 215 | if (tag->pointedTo) { 216 | PointedToList *t; 217 | while (tag->pointedTo) { 218 | t = tag->pointedTo->cdr; 219 | free(tag->pointedTo); 220 | tag->pointedTo = t; 221 | } 222 | } 223 | if (freeFully) 224 | free(tag); 225 | } 226 | } 227 | /** 228 | * Callback for when an object is freed - we need to delete our tag on the object too. 229 | * Note that because our tag is just malloc'ed (and not a java object), we can trivially free 230 | * it directly within this callback. 231 | */ 232 | static void JNICALL 233 | cbObjectFree(jvmtiEnv *jvmti_env, jlong tag) { 234 | if (gdata->vmDead) { 235 | return; 236 | } 237 | jvmtiError error; 238 | if (tag) { 239 | freeTag((Tag*) tag, true); 240 | } 241 | } 242 | /** 243 | * Update our cache of all of the loaded classes and their fields. We will need this information 244 | * to understand field relationships, which require calculating super classes, super interfaces, etc. 245 | */ 246 | static void updateClassCache(JNIEnv *env) { 247 | jvmtiError err; 248 | jint nClasses; 249 | jclass* classes; 250 | 251 | err = gdata->jvmti->GetLoadedClasses(&nClasses, &classes); 252 | check_jvmti_error(gdata->jvmti, err, "Cannot get classes"); 253 | if (classCache != NULL) { 254 | free(classCache); 255 | } 256 | classCache = new Clazz*[nClasses]; 257 | memset(classCache, 0, sizeof(Clazz*) * nClasses); 258 | sizeOfClassCache = nClasses; 259 | 260 | Clazz *c; 261 | int i; 262 | jlong tag; 263 | jint status; 264 | Tag *t; 265 | for (i = 0; i < nClasses; i++) { 266 | err = gdata->jvmti->GetTag(classes[i], &tag); 267 | check_jvmti_error(gdata->jvmti, err, "Unable to get class tag"); 268 | if (tag) //We already built this class info object - and the class info object is in the tag of the class object 269 | { 270 | classCache[i] = ((Tag*) tag)->clazz; 271 | continue; 272 | } 273 | err = gdata->jvmti->GetClassStatus(classes[i], &status); 274 | check_jvmti_error(gdata->jvmti, err, "Cannot get class status"); 275 | if ((status & JVMTI_CLASS_STATUS_PREPARED) == 0) { 276 | classCache[i] = NULL; 277 | continue; 278 | } 279 | c = new Clazz(); 280 | t = new Tag(); 281 | t->visited = false; 282 | t->clazz = c; 283 | t->directFieldList = NULL; 284 | t->pointedTo = NULL; 285 | c->fieldOffsetFromParent = -1; 286 | c->fieldOffsetFromInterfaces = -1; 287 | c->fieldsCalculated = false; 288 | c->clazz = (jclass) env->NewGlobalRef(classes[i]); 289 | gdata->jvmti->GetClassSignature(classes[i], &c->name, NULL); 290 | classCache[i] = c; 291 | err = gdata->jvmti->SetTag(classes[i], (ptrdiff_t) (void*) t); 292 | check_jvmti_error(gdata->jvmti, err, "Cannot set class tag"); 293 | } 294 | jclass *intfcs; 295 | jfieldID *fields; 296 | int j; 297 | jclass super; 298 | //Now that we've built info on each class, we will make sure that for each one, 299 | //we also have pointers to its super class, interfaces, etc. 300 | for (i = 0; i < nClasses; i++) { 301 | if (classCache[i] && !classCache[i]->fieldsCalculated) { 302 | c = classCache[i]; 303 | 304 | super = env->GetSuperclass(classes[i]); 305 | if (super) { 306 | err = gdata->jvmti->GetTag(super, &tag); 307 | check_jvmti_error(gdata->jvmti, err, "Cannot get super class"); 308 | if (tag) 309 | c->super = ((Tag*) (ptrdiff_t) tag)->clazz; 310 | } 311 | 312 | //Get the fields 313 | err = gdata->jvmti->GetClassFields(c->clazz, &(c->nFields), 314 | &fields); 315 | check_jvmti_error(gdata->jvmti, err, "Cannot get class fields"); 316 | c->fields = new Field*[c->nFields]; 317 | for (j = 0; j < c->nFields; j++) { 318 | c->fields[j] = new Field(); 319 | c->fields[j]->clazz = c; 320 | c->fields[j]->fieldID = fields[j]; 321 | err = gdata->jvmti->GetFieldName(c->clazz, fields[j], 322 | &(c->fields[j]->name), NULL, NULL); 323 | check_jvmti_error(gdata->jvmti, err, "Can't get field name"); 324 | 325 | } 326 | gdata->jvmti->Deallocate((unsigned char *) (void*) fields); 327 | 328 | //Get the interfaces 329 | err = gdata->jvmti->GetImplementedInterfaces(classes[i], 330 | &(c->nIntfc), &intfcs); 331 | check_jvmti_error(gdata->jvmti, err, "Cannot get interface info"); 332 | c->intfcs = new Clazz*[c->nIntfc]; 333 | for (j = 0; j < c->nIntfc; j++) { 334 | err = gdata->jvmti->GetTag(intfcs[j], &tag); 335 | check_jvmti_error(gdata->jvmti, err, 336 | "Cannot get interface info"); 337 | if (tag) { 338 | c->intfcs[j] = ((Tag*) tag)->clazz; 339 | } else 340 | c->intfcs[j] = NULL; 341 | } 342 | gdata->jvmti->Deallocate((unsigned char*) (void*) intfcs); 343 | classCache[i]->fieldsCalculated = 1; 344 | } 345 | } 346 | } 347 | /** 348 | * Callback for heap reference following. Propogates points-to through tags. 349 | * For objects that are directly pointed to by a static field, it will also 350 | * note which static fields point to them. 351 | */ 352 | JNIEXPORT static int cbHeapReference(jvmtiHeapReferenceKind reference_kind, 353 | const jvmtiHeapReferenceInfo* reference_info, jlong class_tag, 354 | jlong referrer_class_tag, jlong size, jlong* tag_ptr, 355 | jlong* referrer_tag_ptr, jint length, void* user_data) { 356 | jvmtiError err; 357 | if (reference_kind == JVMTI_HEAP_REFERENCE_CONSTANT_POOL) 358 | return 0; 359 | if (reference_kind != JVMTI_HEAP_REFERENCE_FIELD 360 | && reference_kind != JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT 361 | && reference_kind != JVMTI_HEAP_REFERENCE_STATIC_FIELD) { 362 | //We won't bother propogating pointers along other kinds of references 363 | //(e.g. from a class to its classloader - see http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#jvmtiHeapReferenceKind ) 364 | return JVMTI_VISIT_OBJECTS; 365 | } 366 | 367 | if (reference_kind == JVMTI_HEAP_REFERENCE_STATIC_FIELD) { 368 | //We are visiting a static field directly. 369 | if (referrer_tag_ptr > 0) { 370 | Clazz* c = ((Tag*) (*referrer_tag_ptr))->clazz; 371 | Field* f = getField(c, reference_info->field.index); 372 | if (f) { 373 | if (*tag_ptr == 0) { 374 | FieldList * fl = new FieldList(); 375 | fl->car = f; 376 | fl->cdr = NULL; 377 | Tag * t = new Tag(); 378 | t->visited = false; 379 | t->clazz = NULL; 380 | t->directFieldList = fl; 381 | t->pointedTo = NULL; 382 | *tag_ptr = (ptrdiff_t) (void*) t; 383 | } 384 | if (*tag_ptr) { 385 | //Something is already pointing to this object 386 | FieldList * fl = ((Tag*) *tag_ptr)->directFieldList; 387 | if (!fl) { 388 | fl = new FieldList(); 389 | fl->car = f; 390 | fl->cdr = NULL; 391 | ((Tag*) *tag_ptr)->directFieldList = fl; 392 | return JVMTI_VISIT_OBJECTS; 393 | } 394 | FieldList *p = fl; 395 | 396 | while (fl && fl->car) { 397 | if (fl->car == f) { 398 | return JVMTI_VISIT_OBJECTS; 399 | } 400 | if (!fl->cdr) 401 | break; 402 | fl = fl->cdr; 403 | } 404 | 405 | fl->cdr = new FieldList(); 406 | fl->cdr->car = f; 407 | fl->cdr->cdr = NULL; 408 | } 409 | } 410 | } 411 | } 412 | 413 | //Not directly pointed to by an SF. 414 | if (*referrer_tag_ptr) { 415 | if (!*tag_ptr) { 416 | Tag * t = new Tag(); 417 | t->visited = false; 418 | t->clazz = NULL; 419 | t->directFieldList = NULL; 420 | t->pointedTo = NULL; 421 | *tag_ptr = (ptrdiff_t) (void*) t; 422 | } 423 | PointedToList * t = new PointedToList(); 424 | t->car = (Tag*) *referrer_tag_ptr; 425 | t->cdr = ((Tag*) *tag_ptr)->pointedTo; 426 | ((Tag*) *tag_ptr)->pointedTo = t; 427 | } 428 | 429 | return JVMTI_VISIT_OBJECTS; 430 | } 431 | /** 432 | * Append all fields in the "toAppendList" to the "appendTo" list, making 433 | * sure not to add duplicates 434 | */ 435 | static void appendFields(FieldList * toAppend, FieldList ** appendTo) { 436 | FieldList * p; 437 | bool found; 438 | while (toAppend && toAppend->car) { 439 | p = *appendTo; 440 | found = false; 441 | if (!p) { 442 | *appendTo = new FieldList(); 443 | (*appendTo)->car = toAppend->car; 444 | (*appendTo)->cdr = NULL; 445 | found = true; 446 | } else 447 | while (p && p->car) { 448 | if (p->car == toAppend->car) { 449 | found = true; 450 | break; 451 | } 452 | if (p->cdr == NULL) 453 | break; 454 | p = p->cdr; 455 | } 456 | if (!found) { 457 | p->cdr = new FieldList(); 458 | p->cdr->car = toAppend->car; 459 | p->cdr->cdr = NULL; 460 | } 461 | toAppend = toAppend->cdr; 462 | } 463 | } 464 | /** 465 | * For a given object represented by tag t, make its list of static field 466 | * roots complete, by recursively appending all of the static fields that 467 | * point to things that point to this. 468 | */ 469 | static FieldList* visitPointsTo(Tag * t) { 470 | if (t->visited) 471 | return t->directFieldList; 472 | PointedToList * p = t->pointedTo; 473 | t->visited = true; 474 | while (p && p->car) { 475 | appendFields(visitPointsTo(p->car), &t->directFieldList); 476 | p = p->cdr; 477 | } 478 | return t->directFieldList; 479 | } 480 | /* 481 | * Implementation of _getObjRoots JNI function. 482 | * Uses visitPointsTo, then simply builds a java array of the result to return. 483 | */ 484 | JNIEXPORT static jobjectArray JNICALL getObjRoots(JNIEnv *env, jclass klass, jobject o) { 485 | if (gdata->vmDead) { 486 | return NULL; 487 | } 488 | if (!o) { 489 | return NULL; 490 | } 491 | jvmtiError error; 492 | jlong tag; 493 | 494 | error = gdata->jvmti->GetTag(o, &tag); 495 | check_jvmti_error(gdata->jvmti, error, "Cannot get object tag"); 496 | visitPointsTo((Tag *) tag); 497 | 498 | FieldList * fl = ((Tag *) tag)->directFieldList; 499 | int nObjs = 0; 500 | FieldList * t = fl; 501 | while (t && t->car) { 502 | nObjs++; 503 | t = t->cdr; 504 | } 505 | jclass fieldClass = env->FindClass("java/lang/reflect/Field"); 506 | if(fieldClass == NULL) 507 | fatal_error("Unable to find 'field' class!"); 508 | jobjectArray ret = (jobjectArray) env->NewObjectArray(nObjs, 509 | fieldClass, NULL); 510 | jobject _o; 511 | int i = 0; 512 | while (fl && fl->car) { 513 | _o = env->ToReflectedField(fl->car->clazz->clazz,fl->car->fieldID,true); 514 | // env->NewObject(gdata->staticFieldClass, 515 | // gdata->staticFieldConstructor, fl->car->clazz->clazz, 516 | // env->NewStringUTF(fl->car->name)); 517 | env->SetObjectArrayElement(ret, i, _o); 518 | fl = fl->cdr; 519 | i++; 520 | } 521 | return ret; 522 | } 523 | /** 524 | * Callback that we use to make sure that points-to data gets cleared up before 525 | * we begin building it again. Needed so that we can invoke crawlHeap() multiple times 526 | * in different JVM states and get different results :) 527 | */ 528 | jvmtiIterationControl cbHeapCleanup(jvmtiObjectReferenceKind reference_kind, 529 | jlong class_tag, jlong size, jlong* tag_ptr, jlong referrer_tag, 530 | jint referrer_index, void* user_data) { 531 | if (*tag_ptr) { 532 | Tag *t = (Tag*) *tag_ptr; 533 | freeTag(t, false); 534 | } 535 | return JVMTI_ITERATION_CONTINUE; 536 | } 537 | /** 538 | * Implementation of JNI _crawlHeap function. First, it makes sure that we know about 539 | * all of the currently loaded classes. 540 | * Then, it clears any existing points-to data cached. 541 | * Finally, it will follow references through the heap and build a reverse points-to graph 542 | */ 543 | JNIEXPORT static void JNICALL crawlHeap(JNIEnv *env, jclass klass) { 544 | if (gdata->vmDead) { 545 | return; 546 | } 547 | updateClassCache(env); 548 | gdata->jvmti->IterateOverReachableObjects(NULL, NULL, &cbHeapCleanup, 549 | NULL); 550 | 551 | jvmtiHeapCallbacks * cb = new jvmtiHeapCallbacks(); 552 | memset(cb, 0, sizeof(jvmtiHeapCallbacks)); 553 | cb->heap_reference_callback = &cbHeapReference; 554 | gdata->jvmti->FollowReferences(0,NULL, NULL, cb, NULL); 555 | } 556 | 557 | /* 558 | * Callback we receive when the JVM terminates - no more functions can be called after this 559 | */ 560 | static void JNICALL callbackVMDeath(jvmtiEnv * jvmti_env, JNIEnv * jni_env) 561 | { 562 | gdata->vmDead = JNI_TRUE; 563 | } 564 | 565 | /* 566 | * Callback we get when the JVM starts up, but before its initialized. 567 | * Sets up the JNI calls. 568 | */ 569 | static void JNICALL cbVMStart(jvmtiEnv * jvmti, JNIEnv * env) 570 | { 571 | 572 | enter_critical_section (jvmti); 573 | { 574 | jclass klass; 575 | jfieldID field; 576 | jint rc; 577 | static JNINativeMethod registry[2] = 578 | { { "_crawl", "()V", (void*) &crawlHeap}, 579 | { "_getRoots", 580 | "(Ljava/lang/Object;)[Ljava/lang/reflect/Field;", 581 | (void*) &getObjRoots}}; 582 | /* Register Natives for class whose methods we use */ 583 | klass = env->FindClass( 584 | "net/jonbell/examples/jvmti/walking/runtime/HeapWalker"); 585 | if (klass == NULL) { 586 | fatal_error( 587 | "ERROR: JNI: Cannot find JNI Helper with FindClass\n"); 588 | } 589 | 590 | rc = env->RegisterNatives(klass, registry, 2); 591 | if (rc != 0) { 592 | fatal_error("ERROR: JNI: Cannot register natives\n"); 593 | } 594 | /* Engage calls. */ 595 | field = env->GetStaticFieldID(klass, "engaged", "I"); 596 | if (field == NULL) { 597 | fatal_error("ERROR: JNI: Cannot get field\n"); 598 | } 599 | env->SetStaticIntField(klass, field, 1); 600 | 601 | gdata->vm_is_started = JNI_TRUE; 602 | 603 | } 604 | exit_critical_section(jvmti); 605 | } 606 | 607 | /* 608 | * Callback that is notified when our agent is loaded. Registers for event 609 | * notifications. 610 | */ 611 | JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, 612 | void *reserved) { 613 | static GlobalAgentData data; 614 | jvmtiError error; 615 | jint res; 616 | jvmtiEventCallbacks callbacks; 617 | jvmtiEnv *jvmti = NULL; 618 | jvmtiCapabilities capa; 619 | 620 | (void) memset((void*) &data, 0, sizeof(data)); 621 | gdata = &data; 622 | //save jvmti for later 623 | gdata->jvm = jvm; 624 | res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_0); 625 | gdata->jvmti = jvmti; 626 | 627 | if (res != JNI_OK || jvmti == NULL) { 628 | /* This means that the VM was unable to obtain this version of the 629 | * JVMTI interface, this is a fatal error. 630 | */ 631 | printf("ERROR: Unable to access JVMTI Version 1 (0x%x)," 632 | " is your J2SE a 1.5 or newer version?" 633 | " JNIEnv's GetEnv() returned %d\n", JVMTI_VERSION_1, res); 634 | 635 | } 636 | 637 | //Register our capabilities 638 | (void) memset(&capa, 0, sizeof(jvmtiCapabilities)); 639 | capa.can_tag_objects = 1; 640 | capa.can_generate_object_free_events = 1; 641 | 642 | error = jvmti->AddCapabilities(&capa); 643 | check_jvmti_error(jvmti, error, 644 | "Unable to get necessary JVMTI capabilities."); 645 | 646 | //Register callbacks 647 | (void) memset(&callbacks, 0, sizeof(callbacks)); 648 | callbacks.VMDeath = &callbackVMDeath; 649 | callbacks.VMStart = &cbVMStart; 650 | callbacks.ObjectFree = &cbObjectFree; 651 | 652 | error = jvmti->SetEventCallbacks(&callbacks, (jint) sizeof(callbacks)); 653 | check_jvmti_error(jvmti, error, "Cannot set jvmti callbacks"); 654 | 655 | //Register for events 656 | error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_START, 657 | (jthread) NULL); 658 | check_jvmti_error(jvmti, error, "Cannot set event notification"); 659 | check_jvmti_error(jvmti, error, "Cannot set event notification"); 660 | error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, 661 | (jthread) NULL); 662 | check_jvmti_error(jvmti, error, "Cannot set event notification"); 663 | error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, 664 | (jthread) NULL); 665 | check_jvmti_error(jvmti, error, "Cannot set event notification"); 666 | error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, 667 | JVMTI_EVENT_OBJECT_FREE, (jthread) NULL); 668 | check_jvmti_error(jvmti, error, "Cannot set event notification"); 669 | 670 | //Set up a few locks 671 | error = jvmti->CreateRawMonitor("agent data", &(gdata->lock)); 672 | check_jvmti_error(jvmti, error, "Cannot create raw monitor"); 673 | 674 | return JNI_OK; 675 | } 676 | -------------------------------------------------------------------------------- /heapWalking/src/main/java/net/jonbell/examples/jvmti/walking/runtime/HeapWalker.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.jvmti.walking.runtime; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Proxy; 5 | import java.util.ArrayList; 6 | 7 | import sun.misc.Launcher; 8 | 9 | public class HeapWalker { 10 | /** 11 | * Flag set by the JVMTI agent to indicate that it was successfully loaded 12 | */ 13 | public static int engaged = 0; 14 | 15 | private static native Field[] _getRoots(Object obj); 16 | 17 | private static native void _crawl(); 18 | 19 | /** 20 | * Get the static field roots that point to obj. 21 | * The data is accurate as of the last time you call the "crawl" function, which captures the pointers. 22 | * @param obj 23 | * @return 24 | */ 25 | public static Field[] getRoots(Object obj) { 26 | if (engaged == 0) 27 | throw new IllegalStateException("Attempting to use JVMTI features, but native agent not loaded"); 28 | if (obj == null) 29 | return null; 30 | return filter(_getRoots(obj)); 31 | } 32 | 33 | /** 34 | * Crawl the heap and generate a reverse points-to graph, which we need to suppor the "getRoots" functionality. 35 | */ 36 | public static void crawl() { 37 | if (engaged == 0) 38 | throw new IllegalStateException("Attempting to use JVMTI features, but native agent not loaded"); 39 | _crawl(); 40 | } 41 | 42 | /** 43 | * Filter some internal static fields out of the returned list 44 | * @param in 45 | * @return 46 | */ 47 | private static Field[] filter(Field[] in) 48 | { 49 | ArrayList ret = new ArrayList(); 50 | for(Field sf : in) 51 | { 52 | if(sf.getDeclaringClass() == ClassLoader.class || sf.getDeclaringClass() == Launcher.class || sf.getDeclaringClass() == Proxy.class) 53 | continue; 54 | ret.add(sf); 55 | } 56 | return ret.toArray(new Field[ret.size()]); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /heapWalking/src/test/java/net/jonbell/examples/jvmti/tagging/WalkingITCase.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.jvmti.tagging; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import java.lang.reflect.Field; 6 | import java.util.Arrays; 7 | 8 | import net.jonbell.examples.jvmti.walking.runtime.HeapWalker; 9 | 10 | import org.junit.Test; 11 | 12 | public class WalkingITCase { 13 | static class Foo { 14 | Object[] FOOFIELD; 15 | } 16 | 17 | static Object bar; 18 | static Object baz; 19 | 20 | @Test 21 | public void testCleanupBetweenCrawls() throws Exception { 22 | Object foo = "foo"; 23 | baz = foo; 24 | for (int i = 0; i < 10; i++) 25 | HeapWalker.crawl(); 26 | 27 | Field[] ret = HeapWalker.getRoots(foo); 28 | System.out.println(Arrays.toString(ret)); 29 | 30 | assertNotNull(ret); 31 | assertEquals(1, ret.length); 32 | assertEquals("net.jonbell.examples.jvmti.tagging.WalkingITCase", ret[0].getDeclaringClass().getName()); 33 | assertEquals("baz", ret[0].getName()); 34 | bar = null; 35 | baz = null; 36 | } 37 | 38 | @Test 39 | public void testDirectlyReachable() throws Exception { 40 | Object foo = "foo"; 41 | baz = foo; 42 | HeapWalker.crawl(); 43 | Field[] ret = HeapWalker.getRoots(foo); 44 | System.out.println(Arrays.toString(ret)); 45 | 46 | assertNotNull(ret); 47 | assertEquals(1, ret.length); 48 | assertEquals("net.jonbell.examples.jvmti.tagging.WalkingITCase", ret[0].getDeclaringClass().getName()); 49 | assertEquals("baz", ret[0].getName()); 50 | bar = null; 51 | baz = null; 52 | } 53 | 54 | @Test 55 | public void testIndiriectlyReachable() throws Exception { 56 | Object foo = "foo"; 57 | baz = new Foo(); 58 | ((Foo) baz).FOOFIELD = new Object[100]; 59 | ((Foo) baz).FOOFIELD[0] = foo; 60 | HeapWalker.crawl(); 61 | Field[] ret = HeapWalker.getRoots(foo); 62 | System.out.println(Arrays.toString(ret)); 63 | 64 | assertNotNull(ret); 65 | assertEquals(1, ret.length); 66 | assertEquals("net.jonbell.examples.jvmti.tagging.WalkingITCase", ret[0].getDeclaringClass().getName()); 67 | assertEquals("baz", ret[0].getName()); 68 | bar = null; 69 | baz = null; 70 | } 71 | 72 | @Test 73 | public void testMultipleReachable() throws Exception { 74 | Object foo = "foo"; 75 | baz = new Foo(); 76 | ((Foo) baz).FOOFIELD = new Object[100]; 77 | ((Foo) baz).FOOFIELD[0] = foo; 78 | bar = baz; 79 | HeapWalker.crawl(); 80 | Field[] ret = HeapWalker.getRoots(foo); 81 | System.out.println(Arrays.toString(ret)); 82 | 83 | assertNotNull(ret); 84 | assertEquals(2, ret.length); 85 | assertEquals("net.jonbell.examples.jvmti.tagging.WalkingITCase", ret[0].getDeclaringClass().getName()); 86 | assertTrue(("baz".equals(ret[0].getName()) && "bar".equals(ret[1].getName())) || ("bar".equals(ret[0].getName()) && "baz".equals(ret[1].getName()))); 87 | bar = null; 88 | baz = null; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /methodCoverage/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .project 3 | .classpath 4 | .settings 5 | dependency-reduced-pom.xml 6 | -------------------------------------------------------------------------------- /methodCoverage/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | net.jonbell.examples.bytecode 5 | MethodCoverageExample 6 | 0.0.1-SNAPSHOT 7 | jar 8 | 9 | 10 | 11 | org.apache.maven.plugins 12 | maven-jar-plugin 13 | 2.3.1 14 | 15 | 16 | resources/META-INF/MANIFEST.MF 17 | 18 | 19 | 20 | 21 | maven-compiler-plugin 22 | 3.1 23 | 24 | 1.7 25 | 1.7 26 | 27 | 28 | 29 | org.apache.maven.plugins 30 | maven-failsafe-plugin 31 | 2.18.1 32 | 33 | 34 | 35 | integration-test 36 | verify 37 | 38 | 39 | 40 | 41 | -Xbootclasspath/p:${project.build.directory}/${project.build.finalName}.jar 42 | -javaagent:${project.build.directory}/${project.build.finalName}.jar 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-shade-plugin 48 | 2.3 49 | 50 | 51 | package 52 | 53 | shade 54 | 55 | 56 | 57 | 58 | org.objectweb.asm 59 | net.jonbell.examples.jvmti.org.objectweb.asm 60 | 61 | 62 | false 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | junit 73 | junit 74 | 4.11 75 | test 76 | 77 | 78 | 79 | org.ow2.asm 80 | asm-all 81 | 5.0.3 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /methodCoverage/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: 1.6.0_06 (Sun Microsystems Inc.) 3 | Premain-Class: net.jonbell.examples.methodprof.PreMain 4 | 5 | -------------------------------------------------------------------------------- /methodCoverage/src/main/java/net/jonbell/examples/methodprof/PreMain.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.methodprof; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.lang.instrument.ClassFileTransformer; 6 | import java.lang.instrument.IllegalClassFormatException; 7 | import java.lang.instrument.Instrumentation; 8 | import java.security.ProtectionDomain; 9 | 10 | import net.jonbell.examples.methodprof.inst.MethodProfilingCV; 11 | 12 | import org.objectweb.asm.ClassReader; 13 | import org.objectweb.asm.ClassVisitor; 14 | import org.objectweb.asm.ClassWriter; 15 | 16 | public class PreMain { 17 | public static void premain(String args, Instrumentation inst) { 18 | inst.addTransformer(new ClassFileTransformer() { 19 | 20 | @Override 21 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 22 | //For our purposes, skip java* and sun* internal methods 23 | if (className.startsWith("java") || className.startsWith("sun")) 24 | return null; 25 | ClassReader cr = new ClassReader(classfileBuffer); 26 | ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); 27 | try { 28 | ClassVisitor cv = new MethodProfilingCV(cw); 29 | cr.accept(cv, 0); 30 | return cw.toByteArray(); 31 | } catch (Throwable t) { 32 | t.printStackTrace(); 33 | return null; 34 | } 35 | } 36 | }); 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /methodCoverage/src/main/java/net/jonbell/examples/methodprof/ProfileLogger.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.methodprof; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.lang.reflect.Method; 5 | import java.util.HashSet; 6 | 7 | public class ProfileLogger { 8 | static HashSet> classesHit = new HashSet>(); 9 | 10 | /** 11 | * Callback called when a classfile is covered the first time. 12 | * State is reset upon calling dump(). 13 | * @param c 14 | */ 15 | public static void classHit(Class c) { 16 | classesHit.add(c); 17 | } 18 | 19 | static HashSet methodsHit; 20 | 21 | /** 22 | * Callback called when a method is covered the first time. 23 | * The callback occurs only when dump() is called - it is not called in real time. 24 | * State is reset upon calling dump. 25 | * @param method 26 | */ 27 | public static void methodHit(String method) { 28 | methodsHit.add(method); 29 | } 30 | 31 | /** 32 | * Return the list of methods covered since the last invocation of dump() 33 | * @return 34 | */ 35 | public static HashSet dump() { 36 | HashSet> classes = classesHit; 37 | classesHit = new HashSet>(); 38 | methodsHit = new HashSet(); 39 | for (Class c : classes) { 40 | try { 41 | Method m = c.getDeclaredMethod("__dumpMethodsHit"); 42 | m.setAccessible(true); 43 | m.invoke(null); 44 | } catch (IllegalAccessException e) { 45 | } catch (IllegalArgumentException e) { 46 | } catch (InvocationTargetException e) { 47 | } catch (NoSuchMethodException e) { 48 | } catch (SecurityException e) { 49 | } 50 | } 51 | return methodsHit; 52 | } 53 | } -------------------------------------------------------------------------------- /methodCoverage/src/main/java/net/jonbell/examples/methodprof/inst/MethodProfilingCV.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.methodprof.inst; 2 | 3 | import java.util.HashMap; 4 | import java.util.HashSet; 5 | 6 | import net.jonbell.examples.methodprof.ProfileLogger; 7 | 8 | import org.objectweb.asm.ClassVisitor; 9 | import org.objectweb.asm.Label; 10 | import org.objectweb.asm.MethodVisitor; 11 | import org.objectweb.asm.Opcodes; 12 | import org.objectweb.asm.Type; 13 | 14 | public class MethodProfilingCV extends ClassVisitor { 15 | private String classKey; 16 | 17 | private String cName; 18 | private boolean fixLdcClass = false; 19 | private boolean isClass = false; 20 | 21 | private HashMap keyToMethod = new HashMap(); 22 | 23 | private HashSet methods = new HashSet(); 24 | 25 | public MethodProfilingCV(ClassVisitor cv) { 26 | super(Opcodes.ASM5, cv); 27 | } 28 | @Override 29 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 30 | super.visit(version, access, name, signature, superName, interfaces); 31 | isClass = (access & Opcodes.ACC_INTERFACE) == 0 && (access & Opcodes.ACC_ENUM) == 0; 32 | this.fixLdcClass = (version & 0xFFFF) < Opcodes.V1_5; 33 | this.cName = name; 34 | classKey = "__instHit" + cName.replace("/", "_"); 35 | } 36 | 37 | @Override 38 | public void visitEnd() { 39 | if (isClass) { 40 | for (String s : keyToMethod.keySet()) { 41 | //Add a field for every method to locally cache its hit state. 42 | super.visitField(Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, s, "Z", null, 0); 43 | } 44 | //Add a field for the class itself to locally cache its hit state 45 | super.visitField(Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, classKey, "Z", null, 0); 46 | //Generate a method to collect all local method hit-state 47 | MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, "__dumpMethodsHit", "()V", null, null); 48 | mv.visitCode(); 49 | for (String key : keyToMethod.keySet()) { 50 | Label ok = new Label(); 51 | mv.visitFieldInsn(Opcodes.GETSTATIC, cName, key, "Z"); 52 | mv.visitJumpInsn(Opcodes.IFEQ, ok); 53 | mv.visitLdcInsn(cName + "." + keyToMethod.get(key)); //Get the fully qualified name of this method 54 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(ProfileLogger.class), "methodHit", "(Ljava/lang/String;)V", false); 55 | mv.visitInsn(Opcodes.ICONST_0); 56 | mv.visitFieldInsn(Opcodes.PUTSTATIC, cName, key, "Z"); //Reset local state 57 | mv.visitLabel(ok); 58 | mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 59 | } 60 | mv.visitInsn(Opcodes.ICONST_0); 61 | mv.visitFieldInsn(Opcodes.PUTSTATIC, cName, classKey, "Z"); //Reset local state 62 | 63 | mv.visitInsn(Opcodes.RETURN); 64 | mv.visitMaxs(0, 0); 65 | mv.visitEnd(); 66 | } 67 | super.visitEnd(); 68 | } 69 | 70 | @Override 71 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 72 | final String key = "__instCounter_" + name.replace("<", "").replace(">", "") + methods.size(); 73 | keyToMethod.put(key, name + desc); 74 | MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 75 | return new MethodVisitor(Opcodes.ASM5, mv) { 76 | @Override 77 | public void visitCode() { 78 | if (isClass) { 79 | methods.add(key); 80 | //At method entry, check and see if we have locally cached that this method has been hit. If not, flag it. 81 | super.visitCode(); 82 | Label ok = new Label(); 83 | super.visitFieldInsn(Opcodes.GETSTATIC, cName, key, "Z"); 84 | super.visitJumpInsn(Opcodes.IFNE, ok); 85 | super.visitInsn(Opcodes.ICONST_1); 86 | super.visitFieldInsn(Opcodes.PUTSTATIC, cName, key, "Z"); 87 | 88 | /* 89 | * Make sure that we noticed that this class was hit. Could be done only once and placed in instead, 90 | * but for the purposes of this example I'm keeping it simple (we would have to make sure that each class 91 | * already has a , and add this code to it). 92 | */ 93 | super.visitFieldInsn(Opcodes.GETSTATIC, cName, classKey, "Z"); 94 | super.visitJumpInsn(Opcodes.IFNE, ok); 95 | super.visitInsn(Opcodes.ICONST_1); 96 | super.visitFieldInsn(Opcodes.PUTSTATIC, cName, classKey, "Z"); 97 | if (fixLdcClass) { 98 | super.visitLdcInsn(cName.replace("/", ".")); 99 | super.visitInsn(Opcodes.ICONST_0); 100 | super.visitLdcInsn(cName.replace("/", ".")); 101 | super.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false); 102 | super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false); 103 | super.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;", false); 104 | } else 105 | super.visitLdcInsn(Type.getObjectType(cName)); 106 | super.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(ProfileLogger.class), "classHit", "(Ljava/lang/Class;)V", false); 107 | super.visitLabel(ok); 108 | super.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 109 | } 110 | } 111 | }; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /methodCoverage/src/test/java/net/jonbell/examples/methodprof/MethodCovIT.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.methodprof; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import java.util.HashSet; 6 | 7 | import org.junit.Test; 8 | 9 | public class MethodCovIT { 10 | @Test 11 | public void testCleanup() throws Exception { 12 | ProfileLogger.dump(); 13 | assertEquals(0, ProfileLogger.dump().size()); 14 | } 15 | 16 | @Test 17 | public void testSingleCall() throws Exception { 18 | ProfileLogger.dump(); 19 | otherMethod(); 20 | HashSet meths = ProfileLogger.dump(); 21 | assertEquals(1, meths.size()); 22 | assertEquals("net/jonbell/examples/methodprof/MethodCovIT.otherMethod()V", meths.iterator().next()); 23 | } 24 | 25 | private void otherMethod() { 26 | } 27 | 28 | @Test 29 | public void testJavaMethodsExcluded() throws Exception { 30 | ProfileLogger.dump(); 31 | HashSet foo = new HashSet(); 32 | assertEquals(0, ProfileLogger.dump().size()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /nativeWrapping/Makefile: -------------------------------------------------------------------------------- 1 | UNAME := $(shell uname) 2 | CCFLAGS = -o target/taggingExample.o -I${JAVA_HOME}/include -c -fPIC -fpermissive 3 | ifeq ($(UNAME), Linux) 4 | CCFLAGS += -I${JAVA_HOME}/include/linux 5 | LINKFLAGS = -z defs -static-libgcc -shared -o target/libtaggingExample.so -lc 6 | endif 7 | ifeq ($(UNAME), Darwin) 8 | CCFLAGS += -I${JAVA_HOME}/include/darwin 9 | LINKFLAGS += -dynamiclib -o target/libtaggingExample.so 10 | endif 11 | 12 | libtaggingExample.dylib: 13 | gcc ${CCFLAGS} src/main/c/tagger.cpp 14 | g++ ${LINKFLAGS} target/taggingExample.o 15 | clean: 16 | rm -rf target/libtaggingExample.o target/libtaggingExample.dylib target/libtaggingExample.so 17 | -------------------------------------------------------------------------------- /nativeWrapping/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | net.jonbell.examples.jvmti 5 | NativeWrappingExample 6 | 0.0.1-SNAPSHOT 7 | jar 8 | 9 | 10 | 11 | org.apache.maven.plugins 12 | maven-jar-plugin 13 | 2.3.1 14 | 15 | 16 | resources/META-INF/MANIFEST.MF 17 | 18 | 19 | 20 | 21 | maven-compiler-plugin 22 | 3.1 23 | 24 | 1.7 25 | 1.7 26 | 27 | 28 | 29 | org.codehaus.mojo 30 | exec-maven-plugin 31 | 1.1 32 | 33 | 34 | exe 35 | compile 36 | 37 | exec 38 | 39 | 40 | make 41 | 42 | 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-failsafe-plugin 48 | 2.18.1 49 | 50 | 51 | 52 | integration-test 53 | verify 54 | 55 | 56 | 57 | 58 | -Xbootclasspath/p:${project.build.directory}/${project.build.finalName}.jar 59 | -agentpath:${project.build.directory}/libtaggingExample.so 60 | -javaagent:${project.build.directory}/${project.build.finalName}.jar 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-shade-plugin 66 | 2.3 67 | 68 | 69 | package 70 | 71 | shade 72 | 73 | 74 | 75 | 76 | org.objectweb.asm 77 | net.jonbell.examples.jvmti.org.objectweb.asm 78 | 79 | 80 | false 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | junit 91 | junit 92 | 4.11 93 | test 94 | 95 | 96 | 97 | org.ow2.asm 98 | asm-all 99 | 5.0.3 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /nativeWrapping/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: 1.6.0_06 (Sun Microsystems Inc.) 3 | Premain-Class: net.jonbell.examples.jvmti.nativeWrapping.inst.PreMain 4 | 5 | -------------------------------------------------------------------------------- /nativeWrapping/src/main/c/tagger.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "jvmti.h" 6 | #include "jni.h" 7 | #pragma GCC diagnostic ignored "-Wwrite-strings" 8 | 9 | void fatal_error(const char * format, ...) { 10 | va_list ap; 11 | 12 | va_start(ap, format); 13 | (void) vfprintf(stderr, format, ap); 14 | (void) fflush(stderr); 15 | va_end(ap); 16 | exit(3); 17 | } 18 | 19 | static void check_jvmti_error(jvmtiEnv *jvmti, jvmtiError errnum, 20 | const char *str) { 21 | if (errnum != JVMTI_ERROR_NONE) { 22 | char *errnum_str; 23 | 24 | errnum_str = NULL; 25 | (void) jvmti->GetErrorName(errnum, &errnum_str); 26 | 27 | printf("ERROR: JVMTI: %d(%s): %s\n", errnum, 28 | (errnum_str == NULL ? "Unknown" : errnum_str), 29 | (str == NULL ? "" : str)); 30 | } 31 | } 32 | 33 | /* 34 | * Callback that is notified when our agent is loaded. Registers for event 35 | * notifications. 36 | */ 37 | JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, 38 | void *reserved) { 39 | jvmtiError error; 40 | jint res; 41 | jvmtiEventCallbacks callbacks; 42 | jvmtiEnv *jvmti = NULL; 43 | jvmtiCapabilities capa; 44 | 45 | res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_0); 46 | 47 | if (res != JNI_OK || jvmti == NULL) { 48 | /* This means that the VM was unable to obtain this version of the 49 | * JVMTI interface, this is a fatal error. 50 | */ 51 | printf("ERROR: Unable to access JVMTI Version 1 (0x%x)," 52 | " is your J2SE a 1.5 or newer version?" 53 | " JNIEnv's GetEnv() returned %d\n", JVMTI_VERSION_1, res); 54 | 55 | } 56 | //Register our capabilities 57 | (void) memset(&capa, 0, sizeof(jvmtiCapabilities)); 58 | capa.can_set_native_method_prefix = 1; 59 | 60 | error = jvmti->AddCapabilities(&capa); 61 | check_jvmti_error(jvmti, error, 62 | "Unable to get necessary JVMTI capabilities."); 63 | 64 | error = jvmti->SetNativeMethodPrefix("$$Enhanced$$By$$JVMTI$$"); 65 | check_jvmti_error(jvmti, error, 66 | "Unable to set native prefix."); 67 | 68 | return JNI_OK; 69 | } 70 | -------------------------------------------------------------------------------- /nativeWrapping/src/main/java/net/jonbell/examples/jvmti/nativeWrapping/inst/NativeWrappingCV.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.jvmti.nativeWrapping.inst; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashSet; 5 | 6 | import org.objectweb.asm.ClassVisitor; 7 | import org.objectweb.asm.MethodVisitor; 8 | import org.objectweb.asm.Opcodes; 9 | import org.objectweb.asm.commons.GeneratorAdapter; 10 | import org.objectweb.asm.tree.MethodNode; 11 | 12 | import com.sun.org.apache.bcel.internal.generic.INVOKESPECIAL; 13 | import com.sun.org.apache.bcel.internal.generic.INVOKESTATIC; 14 | 15 | public class NativeWrappingCV extends ClassVisitor { 16 | 17 | public NativeWrappingCV(ClassVisitor cv) { 18 | super(Opcodes.ASM5, cv); 19 | } 20 | 21 | boolean addField = false; 22 | boolean isClass; 23 | String className; 24 | 25 | @Override 26 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 27 | className = name; 28 | super.visit(version, access, name, signature, superName, interfaces); 29 | } 30 | 31 | HashSet nativeMethods = new HashSet(); 32 | 33 | @Override 34 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 35 | if ((access & Opcodes.ACC_NATIVE) != 0) { 36 | // Native method 37 | nativeMethods.add(new MethodNode(access, name, desc, signature, exceptions)); 38 | name = "$$Enhanced$$By$$JVMTI$$" + name; 39 | } 40 | return super.visitMethod(access, name, desc, signature, exceptions); 41 | } 42 | 43 | @Override 44 | public void visitEnd() { 45 | for (MethodNode mn : nativeMethods) { 46 | int acc = mn.access & ~Opcodes.ACC_NATIVE; 47 | GeneratorAdapter ga = new GeneratorAdapter(super.visitMethod(acc, mn.name, mn.desc, mn.signature, new String[] {}), acc, mn.name, mn.desc); 48 | 49 | ga.visitCode(); 50 | 51 | //Log the fact that this native method was called 52 | ga.visitLdcInsn(className+"."+mn.name+mn.desc); 53 | ga.visitMethodInsn(Opcodes.INVOKESTATIC, "net/jonbell/examples/jvmti/nativeWrapping/runtime/NativeLogger", "recordNative", "(Ljava/lang/String;)V", false); 54 | 55 | boolean isStatic = (mn.access & Opcodes.ACC_STATIC) != 0; 56 | if(!isStatic) 57 | ga.loadThis(); 58 | ga.loadArgs(); 59 | ga.visitMethodInsn((isStatic ? Opcodes.INVOKESTATIC : Opcodes.INVOKESPECIAL), className, "$$Enhanced$$By$$JVMTI$$" + mn.name, mn.desc, false); 60 | ga.returnValue(); 61 | ga.visitMaxs(0, 0); 62 | ga.visitEnd(); 63 | } 64 | super.visitEnd(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /nativeWrapping/src/main/java/net/jonbell/examples/jvmti/nativeWrapping/inst/PreMain.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.jvmti.nativeWrapping.inst; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.lang.instrument.ClassFileTransformer; 6 | import java.lang.instrument.IllegalClassFormatException; 7 | import java.lang.instrument.Instrumentation; 8 | import java.security.ProtectionDomain; 9 | 10 | import org.objectweb.asm.ClassReader; 11 | import org.objectweb.asm.ClassWriter; 12 | 13 | public class PreMain { 14 | public static void premain(String args, Instrumentation inst) { 15 | inst.addTransformer(new ClassFileTransformer() { 16 | @Override 17 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 18 | if(isIgnoredClass(className)) 19 | return classfileBuffer; 20 | try { 21 | ClassReader cr = new ClassReader(classfileBuffer); 22 | 23 | ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); 24 | cr.accept(new NativeWrappingCV(cw), 0); 25 | 26 | // Uncomment below if you want to dump all of the instrumented class files 27 | // File f = new File("debug/"+cr.getClassName().replace('/', '.')+".class"); 28 | // if(!f.getParentFile().exists()) 29 | // f.getParentFile().mkdir(); 30 | // FileOutputStream fos = new FileOutputStream(f); 31 | // fos.write(cw.toByteArray()); 32 | // fos.close(); 33 | 34 | return cw.toByteArray(); 35 | } catch (Throwable t) { 36 | //Make sure that an exception in instrumentation gets printed, rather than squelched 37 | t.printStackTrace(); 38 | return null; 39 | } 40 | } 41 | }); 42 | } 43 | 44 | public static boolean isIgnoredClass(String owner) { 45 | //For one reason or another, we can't muck with these classes with ease. 46 | //Instances of these classes that you want to be tagged will be tagged with JVMTI 47 | return owner.startsWith("java/lang/Object") || owner.startsWith("java/lang/Number") || owner.startsWith("java/lang/Comparable") || owner.startsWith("java/lang/ref/SoftReference") 48 | || owner.startsWith("java/lang/ref/Reference") || owner.startsWith("java/lang/ref/FinalizerReference") || owner.startsWith("java/lang/Boolean") 49 | || owner.startsWith("java/lang/Character") || owner.startsWith("java/lang/Float") || owner.startsWith("java/lang/Byte") || owner.startsWith("java/lang/Short") 50 | || owner.startsWith("java/lang/Integer") || owner.startsWith("java/lang/StackTraceElement") || (owner.startsWith("edu/columbia/cs/psl/testdepends")) 51 | || owner.startsWith("sun/awt/image/codec/") 52 | || (owner.startsWith("sun/reflect/Reflection")) 53 | || owner.equals("java/lang/reflect/Proxy") 54 | || owner.startsWith("sun/reflection/annotation/AnnotationParser") 55 | || owner.startsWith("sun/reflect/MethodAccessor") 56 | || owner.startsWith("sun/reflect/ConstructorAccessor") 57 | || owner.startsWith("sun/reflect/SerializationConstructorAccessor") 58 | || owner.startsWith("sun/reflect/GeneratedMethodAccessor") || owner.startsWith("sun/reflect/GeneratedConstructorAccessor") 59 | || owner.startsWith("sun/reflect/GeneratedSerializationConstructor"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /nativeWrapping/src/main/java/net/jonbell/examples/jvmti/nativeWrapping/runtime/NativeLogger.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.jvmti.nativeWrapping.runtime; 2 | 3 | import java.util.HashSet; 4 | 5 | public class NativeLogger { 6 | 7 | public static HashSet nativeCalls = new HashSet(); 8 | public static void recordNative(String meth) 9 | { 10 | nativeCalls.add(meth); 11 | } 12 | static 13 | { 14 | Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { 15 | 16 | @Override 17 | public void run() { 18 | System.out.println("Native methods invoked:"); 19 | HashSet toPrint = nativeCalls; 20 | nativeCalls = new HashSet(); 21 | for(String s : toPrint) 22 | System.out.println(s); 23 | } 24 | })); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /nativeWrapping/src/test/java/net/jonbell/examples/jvmti/nativeWrapping/NativeWrappingITCase.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.jvmti.nativeWrapping; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | 8 | import org.junit.Test; 9 | 10 | public class NativeWrappingITCase { 11 | @Test 12 | public void testObject() throws Exception { 13 | Object o = new Object(); 14 | Object tag = "foo"; 15 | FileInputStream fis = new FileInputStream(new File("")); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /staticInstrumenter/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .project 3 | .classpath 4 | .settings 5 | dependency-reduced-pom.xml 6 | -------------------------------------------------------------------------------- /staticInstrumenter/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | net.jonbell.examples.bytecode 5 | instrumenter-maven-plugin 6 | 0.0.1-SNAPSHOT 7 | maven-plugin 8 | 9 | 10 | 11 | org.apache.maven.plugins 12 | maven-jar-plugin 13 | 2.3.1 14 | 15 | 16 | resources/META-INF/MANIFEST.MF 17 | 18 | 19 | 20 | 21 | maven-compiler-plugin 22 | 3.1 23 | 24 | 1.7 25 | 1.7 26 | 27 | 28 | 29 | org.apache.maven.plugins 30 | maven-failsafe-plugin 31 | 2.18.1 32 | 33 | 34 | 35 | integration-test 36 | verify 37 | 38 | 39 | 40 | 41 | -Xbootclasspath/p:${project.build.directory}/${project.build.finalName}.jar 42 | -javaagent:${project.build.directory}/${project.build.finalName}.jar 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-shade-plugin 48 | 2.3 49 | 50 | 51 | package 52 | 53 | shade 54 | 55 | 56 | 57 | 58 | commons-cli:commons-cli 59 | org.ow2.asm:asm-all 60 | 61 | 62 | 63 | 64 | org.apache.commons.cli 65 | net.jonbell.examples.bytecode.org.apache.commons.cli 66 | 67 | 68 | 69 | org.objectweb.asm 70 | net.jonbell.examples.bytecode.org.objectweb.asm 71 | 72 | 73 | false 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | org.apache.maven.plugin-tools 84 | maven-plugin-annotations 85 | 3.2 86 | provided 87 | 88 | 89 | org.apache.maven 90 | maven-project 91 | 2.2.1 92 | 93 | 94 | 95 | org.apache.maven 96 | maven-plugin-api 97 | 2.2.1 98 | 99 | 100 | commons-cli 101 | commons-cli 102 | 1.2 103 | 104 | 105 | org.ow2.asm 106 | asm-all 107 | 5.0.3 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /staticInstrumenter/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: 1.6.0_06 (Sun Microsystems Inc.) 3 | Premain-Class: net.jonbell.examples.bytecode.instrumenting.PreMain 4 | Main-Class: net.jonbell.examples.bytecode.instrumenting.Instrumenter 5 | 6 | -------------------------------------------------------------------------------- /staticInstrumenter/src/main/java/net/jonbell/examples/bytecode/instrumenting/ClassCoverageCV.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.bytecode.instrumenting; 2 | 3 | 4 | import org.objectweb.asm.ClassVisitor; 5 | import org.objectweb.asm.Label; 6 | import org.objectweb.asm.MethodVisitor; 7 | import org.objectweb.asm.Opcodes; 8 | import org.objectweb.asm.Type; 9 | 10 | public class ClassCoverageCV extends ClassVisitor { 11 | 12 | public ClassCoverageCV(ClassVisitor cv) { 13 | super(Opcodes.ASM5, cv); 14 | } 15 | 16 | public static final String CLASS_COVERAGE_FIELD = "__jonBellExampleClassCovered"; 17 | private String className; 18 | private boolean isClass; 19 | private boolean fixLdcClass; 20 | @Override 21 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 22 | super.visit(version, access, name, signature, superName, interfaces); 23 | this.className = name; 24 | this.fixLdcClass = (version & 0xFFFF) < Opcodes.V1_5; 25 | this.isClass = (access & Opcodes.ACC_INTERFACE) == 0 && (access & Opcodes.ACC_ENUM) == 0; 26 | } 27 | 28 | @Override 29 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 30 | MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 31 | return new MethodVisitor(Opcodes.ASM5, mv) { 32 | @Override 33 | public void visitCode() { 34 | if (isClass) { 35 | /* 36 | * Make sure that we noticed that this class was hit. 37 | */ 38 | Label ok = new Label(); 39 | super.visitFieldInsn(Opcodes.GETSTATIC, className, CLASS_COVERAGE_FIELD, "Z"); 40 | super.visitJumpInsn(Opcodes.IFNE, ok); 41 | super.visitInsn(Opcodes.ICONST_1); 42 | super.visitFieldInsn(Opcodes.PUTSTATIC, className, CLASS_COVERAGE_FIELD, "Z"); 43 | if (fixLdcClass) { 44 | super.visitLdcInsn(className.replace("/", ".")); 45 | super.visitInsn(Opcodes.ICONST_0); 46 | super.visitLdcInsn(className.replace("/", ".")); 47 | super.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false); 48 | super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false); 49 | super.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;", false); 50 | } else 51 | super.visitLdcInsn(Type.getObjectType(className)); 52 | super.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(CoverageLogger.class), "classHit", "(Ljava/lang/Class;)V", false); 53 | super.visitLabel(ok); 54 | super.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 55 | } 56 | } 57 | }; 58 | } 59 | 60 | @Override 61 | public void visitEnd() { 62 | if (isClass) 63 | super.visitField(Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, CLASS_COVERAGE_FIELD, "Z", null, false); 64 | super.visitEnd(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /staticInstrumenter/src/main/java/net/jonbell/examples/bytecode/instrumenting/ClassCoverageClassFileTransformer.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.bytecode.instrumenting; 2 | 3 | import java.lang.instrument.ClassFileTransformer; 4 | import java.lang.instrument.IllegalClassFormatException; 5 | import java.security.ProtectionDomain; 6 | 7 | import org.objectweb.asm.ClassReader; 8 | import org.objectweb.asm.ClassVisitor; 9 | import org.objectweb.asm.ClassWriter; 10 | import org.objectweb.asm.FieldVisitor; 11 | import org.objectweb.asm.Opcodes; 12 | 13 | public class ClassCoverageClassFileTransformer implements ClassFileTransformer { 14 | boolean shouldIgnore; 15 | 16 | boolean shouldIgnore(ClassReader cr) { 17 | shouldIgnore = false; 18 | cr.accept(new ClassVisitor(Opcodes.ASM5) { 19 | @Override 20 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 21 | super.visit(version, access, name, signature, superName, interfaces); 22 | if (!((access & Opcodes.ACC_INTERFACE) == 0 && (access & Opcodes.ACC_ENUM) == 0)) 23 | shouldIgnore = true; //only bother with classes 24 | } 25 | 26 | @Override 27 | public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { 28 | if (name.equals(ClassCoverageCV.CLASS_COVERAGE_FIELD)) 29 | shouldIgnore = true; //if the field is already there, then we instrumented it statically 30 | return super.visitField(access, name, desc, signature, value); 31 | } 32 | }, ClassReader.SKIP_CODE); 33 | return shouldIgnore; 34 | } 35 | 36 | @Override 37 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 38 | ClassReader cr = new ClassReader(classfileBuffer); 39 | if (shouldIgnore(cr)) 40 | return classfileBuffer; 41 | ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); 42 | try { 43 | ClassVisitor cv = new ClassCoverageCV(cw); 44 | cr.accept(cv, 0); 45 | return cw.toByteArray(); 46 | } catch (Throwable t) { 47 | t.printStackTrace(); 48 | return null; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /staticInstrumenter/src/main/java/net/jonbell/examples/bytecode/instrumenting/CoverageLogger.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.bytecode.instrumenting; 2 | 3 | import java.util.HashSet; 4 | 5 | public class CoverageLogger { 6 | 7 | private static HashSet> covered = new HashSet>(); 8 | public static HashSet> getCoveredClasses() 9 | { 10 | return covered; 11 | } 12 | public static void resetCoverage() 13 | { 14 | for(Class c : covered) 15 | { 16 | try { 17 | c.getField(ClassCoverageCV.CLASS_COVERAGE_FIELD).setBoolean(null, false); 18 | } catch (IllegalArgumentException e) { 19 | } catch (IllegalAccessException e) { 20 | } catch (NoSuchFieldException e) { 21 | } catch (SecurityException e) { 22 | } 23 | } 24 | covered = new HashSet>(); 25 | } 26 | public static void classHit(Class c) 27 | { 28 | covered.add(c); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /staticInstrumenter/src/main/java/net/jonbell/examples/bytecode/instrumenting/Instrumenter.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.bytecode.instrumenting; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.FileNotFoundException; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.nio.channels.FileChannel; 11 | import java.util.Enumeration; 12 | import java.util.Scanner; 13 | import java.util.jar.JarEntry; 14 | import java.util.jar.JarFile; 15 | import java.util.jar.JarOutputStream; 16 | import java.util.zip.ZipEntry; 17 | import java.util.zip.ZipException; 18 | import java.util.zip.ZipFile; 19 | import java.util.zip.ZipOutputStream; 20 | 21 | import org.apache.commons.cli.BasicParser; 22 | import org.apache.commons.cli.CommandLine; 23 | import org.apache.commons.cli.CommandLineParser; 24 | import org.apache.commons.cli.HelpFormatter; 25 | import org.apache.commons.cli.Option; 26 | import org.apache.commons.cli.Options; 27 | 28 | public class Instrumenter { 29 | public static ClassLoader loader; 30 | 31 | static String curPath; 32 | 33 | static int n = 0; 34 | 35 | public static byte[] instrumentClass(String path, InputStream is, boolean renameInterfaces) { 36 | try { 37 | n++; 38 | if (n % 1000 == 0) 39 | System.out.println("Processed: " + n); 40 | curPath = path; 41 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 42 | 43 | int nRead; 44 | byte[] data = new byte[16384]; 45 | 46 | while ((nRead = is.read(data, 0, data.length)) != -1) { 47 | buffer.write(data, 0, nRead); 48 | } 49 | 50 | buffer.flush(); 51 | ClassCoverageClassFileTransformer transformer = new ClassCoverageClassFileTransformer(); 52 | byte[] ret = transformer.transform(Instrumenter.loader, path, null, null, buffer.toByteArray()); 53 | curPath = null; 54 | return ret; 55 | } catch (Exception ex) { 56 | curPath = null; 57 | ex.printStackTrace(); 58 | return null; 59 | } 60 | } 61 | 62 | static Option help = new Option("help", "print this message"); 63 | 64 | public static void main(String[] args) { 65 | 66 | Options options = new Options(); 67 | options.addOption(help); 68 | 69 | CommandLineParser parser = new BasicParser(); 70 | CommandLine line = null; 71 | try { 72 | line = parser.parse(options, args); 73 | } catch (org.apache.commons.cli.ParseException exp) { 74 | 75 | HelpFormatter formatter = new HelpFormatter(); 76 | formatter.printHelp("java -jar instrumenter.jar [OPTIONS] [input] [output]", options); 77 | System.err.println(exp.getMessage()); 78 | return; 79 | } 80 | if (line.hasOption("help") || line.getArgs().length != 2) { 81 | HelpFormatter formatter = new HelpFormatter(); 82 | formatter.printHelp("java -jar instrumenter.jar [OPTIONS] [input] [output]", options); 83 | return; 84 | } 85 | 86 | PreMain.IS_RUNTIME_INST = false; 87 | instrumentDir(line.getArgs()[0], line.getArgs()[1]); 88 | } 89 | 90 | static File rootOutputDir; 91 | 92 | public static void instrumentDir(String inputFolder, String outputFolder) { 93 | 94 | rootOutputDir = new File(outputFolder); 95 | if (!rootOutputDir.exists()) 96 | rootOutputDir.mkdir(); 97 | 98 | File f = new File(inputFolder); 99 | if (!f.exists()) { 100 | System.err.println("Unable to read path " + inputFolder); 101 | System.exit(-1); 102 | } 103 | if (f.isDirectory()) 104 | processDirectory(f, rootOutputDir, true); 105 | else if (inputFolder.endsWith(".jar") || inputFolder.endsWith(".war")) 106 | processJar(f, rootOutputDir); 107 | else if (inputFolder.endsWith(".class")) 108 | try { 109 | processClass(f.getName(), new FileInputStream(f), rootOutputDir); 110 | } catch (FileNotFoundException e) { 111 | e.printStackTrace(); 112 | } 113 | else if (inputFolder.endsWith(".zip")) { 114 | processZip(f, rootOutputDir); 115 | } else { 116 | System.err.println("Unknown type for path " + inputFolder); 117 | System.exit(-1); 118 | } 119 | } 120 | 121 | static String lastInstrumentedClass; 122 | 123 | private static void processClass(String name, InputStream is, File outputDir) { 124 | 125 | try { 126 | FileOutputStream fos = new FileOutputStream(outputDir.getPath() + File.separator + name); 127 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 128 | lastInstrumentedClass = outputDir.getPath() + File.separator + name; 129 | 130 | byte[] c = instrumentClass(outputDir.getAbsolutePath(), is, true); 131 | bos.write(c); 132 | bos.writeTo(fos); 133 | fos.close(); 134 | is.close(); 135 | 136 | } catch (Exception ex) { 137 | ex.printStackTrace(); 138 | } 139 | } 140 | 141 | private static void processDirectory(File f, File parentOutputDir, boolean isFirstLevel) { 142 | File thisOutputDir; 143 | if (isFirstLevel) { 144 | thisOutputDir = parentOutputDir; 145 | } else { 146 | thisOutputDir = new File(parentOutputDir.getAbsolutePath() + File.separator + f.getName()); 147 | thisOutputDir.mkdir(); 148 | } 149 | for (File fi : f.listFiles()) { 150 | if (fi.isDirectory()) 151 | processDirectory(fi, thisOutputDir, false); 152 | else if (fi.getName().endsWith(".class")) 153 | try { 154 | processClass(fi.getName(), new FileInputStream(fi), thisOutputDir); 155 | } catch (FileNotFoundException e) { 156 | e.printStackTrace(); 157 | } 158 | else if (fi.getName().endsWith(".jar") || fi.getName().endsWith(".war")) 159 | processJar(fi, thisOutputDir); 160 | else if (fi.getName().endsWith(".zip")) 161 | processZip(fi, thisOutputDir); 162 | else { 163 | File dest = new File(thisOutputDir.getPath() + File.separator + fi.getName()); 164 | FileChannel source = null; 165 | FileChannel destination = null; 166 | 167 | try { 168 | source = new FileInputStream(fi).getChannel(); 169 | destination = new FileOutputStream(dest).getChannel(); 170 | destination.transferFrom(source, 0, source.size()); 171 | } catch (Exception ex) { 172 | System.err.println("error copying file " + fi); 173 | ex.printStackTrace(); 174 | } finally { 175 | if (source != null) { 176 | try { 177 | source.close(); 178 | } catch (IOException e) { 179 | e.printStackTrace(); 180 | } 181 | } 182 | if (destination != null) { 183 | try { 184 | destination.close(); 185 | } catch (IOException e) { 186 | e.printStackTrace(); 187 | } 188 | } 189 | } 190 | 191 | } 192 | } 193 | 194 | } 195 | 196 | public static void processJar(File f, File outputDir) { 197 | try { 198 | JarFile jar = new JarFile(f); 199 | JarOutputStream jos = null; 200 | jos = new JarOutputStream(new FileOutputStream(outputDir.getPath() + File.separator + f.getName())); 201 | Enumeration entries = jar.entries(); 202 | while (entries.hasMoreElements()) { 203 | JarEntry e = entries.nextElement(); 204 | if (e.getName().endsWith(".class")) { 205 | { 206 | 207 | try { 208 | JarEntry outEntry = new JarEntry(e.getName()); 209 | jos.putNextEntry(outEntry); 210 | byte[] clazz = instrumentClass(f.getAbsolutePath(), jar.getInputStream(e), true); 211 | if (clazz == null) { 212 | System.out.println("Failed to instrument " + e.getName() + " in " + f.getName()); 213 | InputStream is = jar.getInputStream(e); 214 | byte[] buffer = new byte[1024]; 215 | while (true) { 216 | int count = is.read(buffer); 217 | if (count == -1) 218 | break; 219 | jos.write(buffer, 0, count); 220 | } 221 | } else { 222 | jos.write(clazz); 223 | } 224 | jos.closeEntry(); 225 | } catch (ZipException ex) { 226 | ex.printStackTrace(); 227 | continue; 228 | } 229 | 230 | } 231 | 232 | } else { 233 | JarEntry outEntry = new JarEntry(e.getName()); 234 | if (e.isDirectory()) { 235 | try { 236 | jos.putNextEntry(outEntry); 237 | jos.closeEntry(); 238 | } catch (ZipException exxx) { 239 | System.out.println("Ignoring exception: " + exxx); 240 | } 241 | } else if (e.getName().startsWith("META-INF") && (e.getName().endsWith(".SF") || e.getName().endsWith(".RSA"))) { 242 | // don't copy this 243 | } else if (e.getName().equals("META-INF/MANIFEST.MF")) { 244 | Scanner s = new Scanner(jar.getInputStream(e)); 245 | jos.putNextEntry(outEntry); 246 | 247 | String curPair = ""; 248 | while (s.hasNextLine()) { 249 | String line = s.nextLine(); 250 | if (line.equals("")) { 251 | curPair += "\n"; 252 | if (!curPair.contains("SHA1-Digest:")) 253 | jos.write(curPair.getBytes()); 254 | curPair = ""; 255 | } else { 256 | curPair += line + "\n"; 257 | } 258 | } 259 | s.close(); 260 | jos.closeEntry(); 261 | } else { 262 | try { 263 | jos.putNextEntry(outEntry); 264 | InputStream is = jar.getInputStream(e); 265 | byte[] buffer = new byte[1024]; 266 | while (true) { 267 | int count = is.read(buffer); 268 | if (count == -1) 269 | break; 270 | jos.write(buffer, 0, count); 271 | } 272 | jos.closeEntry(); 273 | } catch (ZipException ex) { 274 | if (!ex.getMessage().contains("duplicate entry")) { 275 | ex.printStackTrace(); 276 | System.out.println("Ignoring above warning from improper source zip..."); 277 | } 278 | } 279 | } 280 | 281 | } 282 | 283 | } 284 | if (jos != null) { 285 | jos.close(); 286 | 287 | } 288 | jar.close(); 289 | } catch (Exception e) { 290 | System.err.println("Unable to process jar: " + f.getAbsolutePath()); 291 | e.printStackTrace(); 292 | File dest = new File(outputDir.getPath() + File.separator + f.getName()); 293 | FileChannel source = null; 294 | FileChannel destination = null; 295 | 296 | try { 297 | source = new FileInputStream(f).getChannel(); 298 | destination = new FileOutputStream(dest).getChannel(); 299 | destination.transferFrom(source, 0, source.size()); 300 | } catch (Exception ex) { 301 | System.err.println("Unable to copy file: " + f.getAbsolutePath()); 302 | ex.printStackTrace(); 303 | } finally { 304 | if (source != null) { 305 | try { 306 | source.close(); 307 | } catch (IOException e2) { 308 | e2.printStackTrace(); 309 | } 310 | } 311 | if (destination != null) { 312 | try { 313 | destination.close(); 314 | } catch (IOException e2) { 315 | e2.printStackTrace(); 316 | } 317 | } 318 | } 319 | } 320 | 321 | } 322 | 323 | private static void processZip(File f, File outputDir) { 324 | try { 325 | ZipFile zip = new ZipFile(f); 326 | ZipOutputStream zos = null; 327 | zos = new ZipOutputStream(new FileOutputStream(outputDir.getPath() + File.separator + f.getName())); 328 | Enumeration entries = zip.entries(); 329 | while (entries.hasMoreElements()) { 330 | ZipEntry e = entries.nextElement(); 331 | 332 | if (e.getName().endsWith(".class")) { 333 | { 334 | ZipEntry outEntry = new ZipEntry(e.getName()); 335 | zos.putNextEntry(outEntry); 336 | 337 | byte[] clazz = instrumentClass(f.getAbsolutePath(), zip.getInputStream(e), true); 338 | if (clazz == null) { 339 | InputStream is = zip.getInputStream(e); 340 | byte[] buffer = new byte[1024]; 341 | while (true) { 342 | int count = is.read(buffer); 343 | if (count == -1) 344 | break; 345 | zos.write(buffer, 0, count); 346 | } 347 | } else 348 | zos.write(clazz); 349 | zos.closeEntry(); 350 | 351 | } 352 | 353 | } else if (e.getName().endsWith(".jar")) { 354 | ZipEntry outEntry = new ZipEntry(e.getName()); 355 | File tmp = new File("/tmp/classfile"); 356 | if (tmp.exists()) 357 | tmp.delete(); 358 | FileOutputStream fos = new FileOutputStream(tmp); 359 | byte buf[] = new byte[1024]; 360 | int len; 361 | InputStream is = zip.getInputStream(e); 362 | while ((len = is.read(buf)) > 0) { 363 | fos.write(buf, 0, len); 364 | } 365 | is.close(); 366 | fos.close(); 367 | 368 | File tmp2 = new File("tmp2"); 369 | if (!tmp2.exists()) 370 | tmp2.mkdir(); 371 | processJar(tmp, new File("tmp2")); 372 | 373 | zos.putNextEntry(outEntry); 374 | is = new FileInputStream("tmp2/classfile"); 375 | byte[] buffer = new byte[1024]; 376 | while (true) { 377 | int count = is.read(buffer); 378 | if (count == -1) 379 | break; 380 | zos.write(buffer, 0, count); 381 | } 382 | is.close(); 383 | zos.closeEntry(); 384 | } else { 385 | ZipEntry outEntry = new ZipEntry(e.getName()); 386 | if (e.isDirectory()) { 387 | try { 388 | zos.putNextEntry(outEntry); 389 | zos.closeEntry(); 390 | } catch (ZipException exxxx) { 391 | System.out.println("Ignoring exception: " + exxxx.getMessage()); 392 | } 393 | } else if (e.getName().startsWith("META-INF") && (e.getName().endsWith(".SF") || e.getName().endsWith(".RSA"))) { 394 | // don't copy this 395 | } else if (e.getName().equals("META-INF/MANIFEST.MF")) { 396 | Scanner s = new Scanner(zip.getInputStream(e)); 397 | zos.putNextEntry(outEntry); 398 | 399 | String curPair = ""; 400 | while (s.hasNextLine()) { 401 | String line = s.nextLine(); 402 | if (line.equals("")) { 403 | curPair += "\n"; 404 | if (!curPair.contains("SHA1-Digest:")) 405 | zos.write(curPair.getBytes()); 406 | curPair = ""; 407 | } else { 408 | curPair += line + "\n"; 409 | } 410 | } 411 | s.close(); 412 | zos.write("\n".getBytes()); 413 | zos.closeEntry(); 414 | } else { 415 | zos.putNextEntry(outEntry); 416 | InputStream is = zip.getInputStream(e); 417 | byte[] buffer = new byte[1024]; 418 | while (true) { 419 | int count = is.read(buffer); 420 | if (count == -1) 421 | break; 422 | zos.write(buffer, 0, count); 423 | } 424 | zos.closeEntry(); 425 | } 426 | } 427 | 428 | } 429 | zos.close(); 430 | zip.close(); 431 | } catch (Exception e) { 432 | System.err.println("Unable to process zip: " + f.getAbsolutePath()); 433 | e.printStackTrace(); 434 | File dest = new File(outputDir.getPath() + File.separator + f.getName()); 435 | FileChannel source = null; 436 | FileChannel destination = null; 437 | 438 | try { 439 | source = new FileInputStream(f).getChannel(); 440 | destination = new FileOutputStream(dest).getChannel(); 441 | destination.transferFrom(source, 0, source.size()); 442 | } catch (Exception ex) { 443 | System.err.println("Unable to copy zip: " + f.getAbsolutePath()); 444 | ex.printStackTrace(); 445 | } finally { 446 | if (source != null) { 447 | try { 448 | source.close(); 449 | } catch (IOException e2) { 450 | e2.printStackTrace(); 451 | } 452 | } 453 | if (destination != null) { 454 | try { 455 | destination.close(); 456 | } catch (IOException e2) { 457 | e2.printStackTrace(); 458 | } 459 | } 460 | } 461 | } 462 | 463 | } 464 | 465 | } -------------------------------------------------------------------------------- /staticInstrumenter/src/main/java/net/jonbell/examples/bytecode/instrumenting/InstrumenterMojo.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.bytecode.instrumenting; 2 | 3 | import java.io.File; 4 | 5 | import org.apache.maven.plugin.AbstractMojo; 6 | import org.apache.maven.plugin.MojoExecutionException; 7 | import org.apache.maven.plugin.MojoFailureException; 8 | import org.apache.maven.plugins.annotations.Component; 9 | import org.apache.maven.plugins.annotations.LifecyclePhase; 10 | import org.apache.maven.plugins.annotations.Mojo; 11 | import org.apache.maven.plugins.annotations.Parameter; 12 | import org.apache.maven.project.MavenProject; 13 | 14 | @Mojo(name = "instrumentBytecode", defaultPhase = LifecyclePhase.PROCESS_CLASSES) 15 | public class InstrumenterMojo extends AbstractMojo { 16 | 17 | @Component 18 | private MavenProject project; 19 | @Parameter(defaultValue = "false", readonly = true) 20 | private boolean instrumentTest; 21 | 22 | @Override 23 | public void execute() throws MojoExecutionException, MojoFailureException { 24 | String buildOutput = null; 25 | if(instrumentTest) 26 | buildOutput = project.getBuild().getTestOutputDirectory(); 27 | else 28 | buildOutput = project.getBuild().getOutputDirectory(); 29 | 30 | if (buildOutput.endsWith("/")) 31 | buildOutput = buildOutput.substring(0, buildOutput.length() - 1); 32 | Instrumenter.main(new String[] { buildOutput, buildOutput + "-instrumented" }); 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /staticInstrumenter/src/main/java/net/jonbell/examples/bytecode/instrumenting/PreMain.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.bytecode.instrumenting; 2 | 3 | 4 | import java.lang.instrument.Instrumentation; 5 | 6 | public class PreMain { 7 | public static boolean IS_RUNTIME_INST = true; 8 | 9 | public static void premain(String args, Instrumentation inst) { 10 | inst.addTransformer(new ClassCoverageClassFileTransformer()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /staticInstrumenterUsage/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .project 3 | .classpath 4 | .settings 5 | dependency-reduced-pom.xml 6 | -------------------------------------------------------------------------------- /staticInstrumenterUsage/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | net.jonbell.examples.bytecode 5 | StaticBytecodeInstrumenterDemo 6 | 0.0.1-SNAPSHOT 7 | jar 8 | 9 | 10 | 11 | net.jonbell.examples.bytecode 12 | instrumenter-maven-plugin 13 | 0.0.1-SNAPSHOT 14 | 15 | 16 | instrument-main 17 | process-classes 18 | 19 | instrumentBytecode 20 | 21 | 22 | 23 | instrument-tests 24 | process-test-classes 25 | 26 | instrumentBytecode 27 | 28 | 29 | true 30 | 31 | 32 | 33 | 34 | 35 | maven-compiler-plugin 36 | 3.1 37 | 38 | 1.7 39 | 1.7 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-failsafe-plugin 45 | 2.18.1 46 | 47 | 48 | 49 | integration-test 50 | verify 51 | 52 | 53 | 54 | 55 | -Xbootclasspath/p:${project.build.directory}/${project.build.finalName}.jar 56 | 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-shade-plugin 61 | 2.3 62 | 63 | 64 | package 65 | 66 | shade 67 | 68 | 69 | 70 | 71 | org.apache.commons.cli 72 | net.jonbell.examples.bytecode.org.apache.commons.cli 73 | 74 | 75 | 76 | org.objectweb.asm 77 | net.jonbell.examples.bytecode.org.objectweb.asm 78 | 79 | 80 | false 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | junit 91 | junit 92 | 4.11 93 | test 94 | 95 | 96 | commons-cli 97 | commons-cli 98 | 1.2 99 | 100 | 101 | org.ow2.asm 102 | asm-all 103 | 5.0.3 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /staticInstrumenterUsage/src/main/java/net/jonbell/examples/bytecode/instrument/demo/FooClass.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.bytecode.instrument.demo; 2 | 3 | public class FooClass { 4 | public void magic() 5 | { 6 | 7 | } 8 | public void moreMagic() 9 | { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /staticInstrumenterUsage/src/test/java/net/jonbell/examples/bytecode/instrument/demo/ClassCoverageIT.java: -------------------------------------------------------------------------------- 1 | package net.jonbell.examples.bytecode.instrument.demo; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | 7 | public class ClassCoverageIT { 8 | 9 | @Test 10 | public void testClassCoverage() throws Exception { 11 | new FooClass().magic(); 12 | } 13 | } 14 | --------------------------------------------------------------------------------