1 /*
   2  * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
   3  * Copyright (c) 2020, NTT DATA.
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * This code is free software; you can redistribute it and/or modify it
   7  * under the terms of the GNU General Public License version 2 only, as
   8  * published by the Free Software Foundation.
   9  *
  10  * This code is distributed in the hope that it will be useful, but WITHOUT
  11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  13  * version 2 for more details (a copy is included in the LICENSE file that
  14  * accompanied this code).
  15  *
  16  * You should have received a copy of the GNU General Public License version
  17  * 2 along with this work; if not, write to the Free Software Foundation,
  18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  19  *
  20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  21  * or visit www.oracle.com if you need additional information or have any
  22  * questions.
  23  */
  24 
  25 #include <jni.h>
  26 #include <jvmti.h>
  27 #include <stdio.h>
  28 
  29 #define MAX_FRAMES 100
  30 #define EXCEPTION_MSG_LEN 1024
  31 
  32 #ifdef __cplusplus
  33 extern "C" {
  34 #endif
  35 
  36 static jvmtiEnv *jvmti = NULL;
  37 static jmethodID Thread_getId;
  38 
  39 JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
  40   return (*jvm)->GetEnv(jvm, (void**) &jvmti, JVMTI_VERSION_11);
  41 }
  42 
  43 static jboolean is_same_thread(JNIEnv *env, jthread th1, jthread th2) {
  44   jlong th1_id, th2_id;
  45 
  46   th1_id = (*env)->CallLongMethod(env, th1, Thread_getId);
  47   th2_id = (*env)->CallLongMethod(env, th2, Thread_getId);
  48 
  49   return (th1_id == th2_id) ? JNI_TRUE : JNI_FALSE;
  50 }
  51 
  52 JNIEXPORT void JNICALL Java_OneGetThreadListStackTraces_checkCallStacks(JNIEnv *env, jclass cls, jthread thread) {
  53   jvmtiStackInfo *stack_info, *target_info, *target_one_info;
  54   jint num_threads;
  55   jvmtiError result;
  56   jclass exception_class, thread_class;
  57   char exception_msg[EXCEPTION_MSG_LEN] = {0};
  58   jint i;
  59 
  60   exception_class = (*env)->FindClass(env, "java/lang/RuntimeException");
  61   thread_class = (*env)->FindClass(env, "java/lang/Thread");
  62   Thread_getId = (*env)->GetMethodID(env, thread_class, "getId", "()J");
  63 
  64   /* Get all stack traces */
  65   result = (*jvmti)->GetAllStackTraces(jvmti, MAX_FRAMES,
  66                                        &stack_info, &num_threads);
  67   if (result != JVMTI_ERROR_NONE) {
  68     snprintf(exception_msg, sizeof(exception_msg),
  69              "GetAllStackTraces(): result = %d", result);
  70     (*env)->ThrowNew(env, exception_class, exception_msg);
  71     return;
  72   }
  73 
  74   /* Find jvmtiStackInfo for `thread` (in arguments) */
  75   target_info = NULL;
  76   for (i = 0; i < num_threads; i++) {
  77     if (is_same_thread(env, stack_info[i].thread, thread)) {
  78       target_info = &stack_info[i];
  79       break;
  80     }
  81   }
  82   if (target_info == NULL) {
  83     (*jvmti)->Deallocate(jvmti, (unsigned char *)stack_info);
  84     (*env)->ThrowNew(env, exception_class, "Target thread not found");
  85     return;
  86   }
  87 
  88   /*
  89    * Get jvmtiStackInfo via GetThreadListStackTraces().
  90    * It expects to perform in Thread Local Handshake because thread count is 1.
  91    */
  92   result = (*jvmti)->GetThreadListStackTraces(jvmti, 1, &thread,
  93                                               MAX_FRAMES, &target_one_info);
  94   if (result != JVMTI_ERROR_NONE) {
  95     snprintf(exception_msg, sizeof(exception_msg),
  96              "GetThreadListStackTraces(): result = %d", result);
  97     (*jvmti)->Deallocate(jvmti, (unsigned char *)stack_info);
  98     (*env)->ThrowNew(env, exception_class, exception_msg);
  99     return;
 100   }
 101 
 102   if (!is_same_thread(env, target_info->thread, target_one_info->thread)) { /* jvmtiStackinfo::thread */
 103     snprintf(exception_msg, sizeof(exception_msg),
 104              "thread is different: target_info = %p, target_one_info = %p",
 105              target_info->thread, target_one_info->thread);
 106   } else if (target_info->state != target_one_info->state) { /* jvmtiStackInfo::state */
 107     snprintf(exception_msg, sizeof(exception_msg),
 108              "state is different: target_info = %d, target_one_info = %d",
 109              target_info->state, target_one_info->state);
 110   } else if (target_info->frame_count != target_one_info->frame_count) { /* jvmtiStackInfo::frame_count */
 111     snprintf(exception_msg, sizeof(exception_msg),
 112              "frame_count is different: target_info = %d, target_one_info = %d",
 113              target_info->frame_count, target_one_info->frame_count);
 114   } else {
 115     /* Iterate all jvmtiFrameInfo to check */
 116     for (i = 0; i < target_info->frame_count; i++) {
 117       if (target_info->frame_buffer[i].method != target_one_info->frame_buffer[i].method) { /* jvmtiFrameInfo::method */
 118         snprintf(exception_msg, sizeof(exception_msg),
 119                  "frame_buffer[%d].method is different: target_info = %lx, target_one_info = %lx",
 120                  i, target_info->frame_buffer[i].method, target_one_info->frame_buffer[i].method);
 121         break;
 122       } else if (target_info->frame_buffer[i].location != target_one_info->frame_buffer[i].location) { /* jvmtiFrameInfo::location */
 123         snprintf(exception_msg, sizeof(exception_msg),
 124                  "frame_buffer[%d].location is different: target_info = %ld, target_one_info = %ld",
 125                  i, target_info->frame_buffer[i].location, target_one_info->frame_buffer[i].location);
 126         break;
 127       }
 128     }
 129   }
 130 
 131   (*jvmti)->Deallocate(jvmti, (unsigned char *)stack_info);
 132   (*jvmti)->Deallocate(jvmti, (unsigned char *)target_one_info);
 133 
 134   /* If error message is not empty, it is thrown as RuntimeException */
 135   if (*exception_msg != '\0') {
 136     (*env)->ThrowNew(env, exception_class, exception_msg);
 137   }
 138 }
 139 
 140 #ifdef __cplusplus
 141 }
 142 #endif