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 const char *name; 184 const char *signature; 185 const 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 0; 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 0; 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 0; 217 } 218 219 jvmtiError err = (*jvmti)->GetSourceFileName(jvmti, declaring_class, 220 &file_name); 221 if (err != JVMTI_ERROR_NONE) { 222 return 0; 223 } 224 225 // Compare now, none should be NULL. 226 if (name == NULL) { 227 return 0; 228 } 229 230 if (file_name == NULL) { 231 return 0; 232 } 233 234 if (signature == NULL) { 235 return 0; 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 0; 243 } 244 } 245 246 return 1; 247 } 248 249 static jint compare_samples(JNIEnv* env, jvmtiStackTrace* traces, int trace_count, 250 ExpectedContentFrame* expected_content, size_t size) { 251 // We expect the code to record correctly the bci, retrieve the line 252 // number, have the right method and the class name of the first frames. 253 int i; 254 for (i = 0; i < trace_count; i++) { 255 jvmtiStackTrace *trace = traces + i; 256 if (CheckSampleContent(env, trace, expected_content, size)) { 257 // At least one frame matched what we were looking for. 258 return 1; 259 } 260 } 261 262 return 0; 263 } 264 265 static jint check_samples(JNIEnv* env, ExpectedContentFrame* expected, 266 size_t size, 267 jvmtiError (*const get_traces)(jvmtiEnv*, jvmtiStackTraces*)) { 268 jvmtiStackTraces traces; 269 get_traces(jvmti, &traces); 270 int result = compare_samples(env, traces.stack_traces, traces.trace_count, 271 expected, size); 272 (*jvmti)->ReleaseTraces(jvmti, &traces); 273 return result; 274 } 275 276 static jint frames_exist_live(JNIEnv* env, ExpectedContentFrame* expected, 277 size_t size) { 278 return check_samples(env, expected, size, (*jvmti)->GetLiveTraces); 279 } 280 281 static jint frames_exist_recent(JNIEnv* env, ExpectedContentFrame* expected, 282 size_t size) { 283 return check_samples(env, expected, size, (*jvmti)->GetGarbageTraces); 284 } 285 286 static jint frames_exist_frequent(JNIEnv* env, ExpectedContentFrame* expected, 287 size_t size) { 288 return check_samples(env, expected, size, (*jvmti)->GetFrequentGarbageTraces); 289 } 290 291 // Static native API for various tests. 292 static void fill_native_frames(JNIEnv* env, jobjectArray frames, 293 ExpectedContentFrame* native_frames, size_t size) { 294 size_t i; 295 for(i = 0; i < size; i++) { 296 jobject obj = (*env)->GetObjectArrayElement(env, frames, i); 297 jclass frame_class = (*env)->GetObjectClass(env, obj); 298 jfieldID line_number_field_id = (*env)->GetFieldID(env, frame_class, "lineNumber", "I"); 299 int line_number = (*env)->GetIntField(env, obj, line_number_field_id); 300 301 jfieldID string_id = (*env)->GetFieldID(env, frame_class, "method", "Ljava/lang/String;"); 302 jstring string_object = (jstring) (*env)->GetObjectField(env, obj, string_id); 303 const char* method = (*env)->GetStringUTFChars(env, string_object, 0); 304 305 string_id = (*env)->GetFieldID(env, frame_class, "fileName", "Ljava/lang/String;"); 306 string_object = (jstring) (*env)->GetObjectField(env, obj, string_id); 307 const char* file_name = (*env)->GetStringUTFChars(env, string_object, 0); 308 309 string_id = (*env)->GetFieldID(env, frame_class, "signature", "Ljava/lang/String;"); 310 string_object = (jstring) (*env)->GetObjectField(env, obj, string_id); 311 const char* signature= (*env)->GetStringUTFChars(env, string_object, 0); 312 313 native_frames[i].name = method; 314 native_frames[i].file_name = file_name; 315 native_frames[i].signature = signature; 316 native_frames[i].line_number = line_number; 317 } 318 } 319 320 static jint checkAll(JNIEnv *env, jobjectArray frames) { 321 jobject loader = NULL; 322 323 if (frames == NULL) { 324 return 0; 325 } 326 327 // Start by transforming the frames into a C-friendly structure. 328 jsize size = (*env)->GetArrayLength(env, frames); 329 ExpectedContentFrame native_frames[size]; 330 fill_native_frames(env, frames, native_frames, size); 331 332 if (jvmti == NULL) { 333 throw_exc(env, "JVMTI client was not properly loaded!\n"); 334 return 0; 335 } 336 337 if (frames_exist_live(env, native_frames, size) && 338 frames_exist_recent(env, native_frames, size) && 339 frames_exist_frequent(env, native_frames, size)) { 340 return 1; 341 } 342 return 0; 343 } 344 345 static jint checkNone(JNIEnv *env, jobjectArray frames) { 346 jobject loader = NULL; 347 348 if (frames == NULL) { 349 return 0; 350 } 351 352 // Start by transforming the frames into a C-friendly structure. 353 jsize size = (*env)->GetArrayLength(env, frames); 354 ExpectedContentFrame native_frames[size]; 355 fill_native_frames(env, frames, native_frames, size); 356 357 if (jvmti == NULL) { 358 throw_exc(env, "JVMTI client was not properly loaded!\n"); 359 return 0; 360 } 361 362 if ((!frames_exist_live(env, native_frames, size)) && 363 (!frames_exist_recent(env, native_frames, size)) && 364 (!frames_exist_frequent(env, native_frames, size))) { 365 return 1; 366 } 367 return 0; 368 } 369 370 static void enable_sampling() { 371 (*jvmti)->StartHeapSampling(jvmti, 1<<19, MAX_TRACES); 372 } 373 374 static void disable_sampling() { 375 (*jvmti)->StopHeapSampling(jvmti); 376 } 377 378 // HeapMonitorTest JNI. 379 JNIEXPORT jint JNICALL 380 Java_MyPackage_HeapMonitorTest_checkFrames(JNIEnv *env, jclass cls, jobjectArray frames) { 381 // We want the frames in each part. 382 if (!checkAll(env, frames)) { 383 return FAILED; 384 } 385 return PASSED; 386 } 387 388 JNIEXPORT void JNICALL 389 Java_MyPackage_HeapMonitorTest_enableSampling(JNIEnv *env, jclass cls) { 390 enable_sampling(); 391 } 392 393 // HeapMonitorOnOffTest JNI. 394 JNIEXPORT jint JNICALL 395 Java_MyPackage_HeapMonitorOnOffTest_checkFrames(JNIEnv *env, jclass cls, jobjectArray frames) { 396 // We want the frames in each part. 397 if (!checkAll(env, frames)) { 398 return FAILED; 399 } 400 return PASSED; 401 } 402 403 JNIEXPORT jint JNICALL 404 Java_MyPackage_HeapMonitorOnOffTest_checkWipeOut(JNIEnv *env, jclass cls, jobjectArray frames) { 405 // We want the frames in none of the parts. 406 if (!checkNone(env, frames)) { 407 return FAILED; 408 } 409 return PASSED; 410 } 411 412 JNIEXPORT void JNICALL 413 Java_MyPackage_HeapMonitorOnOffTest_enableSampling(JNIEnv *env, jclass cls) { 414 enable_sampling(); 415 } 416 417 JNIEXPORT void JNICALL 418 Java_MyPackage_HeapMonitorOnOffTest_disableSampling(JNIEnv *env, jclass cls) { 419 disable_sampling(); 420 } 421 422 #ifdef __cplusplus 423 } 424 #endif