/* * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, NTT DATA. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #include #include #include #include #include #define MAX_FRAMES 100 #define EXCEPTION_MSG_LEN 1024 #ifdef __cplusplus extern "C" { #endif static jvmtiEnv *jvmti = NULL; static jmethodID Thread_getId; JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { return (*jvm)->GetEnv(jvm, (void**) &jvmti, JVMTI_VERSION_11); } static jboolean is_same_thread(JNIEnv *env, jthread th1, jthread th2) { jlong th1_id, th2_id; jboolean result = JNI_FALSE; th1_id = (*env)->CallLongMethod(env, th1, Thread_getId); if (!(*env)->ExceptionOccurred(env)) { th2_id = (*env)->CallLongMethod(env, th2, Thread_getId); if (th1_id == th2_id) { result = JNI_TRUE; } } if ((*env)->ExceptionOccurred(env)) { (*env)->ExceptionDescribe(env); (*env)->FatalError(env, __FILE__); } return result; } JNIEXPORT void JNICALL Java_GetThreadListStackTraces_checkCallStacks(JNIEnv *env, jclass cls) { jvmtiStackInfo *stack_info_1, *stack_info_2; jthread *jthreads; jint num_threads; jvmtiError result; jclass thread_class; char exception_msg[EXCEPTION_MSG_LEN] = {0}; jint i, j; thread_class = (*env)->FindClass(env, "java/lang/Thread"); if ((*env)->ExceptionOccurred(env)) { return; } Thread_getId = (*env)->GetMethodID(env, thread_class, "getId", "()J"); if ((*env)->ExceptionOccurred(env)) { return; } /* Get all stack traces to compare */ result = (*jvmti)->GetAllStackTraces(jvmti, MAX_FRAMES, &stack_info_1, &num_threads); if (result != JVMTI_ERROR_NONE) { snprintf(exception_msg, sizeof(exception_msg), "GetAllStackTraces(): result = %d", result); (*env)->FatalError(env, exception_msg); } /* Create jthread array to pass GetThreadListStackTraces() */ jthreads = (jthread *)malloc(sizeof(jthread) * num_threads); if (jthreads == NULL) { snprintf(exception_msg, sizeof(exception_msg), "malloc(): errno = %d", errno); (*env)->FatalError(env, exception_msg); } for (i = 0; i < num_threads; i++) { jthreads[i] = stack_info_1[i].thread; } /* Get all stack traces from GetThreadListStackTraces() (Test target) */ result = (*jvmti)->GetThreadListStackTraces(jvmti, num_threads, jthreads, MAX_FRAMES, &stack_info_2); if (result != JVMTI_ERROR_NONE) { snprintf(exception_msg, sizeof(exception_msg), "GetThreadListStackTraces(): result = %d", result); (*env)->FatalError(env, exception_msg); } /* Iterate all jvmtiStackInfo to check */ for (i = 0; i < num_threads, *exception_msg != '\0'; i++) { if (!is_same_thread(env, stack_info_1[i].thread, stack_info_2[i].thread)) { /* jvmtiStackInfo::thread */ snprintf(exception_msg, sizeof(exception_msg), "thread[%d] is different: stack_info_1 = %p, stack_info_2 = %p", i, stack_info_1[i].thread, stack_info_2[i].thread); } else if (stack_info_1[i].state != stack_info_2[i].state) { /* jvmtiStackInfo::state */ snprintf(exception_msg, sizeof(exception_msg), "state[%d] is different: stack_info_1 = %d, stack_info_2 = %d", i, stack_info_1[i].state, stack_info_2[i].state); } else if (stack_info_1[i].frame_count != stack_info_2[i].frame_count) { /* jvmtiStackInfo::frame_count */ snprintf(exception_msg, sizeof(exception_msg), "frame_count[%d] is different: stack_info_1 = %d, stack_info_2 = %d", i, stack_info_1[i].frame_count, stack_info_2[i].frame_count); } else { /* Iterate all jvmtiFrameInfo to check */ for (j = 0; j < stack_info_1[i].frame_count; j++) { if (stack_info_1[i].frame_buffer[j].method != stack_info_2[i].frame_buffer[j].method) { /* jvmtiFrameInfo::method */ snprintf(exception_msg, sizeof(exception_msg), "thread [%d] frame_buffer[%d].method is different: stack_info_1 = %lx, stack_info_2 = %lx", i, j, stack_info_1[i].frame_buffer[j].method, stack_info_1[i].frame_buffer[j].method); break; } else if (stack_info_1[i].frame_buffer[j].location != stack_info_2[i].frame_buffer[j].location) { /* jvmtiFrameInfo::location */ snprintf(exception_msg, sizeof(exception_msg), "thread [%d] frame_buffer[%d].location is different: stack_info_1 = %ld, stack_info_2 = %ld", i, j, stack_info_1[i].frame_buffer[j].location, stack_info_1[i].frame_buffer[j].location); break; } } } } /* If error message is not empty, it is thrown as RuntimeException */ if (*exception_msg != '\0') { (*env)->FatalError(env, exception_msg); } free(jthreads); (*jvmti)->Deallocate(jvmti, (unsigned char *)stack_info_1); (*jvmti)->Deallocate(jvmti, (unsigned char *)stack_info_2); } #ifdef __cplusplus } #endif