494 lines
20 KiB
C++
494 lines
20 KiB
C++
|
/*
|
||
|
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||
|
* 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 <string.h>
|
||
|
#include "jvmti.h"
|
||
|
#include "jvmti_common.h"
|
||
|
|
||
|
extern "C" {
|
||
|
|
||
|
#define MAX_FRAME_COUNT 30
|
||
|
#define MAX_WORKER_THREADS 10
|
||
|
|
||
|
enum Slots {
|
||
|
SlotInvalid0 = -1,
|
||
|
SlotString = 0,
|
||
|
SlotThread = 1,
|
||
|
SlotInt = 2,
|
||
|
SlotLong = 3,
|
||
|
SlotUnaligned = 4,
|
||
|
SlotFloat = 5,
|
||
|
SlotDouble = 6,
|
||
|
};
|
||
|
|
||
|
typedef struct Values {
|
||
|
jobject msg;
|
||
|
jobject tt;
|
||
|
jint ii;
|
||
|
jlong ll;
|
||
|
jfloat ff;
|
||
|
jdouble dd;
|
||
|
} Values;
|
||
|
|
||
|
static const int MAX_EVENTS_TO_PROCESS = 20;
|
||
|
static jvmtiEnv *jvmti = NULL;
|
||
|
static volatile jboolean completed = JNI_FALSE;
|
||
|
|
||
|
static void
|
||
|
set_breakpoint(JNIEnv *jni, jclass klass, const char *mname, jlocation location) {
|
||
|
// Find the jmethodID of the specified method
|
||
|
jmethodID method = find_method(jvmti, jni, klass, mname);
|
||
|
jvmtiError err;
|
||
|
|
||
|
if (method == NULL) {
|
||
|
LOG("set_breakpoint: Failed to find method %s()\n", mname);
|
||
|
fatal(jni, "set_breakpoint: not found method");
|
||
|
}
|
||
|
err = jvmti->SetBreakpoint(method, location);
|
||
|
check_jvmti_status(jni, err, "set_breakpoint: error in JVMTI SetBreakpoint");
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
clear_breakpoint(JNIEnv *jni, jmethodID method, jlocation location) {
|
||
|
jvmtiError err = jvmti->ClearBreakpoint(method, location);
|
||
|
check_jvmti_status(jni, err, "clear_breakpoint: error in JVMTI ClearBreakpoint");
|
||
|
}
|
||
|
|
||
|
static jint
|
||
|
find_method_depth(jvmtiEnv *jvmti, JNIEnv *jni, jthread vthread, const char *mname) {
|
||
|
jvmtiFrameInfo frames[MAX_FRAME_COUNT];
|
||
|
jint count = -1;
|
||
|
jvmtiError err;
|
||
|
|
||
|
err = jvmti->GetStackTrace(vthread, 0, MAX_FRAME_COUNT, frames, &count);
|
||
|
if (err == JVMTI_ERROR_WRONG_PHASE || err == JVMTI_ERROR_THREAD_NOT_ALIVE) {
|
||
|
return -1; // VM or target thread completed its work
|
||
|
}
|
||
|
check_jvmti_status(jni, err, "find_method_depth: error in JVMTI GetStackTrace");
|
||
|
|
||
|
for (int depth = 0; depth < count; depth++) {
|
||
|
jmethodID method = frames[depth].method;
|
||
|
char* name = NULL;
|
||
|
char* sign = NULL;
|
||
|
|
||
|
err = jvmti->GetMethodName(method, &name, &sign, NULL);
|
||
|
if (err == JVMTI_ERROR_WRONG_PHASE || err == JVMTI_ERROR_THREAD_NOT_ALIVE) {
|
||
|
return -1; // VM or target thread completed its work
|
||
|
}
|
||
|
check_jvmti_status(jni, err, "find_method_depth: error in JVMTI GetMethodName");
|
||
|
|
||
|
if (strcmp(name, mname) == 0) {
|
||
|
return depth;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
test_GetLocal(jvmtiEnv *jvmti, JNIEnv *jni, jthread cthread, jthread vthread,
|
||
|
int depth, int frame_count, Values *exp_values) {
|
||
|
jobject msg = NULL;
|
||
|
jobject tt = NULL;
|
||
|
jint ii = 0;
|
||
|
jlong ll = 0L;
|
||
|
jfloat ff = 0.0;
|
||
|
jdouble dd = 0.0;
|
||
|
jvmtiError err;
|
||
|
|
||
|
LOG("test_GetLocal: mounted: %d depth: %d fcount: %d\n", cthread != NULL, depth, frame_count);
|
||
|
|
||
|
int dep = find_method_depth(jvmti, jni, vthread, "producer");
|
||
|
if (dep == -1) {
|
||
|
fatal(jni, "test_GetLocal: got vthread with no producer(String msg) method");
|
||
|
}
|
||
|
if (dep != depth) {
|
||
|
fatal(jni, "test_GetLocal: got vthread with unexpected depth of producer(String msg) method");
|
||
|
}
|
||
|
|
||
|
// #0: Test JVMTI GetLocalInstance function for carrier thread
|
||
|
if (cthread != NULL) {
|
||
|
err = jvmti->GetLocalInstance(cthread, 3, &msg);
|
||
|
check_jvmti_status(jni, err, "error in JVMTI GetLocalInstance for carrier thread top frame Continuation.run");
|
||
|
LOG("JVMTI GetLocalInstance succeed for carrier thread top frame Continuation.run()\n");
|
||
|
}
|
||
|
|
||
|
// #1: Test JVMTI GetLocalObject function with negative frame depth
|
||
|
err = jvmti->GetLocalObject(vthread, -1, SlotString, &msg);
|
||
|
if (err != JVMTI_ERROR_ILLEGAL_ARGUMENT) {
|
||
|
LOG("JVMTI GetLocalObject with negative frame depth returned error: %d\n", err);
|
||
|
fatal(jni, "JVMTI GetLocalObject with negative frame depth failed to return JVMTI_ERROR_ILLEGAL_ARGUMENT");
|
||
|
}
|
||
|
|
||
|
// #2: Test JVMTI GetLocalObject function with big frame depth
|
||
|
err = jvmti->GetLocalObject(vthread, frame_count, SlotString, &msg);
|
||
|
if (err != JVMTI_ERROR_NO_MORE_FRAMES) {
|
||
|
LOG("JVMTI GetLocalObject with big frame depth returned error: %d\n", err);
|
||
|
fatal(jni, "JVMTI GetLocalObject with big frame depth failed to return JVMTI_ERROR_NO_MORE_FRAMES");
|
||
|
}
|
||
|
|
||
|
// #3: Test JVMTI GetLocalObject function with invalid slot -1
|
||
|
err = jvmti->GetLocalObject(vthread, depth, SlotInvalid0, &msg);
|
||
|
if (err != JVMTI_ERROR_INVALID_SLOT) {
|
||
|
LOG("JVMTI GetLocalObject with invalid slot -1 returned error: %d\n", err);
|
||
|
fatal(jni, "JVMTI GetLocalObject with invalid slot -1 failed to return JVMTI_ERROR_INVALID_SLOT");
|
||
|
}
|
||
|
|
||
|
// #4: Test JVMTI GetLocalObject function with unaligned slot 4
|
||
|
err = jvmti->GetLocalObject(vthread, depth, SlotUnaligned, &msg);
|
||
|
if (err != JVMTI_ERROR_INVALID_SLOT && err != JVMTI_ERROR_TYPE_MISMATCH) {
|
||
|
LOG("JVMTI GetLocalObject with unaligned slot 4 returned error: %d\n", err);
|
||
|
fatal(jni, "JVMTI GetLocalObject with unaligned slot 4 failed"
|
||
|
" to return JVMTI_ERROR_INVALID_SLOT or JVMTI_ERROR_TYPE_MISMATCH");
|
||
|
}
|
||
|
|
||
|
// #5: Test JVMTI GetLocalObject function with NULL value_ptr
|
||
|
err = jvmti->GetLocalObject(vthread, depth, SlotString, NULL);
|
||
|
if (err != JVMTI_ERROR_NULL_POINTER) {
|
||
|
LOG("JVMTI GetLocalObject with NULL value_ptr returned error: %d\n", err);
|
||
|
fatal(jni, "JVMTI GetLocalObject with NULL value_ptr failed to return JVMTI_ERROR_NULL_POINTER");
|
||
|
}
|
||
|
|
||
|
// #6: Test JVMTI GetLocal<Type> functions with a good vthread
|
||
|
err = jvmti->GetLocalObject(vthread, depth, SlotString, &msg);
|
||
|
check_jvmti_status(jni, err, "error in JVMTI GetLocalObject with good vthread");
|
||
|
|
||
|
const char* str = jni->GetStringUTFChars((jstring)msg, NULL);
|
||
|
LOG(" local String value at slot %d: %s\n", SlotString, str);
|
||
|
const char* exp_str = "msg: ...";
|
||
|
if (strncmp(str, exp_str, 5) != 0) {
|
||
|
LOG(" Failed: Expected local String value: %s, got: %s\n", exp_str, str);
|
||
|
fatal(jni, "Got unexpected local String value");
|
||
|
}
|
||
|
jni->ReleaseStringUTFChars((jstring)msg, str);
|
||
|
|
||
|
err = jvmti->GetLocalObject(vthread, depth, SlotThread, &tt);
|
||
|
check_jvmti_status(jni, err, "error in JVMTI GetLocalObject with good vthread");
|
||
|
|
||
|
LOG(" local Thread value at slot %d: %p\n", SlotThread, (void*)tt);
|
||
|
if (exp_values->tt != NULL && !jni->IsSameObject(tt, exp_values->tt)) {
|
||
|
LOG(" Failed: Expected local Thread value: %p, got: %p\n", exp_values->tt, tt);
|
||
|
fatal(jni, "JVMTI GetLocalObject returned unexpected local Thread value");
|
||
|
}
|
||
|
|
||
|
err = jvmti->GetLocalInt(vthread, depth, SlotInt, &ii);
|
||
|
check_jvmti_status(jni, err, "error in JVMTI GetLocalInt with good vthread");
|
||
|
|
||
|
LOG(" local int value at slot %d: %d\n", SlotInt, ii);
|
||
|
if (ii != exp_values->ii) {
|
||
|
LOG(" Failed: Expected local int value: %d, got %d\n", exp_values->ii, ii);
|
||
|
fatal(jni, "JVMTI GetLocalInt returned unexpected local int value");
|
||
|
}
|
||
|
|
||
|
err = jvmti->GetLocalLong(vthread, depth, SlotLong, &ll);
|
||
|
check_jvmti_status(jni, err, "error in JVMTI GetLocalLong with good vthread");
|
||
|
|
||
|
LOG(" local long value at slot %d: %lld\n", SlotLong, (long long)ll);
|
||
|
if (ll != exp_values->ll) {
|
||
|
LOG(" Failed: Expected local long value: %lld, got %lld\n", (long long)exp_values->ll, (long long)ll);
|
||
|
fatal(jni, "JVMTI GetLocalLong returned unexpected local long value");
|
||
|
}
|
||
|
|
||
|
err = jvmti->GetLocalFloat(vthread, depth, SlotFloat, &ff);
|
||
|
check_jvmti_status(jni, err, "error in JVMTI GetLocalFloat with good vthread");
|
||
|
|
||
|
LOG(" local float value at slot %d: %f\n", SlotFloat, ff);
|
||
|
if (ff < exp_values->ff || ff > exp_values->ff + 0.00001) {
|
||
|
LOG(" Failed: Expected local float value: %f, got %f\n", exp_values->ff, ff);
|
||
|
fatal(jni, "JVMTI GetLocalFloat returned unexpected local float value");
|
||
|
}
|
||
|
|
||
|
err = jvmti->GetLocalDouble(vthread, depth, SlotDouble, &dd);
|
||
|
check_jvmti_status(jni, err, "error in JVMTI GetLocalDouble with good vthread");
|
||
|
|
||
|
LOG(" local double value at slot %d: %f\n", SlotDouble, dd);
|
||
|
if (dd < exp_values->dd || dd > exp_values->dd + 0.00000000000001) {
|
||
|
LOG(" Failed: Expected local double value: %f, got %f\n", exp_values->dd, dd);
|
||
|
fatal(jni, "JVMTI GetLocalDouble returned unexpected local double value");
|
||
|
}
|
||
|
|
||
|
if (msg != 0) jni->DeleteLocalRef(msg);
|
||
|
if (tt != 0) jni->DeleteLocalRef(tt);
|
||
|
}
|
||
|
|
||
|
static bool
|
||
|
test_SetLocal(jvmtiEnv *jvmti, JNIEnv *jni, jthread cthread, jthread vthread,
|
||
|
int depth, int frame_count, Values *values) {
|
||
|
jvmtiError err;
|
||
|
|
||
|
LOG("test_SetLocal: mounted: %d depth: %d fcount: %d\n", cthread != NULL, depth, frame_count);
|
||
|
|
||
|
// #1: Test JVMTI SetLocalObject function with negative frame depth
|
||
|
err = jvmti->SetLocalObject(vthread, -1, SlotString, values->tt);
|
||
|
if (err != JVMTI_ERROR_ILLEGAL_ARGUMENT) {
|
||
|
LOG("JVMTI SetLocalObject with negative frame depth returned error: %d\n", err);
|
||
|
fatal(jni, "JVMTI SetLocalObject with negative frame depth failed to return JVMTI_ERROR_ILLEGAL_ARGUMENT");
|
||
|
}
|
||
|
|
||
|
// #2: Test JVMTI SetLocalObject function with big frame depth
|
||
|
err = jvmti->SetLocalObject(vthread, frame_count, SlotString, values->tt);
|
||
|
if (err != JVMTI_ERROR_NO_MORE_FRAMES) {
|
||
|
LOG("JVMTI SetLocalObject with big frame depth failed to return JVMTI_ERROR_NO_MORE_FRAMES error: %d\n", err);
|
||
|
fatal(jni, "JVMTI SetLocalObject with big frame depth failed to return JVMTI_ERROR_NO_MORE_FRAMES");
|
||
|
}
|
||
|
|
||
|
// #3: Test JVMTI SetLocalObject function with invalid slot -1
|
||
|
err = jvmti->SetLocalObject(vthread, depth, SlotInvalid0, values->tt);
|
||
|
if (depth > 0 || cthread == NULL) {
|
||
|
// JVMTI_ERROR_OPAQUE_FRAME can be returned for unmouted vthread or depth > 0
|
||
|
if (err != JVMTI_ERROR_OPAQUE_FRAME) {
|
||
|
LOG("JVMTI SetLocalObject for unmounted vthread or depth > 0 failed to return JVMTI_ERROR_OPAQUE_FRAME: %d\n", err);
|
||
|
fatal(jni, "JVMTI SetLocalObject for unmounted vthread or depth > 0 failed to return JVMTI_ERROR_OPAQUE_FRAME");
|
||
|
}
|
||
|
}
|
||
|
else if (err != JVMTI_ERROR_INVALID_SLOT) {
|
||
|
LOG("JVMTI SetLocalObject with invalid slot -1 returned error: %d\n", err);
|
||
|
fatal(jni, "JVMTI SetLocalObject with invalid slot -1 failed to return JVMTI_ERROR_INVALID_SLOT");
|
||
|
}
|
||
|
|
||
|
// #4: Test JVMTI SetLocalObject function with unaligned slot 4
|
||
|
err = jvmti->SetLocalObject(vthread, depth, SlotUnaligned, values->tt);
|
||
|
if (depth > 0 || cthread == NULL) {
|
||
|
// JVMTI_ERROR_OPAQUE_FRAME can be returned for unmouted vthread or depth > 0
|
||
|
if (err != JVMTI_ERROR_OPAQUE_FRAME) {
|
||
|
LOG("JVMTI SetLocalObject for unmounted vthread or depth > 0 failed to return JVMTI_ERROR_OPAQUE_FRAME: %d\n", err);
|
||
|
fatal(jni, "JVMTI SetLocalObject for unmounted vthread or depth > 0 failed to return JVMTI_ERROR_OPAQUE_FRAME");
|
||
|
}
|
||
|
}
|
||
|
else if (cthread != NULL && err != JVMTI_ERROR_INVALID_SLOT && err != JVMTI_ERROR_TYPE_MISMATCH) {
|
||
|
LOG("JVMTI SetLocalObject with unaligned slot 4 returned error: %d\n", err);
|
||
|
fatal(jni, "JVMTI SetLocalObject with unaligned slot 4 failed"
|
||
|
" to return JVMTI_ERROR_INVALID_SLOT or JVMTI_ERROR_TYPE_MISMATCH");
|
||
|
}
|
||
|
|
||
|
// #6: Test JVMTI SetLocal<Type> functions with a good vthread
|
||
|
err = jvmti->SetLocalObject(vthread, depth, SlotThread, values->tt);
|
||
|
if (depth > 0 || cthread == NULL) {
|
||
|
if (err != JVMTI_ERROR_OPAQUE_FRAME) {
|
||
|
LOG("JVMTI SetLocalObject for unmounted vthread or depth > 0 failed to return JVMTI_ERROR_OPAQUE_FRAME: %d\n", err);
|
||
|
fatal(jni, "JVMTI SetLocalObject for unmounted vthread pr depth > 0failed to return JVMTI_ERROR_OPAQUE_FRAME");
|
||
|
}
|
||
|
return false; // skip testing other types for unmounted vthread
|
||
|
} else if (err == JVMTI_ERROR_OPAQUE_FRAME) {
|
||
|
LOG("JVMTI SetLocalObject for mounted vthread at depth=0 returned JVMTI_ERROR_OPAQUE_FRAME: %d\n", err);
|
||
|
return false; // skip testing other types for compiled frame that can't be deoptimized
|
||
|
}
|
||
|
check_jvmti_status(jni, err, "error in JVMTI SetLocalObject with good vthread");
|
||
|
|
||
|
err = jvmti->SetLocalInt(vthread, depth, SlotInt, values->ii);
|
||
|
check_jvmti_status(jni, err, "error in JVMTI SetLocalInt with good vthread");
|
||
|
|
||
|
err = jvmti->SetLocalLong(vthread, depth, SlotLong, values->ll);
|
||
|
check_jvmti_status(jni, err, "error in JVMTI SetLocalLong with good vthread");
|
||
|
|
||
|
err = jvmti->SetLocalFloat(vthread, depth, SlotFloat, values->ff);
|
||
|
check_jvmti_status(jni, err, "error in JVMTI SetLocalFloat with good vthread");
|
||
|
|
||
|
err = jvmti->SetLocalDouble(vthread, depth, SlotDouble, values->dd);
|
||
|
check_jvmti_status(jni, err, "error in JVMTI SetLocalDouble with good vthread");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
test_GetSetLocal(jvmtiEnv *jvmti, JNIEnv* jni, jthread vthread, int depth, int frame_count) {
|
||
|
Values values0 = { NULL, NULL, 1, 2L, (jfloat)3.2F, (jdouble)4.500000047683716 };
|
||
|
Values values1 = { NULL, NULL, 2, 3L, (jfloat)4.2F, (jdouble)5.500000047683716 };
|
||
|
jthread cthread = get_carrier_thread(jvmti, jni, vthread);
|
||
|
|
||
|
values0.tt = vthread;
|
||
|
values1.tt = cthread;
|
||
|
|
||
|
LOG("test_GetSetLocal: test_GetLocal with values0\n");
|
||
|
test_GetLocal(jvmti, jni, cthread, vthread, depth, frame_count, &values0);
|
||
|
LOG("test_GetSetLocal: test_SetLocal with values1\n");
|
||
|
bool success = test_SetLocal(jvmti, jni, cthread, vthread, depth, frame_count, &values1);
|
||
|
|
||
|
if (!success) {
|
||
|
goto End; // skip testing for compiled frame that can't be deoptimized
|
||
|
}
|
||
|
if (depth > 0 || cthread == NULL) {
|
||
|
// No values are expected to be set by SetLocal above as
|
||
|
// unmounted virtual thread case is not supported.
|
||
|
// So, we expect local values to remain the same.
|
||
|
LOG("test_GetSetLocal: test_GetLocal with values0\n");
|
||
|
test_GetLocal(jvmti, jni, cthread, vthread, depth, frame_count, &values0);
|
||
|
} else {
|
||
|
LOG("test_GetSetLocal: test_GetLocal with values1\n");
|
||
|
test_GetLocal(jvmti, jni, cthread, vthread, depth, frame_count, &values1);
|
||
|
LOG("test_GetSetLocal: test_SetLocal with values0 to restore original local values\n");
|
||
|
test_SetLocal(jvmti, jni, cthread, vthread, depth, frame_count, &values0);
|
||
|
}
|
||
|
End:
|
||
|
LOG("test_GetSetLocal: finished\n\n");
|
||
|
if (cthread != 0) jni->DeleteLocalRef(cthread);
|
||
|
}
|
||
|
|
||
|
static void JNICALL
|
||
|
Breakpoint(jvmtiEnv *jvmti, JNIEnv* jni, jthread vthread,
|
||
|
jmethodID method, jlocation location) {
|
||
|
char* mname = get_method_name(jvmti, jni, method);
|
||
|
char* tname = get_thread_name(jvmti, jni, vthread);
|
||
|
const char* virt = jni->IsVirtualThread(vthread) ? "virtual" : "carrier";
|
||
|
const jint depth = 0; // the depth is always 0 in case of breakpoint
|
||
|
|
||
|
LOG("Breakpoint: %s on %s thread: %s - Started\n", mname, virt, tname);
|
||
|
|
||
|
// disable BREAKPOINT events
|
||
|
jvmtiError err = jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_BREAKPOINT, vthread);
|
||
|
check_jvmti_status(jni, err, "error in JVMTI SetEventNotificationMode: disable BREAKPOINT");
|
||
|
|
||
|
clear_breakpoint(jni, method, location);
|
||
|
|
||
|
{
|
||
|
int frame_count = get_frame_count(jvmti, jni, vthread);
|
||
|
test_GetSetLocal(jvmti, jni, vthread, depth, frame_count);
|
||
|
}
|
||
|
deallocate(jvmti, jni, (void*)mname);
|
||
|
deallocate(jvmti, jni, (void*)tname);
|
||
|
|
||
|
completed = JNI_TRUE; // done with testing in the agent
|
||
|
LOG("Breakpoint: %s on %s thread: %s - Finished\n", mname, virt, tname);
|
||
|
}
|
||
|
|
||
|
JNIEXPORT jint JNICALL
|
||
|
Agent_OnLoad(JavaVM *jvm, char *options,
|
||
|
void *reserved) {
|
||
|
jvmtiEventCallbacks callbacks;
|
||
|
jvmtiCapabilities caps;
|
||
|
jvmtiError err;
|
||
|
|
||
|
LOG("Agent_OnLoad started\n");
|
||
|
if (jvm->GetEnv((void **) (&jvmti), JVMTI_VERSION) != JNI_OK) {
|
||
|
return JNI_ERR;
|
||
|
}
|
||
|
|
||
|
memset(&callbacks, 0, sizeof(callbacks));
|
||
|
callbacks.Breakpoint = &Breakpoint;
|
||
|
|
||
|
memset(&caps, 0, sizeof(caps));
|
||
|
caps.can_support_virtual_threads = 1;
|
||
|
caps.can_access_local_variables = 1;
|
||
|
caps.can_generate_breakpoint_events = 1;
|
||
|
caps.can_suspend = 1;
|
||
|
|
||
|
err = jvmti->AddCapabilities(&caps);
|
||
|
if (err != JVMTI_ERROR_NONE) {
|
||
|
LOG("error in JVMTI AddCapabilities: %d\n", err);
|
||
|
return JNI_ERR;
|
||
|
}
|
||
|
err = jvmti->SetEventCallbacks(&callbacks, sizeof(jvmtiEventCallbacks));
|
||
|
if (err != JVMTI_ERROR_NONE) {
|
||
|
LOG("error in JVMTI SetEventCallbacks: %d\n", err);
|
||
|
return JNI_ERR;
|
||
|
}
|
||
|
LOG("Agent_OnLoad finished\n");
|
||
|
return JNI_OK;
|
||
|
}
|
||
|
|
||
|
JNIEXPORT void JNICALL
|
||
|
Java_GetSetLocalTest_enableEvents(JNIEnv *jni, jclass klass, jthread vthread) {
|
||
|
const jlocation ProducerLocation = (jlocation)30;
|
||
|
|
||
|
LOG("enableEvents: started\n");
|
||
|
|
||
|
set_breakpoint(jni, klass, "producer", ProducerLocation);
|
||
|
|
||
|
jvmtiError err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_BREAKPOINT, vthread);
|
||
|
check_jvmti_status(jni, err, "enableEvents: error in JVMTI SetEventNotificationMode: enable BREAKPOINT");
|
||
|
|
||
|
LOG("enableEvents: finished\n");
|
||
|
}
|
||
|
|
||
|
JNIEXPORT void JNICALL
|
||
|
Java_GetSetLocalTest_testSuspendedVirtualThreads(JNIEnv *jni, jclass klass, jthread vthread) {
|
||
|
char* tname = get_thread_name(jvmti, jni, vthread);
|
||
|
bool seen_depth_0 = false;
|
||
|
bool seen_depth_positive = false;
|
||
|
bool seen_unmounted = false;
|
||
|
int iter = 0;
|
||
|
|
||
|
LOG("testSuspendedVirtualThreads: started for virtual thread: %s\n", tname);
|
||
|
|
||
|
// Test each of these cases only once: unmounted, positive depth, frame count 0.
|
||
|
while (iter++ < 50 && (!seen_depth_0 || !seen_depth_positive || !seen_unmounted)) {
|
||
|
jmethodID method = NULL;
|
||
|
jlocation location = 0;
|
||
|
|
||
|
sleep_ms(1);
|
||
|
|
||
|
jvmtiError err = jvmti->SuspendThread(vthread);
|
||
|
if (err == JVMTI_ERROR_WRONG_PHASE || err == JVMTI_ERROR_THREAD_NOT_ALIVE) {
|
||
|
break; // VM or target thread completed its work
|
||
|
}
|
||
|
check_jvmti_status(jni, err, "testSuspendedVirtualThreads: error in JVMTI SuspendThread");
|
||
|
|
||
|
jthread cthread = get_carrier_thread(jvmti, jni, vthread);
|
||
|
int depth = find_method_depth(jvmti, jni, vthread, "producer");
|
||
|
int frame_count = get_frame_count(jvmti, jni, vthread);
|
||
|
|
||
|
if (depth != -1) {
|
||
|
err = jvmti->GetFrameLocation(vthread, depth, &method, &location);
|
||
|
check_jvmti_status(jni, err, "testSuspendedVirtualThreads: error in JVMTI GetFrameLocation");
|
||
|
}
|
||
|
bool case_0 = !seen_depth_0 && depth == 0 && (int)location >= 30;
|
||
|
bool case_1 = !seen_depth_positive && depth > 0 && (int)location >= 30;
|
||
|
bool case_2 = !seen_unmounted && depth >= 0 && cthread == NULL;
|
||
|
|
||
|
if (case_0) {
|
||
|
LOG("testSuspendedVirtualThreads: DEPTH == 0\n");
|
||
|
seen_depth_0 = true;
|
||
|
}
|
||
|
if (case_1) {
|
||
|
LOG("testSuspendedVirtualThreads: DEPTH > 0\n");
|
||
|
seen_depth_positive = true;
|
||
|
}
|
||
|
if (case_2) {
|
||
|
LOG("testSuspendedVirtualThreads: UNMOUNTED VTHREAD\n");
|
||
|
seen_unmounted = true;
|
||
|
}
|
||
|
if (depth >=0 && (case_0 || case_1 || case_2)) {
|
||
|
LOG("testSuspendedVirtualThreads: iter: %d\n", iter);
|
||
|
#if 0
|
||
|
print_stack_trace(jvmti, jni, vthread);
|
||
|
#endif
|
||
|
test_GetSetLocal(jvmti, jni, vthread, depth, frame_count);
|
||
|
}
|
||
|
|
||
|
err = jvmti->ResumeThread(vthread);
|
||
|
if (err == JVMTI_ERROR_WRONG_PHASE || err == JVMTI_ERROR_THREAD_NOT_ALIVE) {
|
||
|
break; // VM or target thread completed its work
|
||
|
}
|
||
|
check_jvmti_status(jni, err, "testSuspendedVirtualThreads: error in JVMTI ResumeThread");
|
||
|
|
||
|
if (cthread != 0) jni->DeleteLocalRef(cthread);
|
||
|
}
|
||
|
deallocate(jvmti, jni, (void*)tname);
|
||
|
LOG("testSuspendedVirtualThreads: finished\n");
|
||
|
}
|
||
|
|
||
|
JNIEXPORT jboolean JNICALL
|
||
|
Java_GetSetLocalTest_completed(JNIEnv *jni, jclass klass) {
|
||
|
return completed;
|
||
|
}
|
||
|
|
||
|
} // extern "C"
|