1 /* 2 * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 #include <stdio.h> 25 #include <string.h> 26 #include "jvmti.h" 27 28 #ifdef __cplusplus 29 extern "C" { 30 #endif 31 32 #ifndef JNI_ENV_ARG 33 34 #ifdef __cplusplus 35 #define JNI_ENV_ARG(x, y) y 36 #define JNI_ENV_PTR(x) x 37 #else 38 #define JNI_ENV_ARG(x,y) x, y 39 #define JNI_ENV_PTR(x) (*x) 40 #endif 41 42 #endif 43 44 #define PASSED 0 45 #define FAILED 2 46 47 #define MAX_TRACES 400 48 49 static const char *EXC_CNAME = "java/lang/Exception"; 50 static jvmtiEnv *jvmti = NULL; 51 52 static 53 jint throw_exc(JNIEnv *env, char *msg) { 54 jclass exc_class = JNI_ENV_PTR(env)->FindClass(JNI_ENV_ARG(env, EXC_CNAME)); 55 56 if (exc_class == NULL) { 57 printf("throw_exc: Error in FindClass(env, %s)\n", EXC_CNAME); 58 return -1; 59 } 60 return JNI_ENV_PTR(env)->ThrowNew(JNI_ENV_ARG(env, exc_class), msg); 61 } 62 63 static jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved); 64 65 JNIEXPORT 66 jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { 67 return Agent_Initialize(jvm, options, reserved); 68 } 69 70 JNIEXPORT 71 jint JNICALL Agent_OnAttach(JavaVM *jvm, char *options, void *reserved) { 72 return Agent_Initialize(jvm, options, reserved); 73 } 74 75 JNIEXPORT 76 jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { 77 return JNI_VERSION_1_8; 78 } 79 80 JNIEXPORT void JNICALL OnVMInit(jvmtiEnv *jvmti, JNIEnv *jni_env, jthread thread) { 81 (*jvmti)->StartHeapSampling(jvmti, 1<<19, MAX_TRACES); 82 } 83 84 JNIEXPORT void JNICALL OnClassLoad(jvmtiEnv *jvmti_env, JNIEnv *jni_env, 85 jthread thread, jclass klass) { 86 // NOP. 87 } 88 89 JNIEXPORT void JNICALL OnClassPrepare(jvmtiEnv *jvmti_env, JNIEnv *jni_env, 90 jthread thread, jclass klass) { 91 // We need to do this to "prime the pump", as it were -- make sure 92 // that all of the methodIDs have been initialized internally, for 93 // AsyncGetCallTrace. 94 jint method_count; 95 jmethodID *methods = 0; 96 jvmtiError err = (*jvmti)->GetClassMethods(jvmti, klass, &method_count, &methods); 97 if ((err != JVMTI_ERROR_NONE) && (err != JVMTI_ERROR_CLASS_NOT_PREPARED)) { 98 // JVMTI_ERROR_CLASS_NOT_PREPARED is okay because some classes may 99 // be loaded but not prepared at this point. 100 throw_exc(jni_env, "Failed to create method IDs for methods in class\n"); 101 } 102 } 103 104 static 105 jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) { 106 jint res; 107 108 res = JNI_ENV_PTR(jvm)->GetEnv(JNI_ENV_ARG(jvm, (void **) &jvmti), 109 JVMTI_VERSION_9); 110 if (res != JNI_OK || jvmti == NULL) { 111 printf(" Error: wrong result of a valid call to GetEnv!\n"); 112 return JNI_ERR; 113 } 114 115 jvmtiEventCallbacks callbacks; 116 memset(&callbacks, 0, sizeof(callbacks)); 117 118 callbacks.VMInit = &OnVMInit; 119 callbacks.ClassLoad = &OnClassLoad; 120 callbacks.ClassPrepare = &OnClassPrepare; 121 122 jvmtiCapabilities caps; 123 memset(&caps, 0, sizeof(caps)); 124 // Get line numbers and filename for the test. 125 caps.can_get_line_numbers = 1; 126 caps.can_get_source_file_name = 1; 127 int ernum = (*jvmti)->AddCapabilities(jvmti, &caps); 128 129 ernum = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(jvmtiEventCallbacks)); 130 ernum = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL); 131 ernum = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, NULL); 132 ernum = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, NULL); 133 134 return JNI_OK; 135 } 136 137 // Given a method and a location, this method gets the line number. 138 // Kind of expensive, comparatively. 139 jint GetLineNumber(jvmtiEnv *jvmti, jmethodID method, jlocation location) { 140 // The location is -1 if the bci isn't known or -3 for a native method. 141 if (location == -1 || location == -3) { 142 return -1; 143 } 144 145 // Read the line number table. 146 jvmtiLineNumberEntry *table_ptr = 0; 147 jint line_number_table_entries; 148 int jvmti_error = (*jvmti)->GetLineNumberTable(jvmti, method, 149 &line_number_table_entries, 150 &table_ptr); 151 152 if (JVMTI_ERROR_NONE != jvmti_error) { 153 return -1; 154 } 155 if (line_number_table_entries <= 0) { 156 return -1; 157 } 158 if (line_number_table_entries == 1) { 159 return table_ptr[0].line_number; 160 } 161 162 // Go through all the line numbers... 163 jint last_location = table_ptr[0].start_location; 164 int l; 165 for (l = 1; l < line_number_table_entries; l++) { 166 // ... and if you see one that is in the right place for your 167 // location, you've found the line number! 168 if ((location < table_ptr[l].start_location) && 169 (location >= last_location)) { 170 return table_ptr[l - 1].line_number; 171 } 172 last_location = table_ptr[l].start_location; 173 } 174 175 if (location >= last_location) { 176 return table_ptr[line_number_table_entries - 1].line_number; 177 } else { 178 return -1; 179 } 180 } 181 182 typedef struct _ExpectedContentFrame { 183 char *name; 184 char *signature; 185 char *file_name; 186 int line_number; 187 } ExpectedContentFrame; 188 189 static jint CheckSampleContent(JNIEnv *env, 190 jvmtiStackTrace *trace, 191 ExpectedContentFrame *expected, 192 int expected_count) { 193 int i; 194 195 if (expected_count > trace->frame_count) { 196 return FAILED; 197 } 198 199 for (i = 0; i < expected_count; i++) { 200 // Get basic information out of the trace. 201 int bci = trace->frames[i].bci; 202 jmethodID methodid = trace->frames[i].method_id; 203 char *name = NULL, *signature = NULL, *file_name = NULL; 204 205 if (bci < 0) { 206 return FAILED; 207 } 208 209 // Transform into usable information. 210 int line_number = GetLineNumber(jvmti, methodid, bci); 211 (*jvmti)->GetMethodName(jvmti, methodid, &name, &signature, 0); 212 213 jclass declaring_class; 214 if (JVMTI_ERROR_NONE != 215 (*jvmti)->GetMethodDeclaringClass(jvmti, methodid, &declaring_class)) { 216 return FAILED; 217 } 218 219 jvmtiError err = (*jvmti)->GetSourceFileName(jvmti, declaring_class, 220 &file_name); 221 if (err != JVMTI_ERROR_NONE) { 222 return FAILED; 223 } 224 225 // Compare now, none should be NULL. 226 if (name == NULL) { 227 return FAILED; 228 } 229 230 if (file_name == NULL) { 231 return FAILED; 232 } 233 234 if (signature == NULL) { 235 return FAILED; 236 } 237 238 if (strcmp(name, expected[i].name) || 239 strcmp(signature, expected[i].signature) || 240 strcmp(file_name, expected[i].file_name) || 241 line_number != expected[i].line_number) { 242 return FAILED; 243 } 244 } 245 246 return PASSED; 247 } 248 249 static jint CheckSamples(JNIEnv* env, jvmtiStackTrace* traces, int trace_count) { 250 // Only look at the first few frames, underneath we go under main and it is 251 // less interesting. 252 253 // TODO: There is an issue with the stacktrace walker and I think it is 254 // getting confused, so this is just to show what the test will look like. 255 // Basically the line 61 is not yet accuracte, I have a fix for it but need to 256 // double check. 257 // The full system is not yet functional. 258 ExpectedContentFrame expected_contents[] = { 259 { "helper", "()I", "HeapMonitorTest.java", 61 }, 260 { "main", "([Ljava/lang/String;)V", "HeapMonitorTest.java", 69 }, 261 }; 262 263 const int expected_contents_count = 264 sizeof(expected_contents) / sizeof(expected_contents[0]); 265 266 // We also expect the code to record correctly the BCI, retrieve the line 267 // number, have the right method and the class name of the first frames. 268 int i; 269 for (i = 0; i < trace_count; i++) { 270 jvmtiStackTrace *trace = traces + i; 271 int j; 272 for (j = 0; j < trace->frame_count; j++) { 273 // Get basic information out of the trace. 274 int bci = trace->frames[j].bci; 275 jmethodID methodid = trace->frames[j].method_id; 276 char *name = NULL, *signature = NULL, *file_name = NULL; 277 278 // Transform into usable information. 279 int line_number = GetLineNumber(jvmti, methodid, bci); 280 (*jvmti)->GetMethodName(jvmti, methodid, &name, &signature, 0); 281 282 jclass declaring_class = 0; 283 (*jvmti)->GetMethodDeclaringClass(jvmti, methodid, &declaring_class); 284 285 if (declaring_class) { 286 jvmtiError err = (*jvmti)->GetSourceFileName(jvmti, declaring_class, &file_name); 287 } 288 } 289 } 290 291 for (i = 0; i < trace_count; i++) { 292 jvmtiStackTrace *trace = traces + i; 293 if (CheckSampleContent(env, trace, expected_contents, 294 expected_contents_count) == PASSED) { 295 // At least one frame matched what we were looking for. 296 return PASSED; 297 } 298 } 299 300 return FAILED; 301 } 302 303 static jint GetTraces(JNIEnv* env) { 304 jvmtiStackTraces full_traces; 305 jvmtiStackTraces garbage_traces; 306 jvmtiStackTraces frequent_garbage_traces; 307 308 // Call the JVMTI interface to get the traces. 309 (*jvmti)->GetLiveTraces(jvmti, &full_traces); 310 (*jvmti)->GetGarbageTraces(jvmti, &garbage_traces); 311 (*jvmti)->GetFrequentGarbageTraces(jvmti, &frequent_garbage_traces); 312 313 if (CheckSamples(env, full_traces.stack_traces, 314 full_traces.trace_count) == FAILED) { 315 return FAILED; 316 } 317 318 // Currently we just test the garbage contains also some similar stacktraces. 319 if (CheckSamples(env, garbage_traces.stack_traces, 320 garbage_traces.trace_count) == FAILED) { 321 return FAILED; 322 } 323 324 // Currently we just test the frequent garbage contains also some similar 325 // stacktraces. 326 if (CheckSamples(env, frequent_garbage_traces.stack_traces, 327 frequent_garbage_traces.trace_count) == FAILED) { 328 return FAILED; 329 } 330 331 // Release Traces. 332 (*jvmti)->ReleaseTraces(jvmti, &full_traces); 333 334 return PASSED; 335 } 336 337 JNIEXPORT jint JNICALL 338 Java_MyPackage_HeapMonitorTest_check(JNIEnv *env) { 339 jobject loader = NULL; 340 341 if (jvmti == NULL) { 342 throw_exc(env, "JVMTI client was not properly loaded!\n"); 343 return FAILED; 344 } 345 346 return GetTraces(env); 347 } 348 349 #ifdef __cplusplus 350 } 351 #endif