├── 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