--- old/src/hotspot/share/prims/jvmtiEnv.cpp 2020-06-30 08:44:30.510000000 +0900 +++ new/src/hotspot/share/prims/jvmtiEnv.cpp 2020-06-30 08:44:30.440000000 +0900 @@ -1535,15 +1535,14 @@ jvmtiError err = JVMTI_ERROR_NONE; // It is only safe to perform the direct operation on the current - // thread. All other usage needs to use a vm-safepoint-op for safety. + // thread. All other usage needs to use a direct handshake for safety. if (java_thread == JavaThread::current()) { err = get_stack_trace(java_thread, start_depth, max_frame_count, frame_buffer, count_ptr); } else { - // JVMTI get stack trace at safepoint. Do not require target thread to - // be suspended. - VM_GetStackTrace op(this, java_thread, start_depth, max_frame_count, frame_buffer, count_ptr); - VMThread::execute(&op); - err = op.result(); + // Get stack trace with handshake + GetStackTraceClosure op(this, start_depth, max_frame_count, frame_buffer, count_ptr); + bool executed = Handshake::execute_direct(&op, java_thread); + err = executed ? op.result() : JVMTI_ERROR_THREAD_NOT_ALIVE; } return err; @@ -1575,11 +1574,26 @@ jvmtiError JvmtiEnv::GetThreadListStackTraces(jint thread_count, const jthread* thread_list, jint max_frame_count, jvmtiStackInfo** stack_info_ptr) { jvmtiError err = JVMTI_ERROR_NONE; - // JVMTI get stack traces at safepoint. - VM_GetThreadListStackTraces op(this, thread_count, thread_list, max_frame_count); - VMThread::execute(&op); - err = op.result(); - if (err == JVMTI_ERROR_NONE) { + + if (thread_count == 1) { + // Use direct handshake if we need to get only one stack trace. + JavaThread *current_thread = JavaThread::current(); + ThreadsListHandle tlh(current_thread); + JavaThread *target_javathread; + err = JvmtiExport::cv_external_thread_to_JavaThread(tlh.list(), *thread_list, &target_javathread, NULL); + if (err != JVMTI_ERROR_NONE) { + return err; + } + + GetSingleStackTraceClosure op(this, current_thread, max_frame_count); + bool executed = Handshake::execute_direct(&op, target_javathread); + err = executed ? op.result() : JVMTI_ERROR_THREAD_NOT_ALIVE; + *stack_info_ptr = op.stack_info(); + } else { + // JVMTI get stack traces at safepoint. + VM_GetThreadListStackTraces op(this, thread_count, thread_list, max_frame_count); + VMThread::execute(&op); + err = op.result(); *stack_info_ptr = op.stack_info(); } return err; --- old/src/hotspot/share/prims/jvmtiEnvBase.cpp 2020-06-30 08:44:30.850000000 +0900 +++ new/src/hotspot/share/prims/jvmtiEnvBase.cpp 2020-06-30 08:44:30.780000000 +0900 @@ -816,13 +816,14 @@ #ifdef ASSERT uint32_t debug_bits = 0; #endif - assert((SafepointSynchronize::is_at_safepoint() || - java_thread->is_thread_fully_suspended(false, &debug_bits)), - "at safepoint or target thread is suspended"); + Thread *current_thread = Thread::current(); + assert(SafepointSynchronize::is_at_safepoint() || + java_thread->is_thread_fully_suspended(false, &debug_bits) || + current_thread == java_thread->active_handshaker(), + "at safepoint / handshake or target thread is suspended"); int count = 0; if (java_thread->has_last_Java_frame()) { RegisterMap reg_map(java_thread); - Thread* current_thread = Thread::current(); ResourceMark rm(current_thread); javaVFrame *jvf = java_thread->last_java_vframe(®_map); HandleMark hm(current_thread); @@ -1154,8 +1155,10 @@ // Note that either or both of thr and thread_oop // may be null if the thread is new or has exited. void -VM_GetMultipleStackTraces::fill_frames(jthread jt, JavaThread *thr, oop thread_oop) { - assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint"); +MultipleStackTracesCollector::fill_frames(jthread jt, JavaThread *thr, oop thread_oop) { + assert(SafepointSynchronize::is_at_safepoint() || + Thread::current() == thr->active_handshaker(), + "must be at safepoint or at direct handshake"); jint state = 0; struct StackInfoNode *node = NEW_RESOURCE_OBJ(struct StackInfoNode); @@ -1199,7 +1202,7 @@ // Based on the stack information in the linked list, allocate memory // block to return and fill it from the info in the linked list. void -VM_GetMultipleStackTraces::allocate_and_fill_stacks(jint thread_count) { +MultipleStackTracesCollector::allocate_and_fill_stacks(jint thread_count) { // do I need to worry about alignment issues? jlong alloc_size = thread_count * sizeof(jvmtiStackInfo) + _frame_count_total * sizeof(jvmtiFrameInfo); @@ -1248,14 +1251,29 @@ // only return an error from here if we didn't get a valid // thread_oop. if (thread_oop == NULL) { - set_result(err); + _collector.set_result(err); return; } // We have a valid thread_oop. } - fill_frames(jt, java_thread, thread_oop); + _collector.fill_frames(jt, java_thread, thread_oop); + } + _collector.allocate_and_fill_stacks(_thread_count); +} + +void +GetSingleStackTraceClosure::do_thread(Thread *target) { + assert(target->is_Java_thread(), "just checking"); + JavaThread *jt = (JavaThread *)target; + oop thread_oop = jt->threadObj(); + + if (!jt->is_exiting() && (jt->threadObj() != NULL)) { + ResourceMark rm; + _collector.fill_frames((jthread)JNIHandles::make_local(_calling_thread, thread_oop), + jt, thread_oop); + _collector.allocate_and_fill_stacks(1); + _collector.set_result(JVMTI_ERROR_NONE); } - allocate_and_fill_stacks(_thread_count); } void @@ -1272,11 +1290,11 @@ !jt->is_hidden_from_external_view()) { ++_final_thread_count; // Handle block of the calling thread is used to create local refs. - fill_frames((jthread)JNIHandles::make_local(_calling_thread, thread_oop), - jt, thread_oop); + _collector.fill_frames((jthread)JNIHandles::make_local(_calling_thread, thread_oop), + jt, thread_oop); } } - allocate_and_fill_stacks(_final_thread_count); + _collector.allocate_and_fill_stacks(_final_thread_count); } // Verifies that the top frame is a java frame in an expected state. @@ -1528,12 +1546,11 @@ } void -VM_GetStackTrace::doit() { - _result = JVMTI_ERROR_THREAD_NOT_ALIVE; - ThreadsListHandle tlh; - if (_java_thread != NULL && tlh.includes(_java_thread) - && !_java_thread->is_exiting() && _java_thread->threadObj() != NULL) { - _result = ((JvmtiEnvBase *)_env)->get_stack_trace(_java_thread, +GetStackTraceClosure::do_thread(Thread *target) { + assert(target->is_Java_thread(), "just checking"); + JavaThread *jt = (JavaThread *)target; + if (!jt->is_exiting() && (jt->threadObj() != NULL)) { + _result = ((JvmtiEnvBase *)_env)->get_stack_trace(jt, _start_depth, _max_count, _frame_buffer, _count_ptr); } --- old/src/hotspot/share/prims/jvmtiEnvBase.hpp 2020-06-30 08:44:31.150000000 +0900 +++ new/src/hotspot/share/prims/jvmtiEnvBase.hpp 2020-06-30 08:44:31.060000000 +0900 @@ -437,11 +437,10 @@ void do_thread(Thread *target); }; -// VM operation to get stack trace at safepoint. -class VM_GetStackTrace : public VM_Operation { +// HandshakeClosure to get stack trace. +class GetStackTraceClosure : public HandshakeClosure { private: JvmtiEnv *_env; - JavaThread *_java_thread; jint _start_depth; jint _max_count; jvmtiFrameInfo *_frame_buffer; @@ -449,26 +448,22 @@ jvmtiError _result; public: - VM_GetStackTrace(JvmtiEnv *env, JavaThread *java_thread, - jint start_depth, jint max_count, - jvmtiFrameInfo* frame_buffer, jint* count_ptr) { - _env = env; - _java_thread = java_thread; - _start_depth = start_depth; - _max_count = max_count; - _frame_buffer = frame_buffer; - _count_ptr = count_ptr; + GetStackTraceClosure(JvmtiEnv *env, jint start_depth, jint max_count, + jvmtiFrameInfo* frame_buffer, jint* count_ptr) + : HandshakeClosure("GetStackTrace"), + _env(env), _start_depth(start_depth), _max_count(max_count), + _frame_buffer(frame_buffer), _count_ptr(count_ptr), + _result(JVMTI_ERROR_THREAD_NOT_ALIVE) { } jvmtiError result() { return _result; } - VMOp_Type type() const { return VMOp_GetStackTrace; } - void doit(); + void do_thread(Thread *target); }; // forward declaration struct StackInfoNode; -// VM operation to get stack trace at safepoint. -class VM_GetMultipleStackTraces : public VM_Operation { +// Get stack trace at safepoint or at direct handshake. +class MultipleStackTracesCollector : public StackObj { private: JvmtiEnv *_env; jint _max_frame_count; @@ -482,58 +477,80 @@ struct StackInfoNode *head() { return _head; } void set_head(StackInfoNode *head) { _head = head; } -protected: - void set_result(jvmtiError result) { _result = result; } - void fill_frames(jthread jt, JavaThread *thr, oop thread_oop); - void allocate_and_fill_stacks(jint thread_count); - public: - VM_GetMultipleStackTraces(JvmtiEnv *env, jint max_frame_count) { + MultipleStackTracesCollector(JvmtiEnv *env, jint max_frame_count) { _env = env; _max_frame_count = max_frame_count; _frame_count_total = 0; _head = NULL; + _stack_info = NULL; _result = JVMTI_ERROR_NONE; } - VMOp_Type type() const { return VMOp_GetMultipleStackTraces; } + void set_result(jvmtiError result) { _result = result; } + void fill_frames(jthread jt, JavaThread *thr, oop thread_oop); + void allocate_and_fill_stacks(jint thread_count); jvmtiStackInfo *stack_info() { return _stack_info; } jvmtiError result() { return _result; } }; // VM operation to get stack trace at safepoint. -class VM_GetAllStackTraces : public VM_GetMultipleStackTraces { +class VM_GetAllStackTraces : public VM_Operation { private: JavaThread *_calling_thread; jint _final_thread_count; + MultipleStackTracesCollector _collector; public: VM_GetAllStackTraces(JvmtiEnv *env, JavaThread *calling_thread, jint max_frame_count) - : VM_GetMultipleStackTraces(env, max_frame_count) { - _calling_thread = calling_thread; + : _calling_thread(calling_thread), + _final_thread_count(0), + _collector(env, max_frame_count) { } VMOp_Type type() const { return VMOp_GetAllStackTraces; } void doit(); jint final_thread_count() { return _final_thread_count; } + jvmtiStackInfo *stack_info() { return _collector.stack_info(); } + jvmtiError result() { return _collector.result(); } }; // VM operation to get stack trace at safepoint. -class VM_GetThreadListStackTraces : public VM_GetMultipleStackTraces { +class VM_GetThreadListStackTraces : public VM_Operation { private: jint _thread_count; const jthread* _thread_list; + MultipleStackTracesCollector _collector; public: VM_GetThreadListStackTraces(JvmtiEnv *env, jint thread_count, const jthread* thread_list, jint max_frame_count) - : VM_GetMultipleStackTraces(env, max_frame_count) { - _thread_count = thread_count; - _thread_list = thread_list; + : _thread_count(thread_count), + _thread_list(thread_list), + _collector(env, max_frame_count) { } VMOp_Type type() const { return VMOp_GetThreadListStackTraces; } void doit(); + jvmtiStackInfo *stack_info() { return _collector.stack_info(); } + jvmtiError result() { return _collector.result(); } }; +// HandshakeClosure to get single stack trace. +class GetSingleStackTraceClosure : public HandshakeClosure { +private: + JavaThread *_calling_thread; + MultipleStackTracesCollector _collector; + +public: + GetSingleStackTraceClosure(JvmtiEnv *env, JavaThread *calling_thread, + jint max_frame_count) + : HandshakeClosure("GetSingleStackTrace"), + _calling_thread(calling_thread), + _collector(env, max_frame_count) { + } + void do_thread(Thread *target); + jvmtiStackInfo *stack_info() { return _collector.stack_info(); } + jvmtiError result() { return _collector.result(); } +}; // VM operation to count stack frames at safepoint. class VM_GetFrameCount : public VM_Operation { --- old/src/hotspot/share/runtime/vmOperations.hpp 2020-06-30 08:44:31.440000000 +0900 +++ new/src/hotspot/share/runtime/vmOperations.hpp 2020-06-30 08:44:31.370000000 +0900 @@ -81,8 +81,6 @@ template(UpdateForPopTopFrame) \ template(SetFramePop) \ template(GetObjectMonitorUsage) \ - template(GetStackTrace) \ - template(GetMultipleStackTraces) \ template(GetAllStackTraces) \ template(GetThreadListStackTraces) \ template(GetFrameCount) \ --- /dev/null 2020-06-30 08:37:40.050000000 +0900 +++ new/test/hotspot/jtreg/serviceability/jvmti/GetThreadListStackTraces/GetThreadListStackTraces.java 2020-06-30 08:44:31.650000000 +0900 @@ -0,0 +1,40 @@ +/* + * 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. + */ + +/** + * @test + * @summary Verifies JVMTI GetThreadListStackTraces API + * @library /test/lib + * @run main/othervm/native -agentlib:GetThreadListStackTraces GetThreadListStackTraces + * + */ + +public class GetThreadListStackTraces { + + private static native void checkCallStacks(); + + public static void main(String[] args) throws Exception { + checkCallStacks(); + } +} --- /dev/null 2020-06-30 08:37:40.050000000 +0900 +++ new/test/hotspot/jtreg/serviceability/jvmti/GetThreadListStackTraces/OneGetThreadListStackTraces.java 2020-06-30 08:44:32.030000000 +0900 @@ -0,0 +1,71 @@ +/* + * 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. + */ + +/** + * @test + * @summary Verifies JVMTI GetThreadListStackTraces API with thread_count = 1 + * @library /test/lib + * @run main/othervm/native -agentlib:OneGetThreadListStackTraces OneGetThreadListStackTraces + * + */ + +public class OneGetThreadListStackTraces implements Runnable { + + private Object lock; + + public OneGetThreadListStackTraces(Object lock) { + this.lock = lock; + } + + private static native void checkCallStacks(Thread thread); + + @Override + public void run() { + try { + synchronized (lock) { + lock.wait(); + System.out.println("OK"); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void main(String[] args) throws Exception { + Object lockObj = new Object(); + Thread th = new Thread(new OneGetThreadListStackTraces(lockObj), "test thread"); + th.start(); + + /* Wait until the thread state transits to "waiting" */ + while (th.getState() != Thread.State.WAITING) { + Thread.onSpinWait(); + } + + checkCallStacks(th); + + synchronized (lockObj) { + lockObj.notify(); + } + } +} --- /dev/null 2020-06-30 08:37:40.050000000 +0900 +++ new/test/hotspot/jtreg/serviceability/jvmti/GetThreadListStackTraces/libGetThreadListStackTraces.c 2020-06-30 08:44:32.340000000 +0900 @@ -0,0 +1,136 @@ +/* + * 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 + +#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; + + th1_id = (*env)->CallLongMethod(env, th1, Thread_getId); + th2_id = (*env)->CallLongMethod(env, th2, Thread_getId); + + return (th1_id == th2_id) ? JNI_TRUE : JNI_FALSE; +} + +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 exception_class, thread_class; + char exception_msg[EXCEPTION_MSG_LEN] = {0}; + jint i, j; + + exception_class = (*env)->FindClass(env, "java/lang/RuntimeException"); + thread_class = (*env)->FindClass(env, "java/lang/Thread"); + Thread_getId = (*env)->GetMethodID(env, thread_class, "getId", "()J"); + + /* 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)->ThrowNew(env, exception_class, exception_msg); + return; + } + + /* Create jthread array to pass GetThreadListStackTraces() */ + jthreads = (jthread *)malloc(sizeof(jthread) * num_threads); + 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); + (*jvmti)->Deallocate(jvmti, (unsigned char *)stack_info_1); + (*env)->ThrowNew(env, exception_class, exception_msg); + return; + } + + /* Iterate all jvmtiStackInfo to check */ + for (i = 0; i < num_threads; 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; + } + } + } + } + + (*jvmti)->Deallocate(jvmti, (unsigned char *)stack_info_1); + (*jvmti)->Deallocate(jvmti, (unsigned char *)stack_info_2); + + /* If error message is not empty, it is thrown as RuntimeException */ + if (*exception_msg != '\0') { + (*env)->ThrowNew(env, exception_class, exception_msg); + } +} + +#ifdef __cplusplus +} +#endif --- /dev/null 2020-06-30 08:37:40.050000000 +0900 +++ new/test/hotspot/jtreg/serviceability/jvmti/GetThreadListStackTraces/libOneGetThreadListStackTraces.c 2020-06-30 08:44:32.720000000 +0900 @@ -0,0 +1,142 @@ +/* + * 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 + +#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; + + th1_id = (*env)->CallLongMethod(env, th1, Thread_getId); + th2_id = (*env)->CallLongMethod(env, th2, Thread_getId); + + return (th1_id == th2_id) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL Java_OneGetThreadListStackTraces_checkCallStacks(JNIEnv *env, jclass cls, jthread thread) { + jvmtiStackInfo *stack_info, *target_info, *target_one_info; + jint num_threads; + jvmtiError result; + jclass exception_class, thread_class; + char exception_msg[EXCEPTION_MSG_LEN] = {0}; + jint i; + + exception_class = (*env)->FindClass(env, "java/lang/RuntimeException"); + thread_class = (*env)->FindClass(env, "java/lang/Thread"); + Thread_getId = (*env)->GetMethodID(env, thread_class, "getId", "()J"); + + /* Get all stack traces */ + result = (*jvmti)->GetAllStackTraces(jvmti, MAX_FRAMES, + &stack_info, &num_threads); + if (result != JVMTI_ERROR_NONE) { + snprintf(exception_msg, sizeof(exception_msg), + "GetAllStackTraces(): result = %d", result); + (*env)->ThrowNew(env, exception_class, exception_msg); + return; + } + + /* Find jvmtiStackInfo for `thread` (in arguments) */ + target_info = NULL; + for (i = 0; i < num_threads; i++) { + if (is_same_thread(env, stack_info[i].thread, thread)) { + target_info = &stack_info[i]; + break; + } + } + if (target_info == NULL) { + (*jvmti)->Deallocate(jvmti, (unsigned char *)stack_info); + (*env)->ThrowNew(env, exception_class, "Target thread not found"); + return; + } + + /* + * Get jvmtiStackInfo via GetThreadListStackTraces(). + * It expects to perform in Thread Local Handshake because thread count is 1. + */ + result = (*jvmti)->GetThreadListStackTraces(jvmti, 1, &thread, + MAX_FRAMES, &target_one_info); + if (result != JVMTI_ERROR_NONE) { + snprintf(exception_msg, sizeof(exception_msg), + "GetThreadListStackTraces(): result = %d", result); + (*jvmti)->Deallocate(jvmti, (unsigned char *)stack_info); + (*env)->ThrowNew(env, exception_class, exception_msg); + return; + } + + if (!is_same_thread(env, target_info->thread, target_one_info->thread)) { /* jvmtiStackinfo::thread */ + snprintf(exception_msg, sizeof(exception_msg), + "thread is different: target_info = %p, target_one_info = %p", + target_info->thread, target_one_info->thread); + } else if (target_info->state != target_one_info->state) { /* jvmtiStackInfo::state */ + snprintf(exception_msg, sizeof(exception_msg), + "state is different: target_info = %d, target_one_info = %d", + target_info->state, target_one_info->state); + } else if (target_info->frame_count != target_one_info->frame_count) { /* jvmtiStackInfo::frame_count */ + snprintf(exception_msg, sizeof(exception_msg), + "frame_count is different: target_info = %d, target_one_info = %d", + target_info->frame_count, target_one_info->frame_count); + } else { + /* Iterate all jvmtiFrameInfo to check */ + for (i = 0; i < target_info->frame_count; i++) { + if (target_info->frame_buffer[i].method != target_one_info->frame_buffer[i].method) { /* jvmtiFrameInfo::method */ + snprintf(exception_msg, sizeof(exception_msg), + "frame_buffer[%d].method is different: target_info = %lx, target_one_info = %lx", + i, target_info->frame_buffer[i].method, target_one_info->frame_buffer[i].method); + break; + } else if (target_info->frame_buffer[i].location != target_one_info->frame_buffer[i].location) { /* jvmtiFrameInfo::location */ + snprintf(exception_msg, sizeof(exception_msg), + "frame_buffer[%d].location is different: target_info = %ld, target_one_info = %ld", + i, target_info->frame_buffer[i].location, target_one_info->frame_buffer[i].location); + break; + } + } + } + + (*jvmti)->Deallocate(jvmti, (unsigned char *)stack_info); + (*jvmti)->Deallocate(jvmti, (unsigned char *)target_one_info); + + /* If error message is not empty, it is thrown as RuntimeException */ + if (*exception_msg != '\0') { + (*env)->ThrowNew(env, exception_class, exception_msg); + } +} + +#ifdef __cplusplus +} +#endif