8341273: JVMTI is not properly hiding some continuation related methods

Reviewed-by: alanb, amenkov
This commit is contained in:
Serguei Spitsyn 2024-10-29 19:59:43 +00:00
parent 520ddac970
commit 60364ef001
15 changed files with 316 additions and 79 deletions

View File

@ -926,6 +926,7 @@ public:
_method_ForceInline,
_method_DontInline,
_method_ChangesCurrentThread,
_method_JvmtiHideEvents,
_method_JvmtiMountTransition,
_method_InjectedProfile,
_method_LambdaForm_Compiled,
@ -1830,6 +1831,11 @@ AnnotationCollector::annotation_index(const ClassLoaderData* loader_data,
if (!privileged) break; // only allow in privileged code
return _method_ChangesCurrentThread;
}
case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_JvmtiHideEvents_signature): {
if (_location != _in_method) break; // only allow for methods
if (!privileged) break; // only allow in privileged code
return _method_JvmtiHideEvents;
}
case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_JvmtiMountTransition_signature): {
if (_location != _in_method) break; // only allow for methods
if (!privileged) break; // only allow in privileged code
@ -1917,6 +1923,8 @@ void MethodAnnotationCollector::apply_to(const methodHandle& m) {
m->set_dont_inline();
if (has_annotation(_method_ChangesCurrentThread))
m->set_changes_current_thread();
if (has_annotation(_method_JvmtiHideEvents))
m->set_jvmti_hide_events();
if (has_annotation(_method_JvmtiMountTransition))
m->set_jvmti_mount_transition();
if (has_annotation(_method_InjectedProfile))

View File

@ -306,6 +306,7 @@ class SerializeClosure;
template(jdk_internal_vm_annotation_Stable_signature, "Ljdk/internal/vm/annotation/Stable;") \
\
template(jdk_internal_vm_annotation_ChangesCurrentThread_signature, "Ljdk/internal/vm/annotation/ChangesCurrentThread;") \
template(jdk_internal_vm_annotation_JvmtiHideEvents_signature, "Ljdk/internal/vm/annotation/JvmtiHideEvents;") \
template(jdk_internal_vm_annotation_JvmtiMountTransition_signature, "Ljdk/internal/vm/annotation/JvmtiMountTransition;") \
\
/* Support for JSR 292 & invokedynamic (JDK 1.7 and above) */ \

View File

@ -60,6 +60,7 @@ class ConstMethodFlags {
flag(jvmti_mount_transition , 1 << 18) \
flag(deprecated , 1 << 19) \
flag(deprecated_for_removal , 1 << 20) \
flag(jvmti_hide_events , 1 << 21) \
/* end of list */
#define CM_FLAGS_ENUM_NAME(name, value) _misc_##name = value,

View File

@ -746,6 +746,9 @@ public:
bool changes_current_thread() const { return constMethod()->changes_current_thread(); }
void set_changes_current_thread() { constMethod()->set_changes_current_thread(); }
bool jvmti_hide_events() const { return constMethod()->jvmti_hide_events(); }
void set_jvmti_hide_events() { constMethod()->set_jvmti_hide_events(); }
bool jvmti_mount_transition() const { return constMethod()->jvmti_mount_transition(); }
void set_jvmti_mount_transition() { constMethod()->set_jvmti_mount_transition(); }

View File

@ -584,7 +584,6 @@ JvmtiEnvBase::jvf_for_thread_and_depth(JavaThread* java_thread, jint depth) {
javaVFrame *jvf = java_thread->last_java_vframe(&reg_map);
jvf = JvmtiEnvBase::check_and_skip_hidden_frames(java_thread, jvf);
for (int d = 0; jvf != nullptr && d < depth; d++) {
jvf = jvf->java_sender();
}
@ -652,22 +651,42 @@ JavaThread* JvmtiEnvBase::get_JavaThread_or_null(oop vthread) {
return Continuation::is_continuation_mounted(java_thread, cont) ? java_thread : nullptr;
}
// An unmounted vthread may have an empty stack.
// Otherwise, it always has the yield0() and yield() frames we need to hide.
// The methods yield0() and yield() are annotated with the @JvmtiHideEvents.
javaVFrame*
JvmtiEnvBase::skip_yield_frames_for_unmounted_vthread(javaVFrame* jvf) {
if (jvf == nullptr) {
return jvf; // empty stack is possible
}
assert(jvf->method()->jvmti_hide_events(), "sanity check");
assert(jvf->method()->method_holder() == vmClasses::Continuation_klass(), "expected Continuation class");
jvf = jvf->java_sender(); // skip yield0 frame
assert(jvf != nullptr && jvf->method()->jvmti_hide_events(), "sanity check");
assert(jvf->method()->method_holder() == vmClasses::Continuation_klass(), "expected Continuation class");
jvf = jvf->java_sender(); // skip yield frame
return jvf;
}
// A thread may have an empty stack.
// Otherwise, some top frames may heed to be hidden.
// Two cases are processed below:
// - top frame is annotated with @JvmtiMountTransition: just skip top frames with annotated methods
// - JavaThread is in VTMS transition: skip top frames until a frame annotated with @ChangesCurrentThread is found
javaVFrame*
JvmtiEnvBase::check_and_skip_hidden_frames(bool is_in_VTMS_transition, javaVFrame* jvf) {
// The second condition is needed to hide notification methods.
if (!is_in_VTMS_transition && (jvf == nullptr || !jvf->method()->jvmti_mount_transition())) {
return jvf; // No frames to skip.
if (jvf == nullptr) {
return jvf; // empty stack is possible
}
// Find jvf with a method annotated with @JvmtiMountTransition.
for ( ; jvf != nullptr; jvf = jvf->java_sender()) {
if (jvf->method()->jvmti_mount_transition()) { // Cannot actually appear in an unmounted continuation; they're never frozen.
jvf = jvf->java_sender(); // Skip annotated method.
break;
if (jvf->method()->jvmti_mount_transition()) {
// Skip frames annotated with @JvmtiMountTransition.
for ( ; jvf != nullptr && jvf->method()->jvmti_mount_transition(); jvf = jvf->java_sender()) {
}
if (jvf->method()->changes_current_thread()) {
break;
} else if (is_in_VTMS_transition) {
// Skip frames above the frame annotated with @ChangesCurrentThread.
for ( ; jvf != nullptr && !jvf->method()->changes_current_thread(); jvf = jvf->java_sender()) {
}
// Skip frame above annotated method.
}
return jvf;
}
@ -678,17 +697,6 @@ JvmtiEnvBase::check_and_skip_hidden_frames(JavaThread* jt, javaVFrame* jvf) {
return jvf;
}
javaVFrame*
JvmtiEnvBase::check_and_skip_hidden_frames(oop vthread, javaVFrame* jvf) {
JvmtiThreadState* state = java_lang_Thread::jvmti_thread_state(vthread);
if (state == nullptr) {
// nothing to skip
return jvf;
}
jvf = check_and_skip_hidden_frames(java_lang_Thread::is_in_VTMS_transition(vthread), jvf);
return jvf;
}
javaVFrame*
JvmtiEnvBase::get_vthread_jvf(oop vthread) {
assert(java_lang_VirtualThread::state(vthread) != java_lang_VirtualThread::NEW, "sanity check");
@ -707,12 +715,13 @@ JvmtiEnvBase::get_vthread_jvf(oop vthread) {
return nullptr;
}
vframeStream vfs(java_thread);
assert(!java_thread->is_in_VTMS_transition(), "invariant");
jvf = vfs.at_end() ? nullptr : vfs.asJavaVFrame();
jvf = check_and_skip_hidden_frames(java_thread, jvf);
jvf = check_and_skip_hidden_frames(false, jvf);
} else {
vframeStream vfs(cont);
jvf = vfs.at_end() ? nullptr : vfs.asJavaVFrame();
jvf = check_and_skip_hidden_frames(vthread, jvf);
jvf = skip_yield_frames_for_unmounted_vthread(jvf);
}
return jvf;
}
@ -725,11 +734,9 @@ JvmtiEnvBase::get_cthread_last_java_vframe(JavaThread* jt, RegisterMap* reg_map_
bool cthread_with_cont = JvmtiEnvBase::is_cthread_with_continuation(jt);
javaVFrame *jvf = cthread_with_cont ? jt->carrier_last_java_vframe(reg_map_p)
: jt->last_java_vframe(reg_map_p);
// Skip hidden frames only for carrier threads
// which are in non-temporary VTMS transition.
if (jt->is_in_VTMS_transition()) {
jvf = check_and_skip_hidden_frames(jt, jvf);
}
// Skip hidden frames for carrier threads only.
jvf = check_and_skip_hidden_frames(jt, jvf);
return jvf;
}
@ -1332,7 +1339,9 @@ JvmtiEnvBase::set_frame_pop(JvmtiThreadState* state, javaVFrame* jvf, jint depth
if (jvf == nullptr) {
return JVMTI_ERROR_NO_MORE_FRAMES;
}
if (jvf->method()->is_native() || (depth == 0 && state->top_frame_is_exiting())) {
if (jvf->method()->is_native() ||
(depth == 0 && state->top_frame_is_exiting()) ||
(state->is_virtual() && jvf->method()->jvmti_hide_events())) {
return JVMTI_ERROR_OPAQUE_FRAME;
}
assert(jvf->frame_pointer() != nullptr, "frame pointer mustn't be null");
@ -1989,7 +1998,6 @@ void
JvmtiHandshake::execute(JvmtiUnitedHandshakeClosure* hs_cl, jthread target) {
JavaThread* current = JavaThread::current();
HandleMark hm(current);
JvmtiVTMSTransitionDisabler disabler(target);
ThreadsListHandle tlh(current);
JavaThread* java_thread = nullptr;

View File

@ -366,9 +366,9 @@ class JvmtiEnvBase : public CHeapObj<mtInternal> {
static bool get_field_descriptor(Klass* k, jfieldID field, fieldDescriptor* fd);
// check and skip frames hidden in mount/unmount transitions
static javaVFrame* skip_yield_frames_for_unmounted_vthread(javaVFrame* jvf);
static javaVFrame* check_and_skip_hidden_frames(bool is_in_VTMS_transition, javaVFrame* jvf);
static javaVFrame* check_and_skip_hidden_frames(JavaThread* jt, javaVFrame* jvf);
static javaVFrame* check_and_skip_hidden_frames(oop vthread, javaVFrame* jvf);
// check if virtual thread is not terminated (alive)
static bool is_vthread_alive(oop vt);

View File

@ -833,11 +833,6 @@ VM_VirtualThreadGetOrSetLocal::VM_VirtualThreadGetOrSetLocal(JvmtiEnv* env, Hand
}
javaVFrame *VM_VirtualThreadGetOrSetLocal::get_java_vframe() {
Thread* cur_thread = Thread::current();
oop cont = java_lang_VirtualThread::continuation(_vthread_h());
assert(cont != nullptr, "vthread contintuation must not be null");
javaVFrame* jvf = nullptr;
JavaThread* java_thread = JvmtiEnvBase::get_JavaThread_or_null(_vthread_h());
bool is_cont_mounted = (java_thread != nullptr);
@ -845,22 +840,8 @@ javaVFrame *VM_VirtualThreadGetOrSetLocal::get_java_vframe() {
_result = JVMTI_ERROR_THREAD_NOT_SUSPENDED;
return nullptr;
}
javaVFrame* jvf = JvmtiEnvBase::get_vthread_jvf(_vthread_h());
if (is_cont_mounted) {
vframeStream vfs(java_thread);
if (!vfs.at_end()) {
jvf = vfs.asJavaVFrame();
jvf = JvmtiEnvBase::check_and_skip_hidden_frames(java_thread, jvf);
}
} else {
vframeStream vfs(cont);
if (!vfs.at_end()) {
jvf = vfs.asJavaVFrame();
jvf = JvmtiEnvBase::check_and_skip_hidden_frames(_vthread_h(), jvf);
}
}
int d = 0;
while ((jvf != nullptr) && (d < _depth)) {
jvf = jvf->java_sender();

View File

@ -253,7 +253,7 @@ JvmtiVTMSTransitionDisabler::print_info() {
#endif
// disable VTMS transitions for one virtual thread
// no-op if thread is non-null and not a virtual thread
// disable VTMS transitions for all threads if thread is nullptr or a platform thread
JvmtiVTMSTransitionDisabler::JvmtiVTMSTransitionDisabler(jthread thread)
: _is_SR(false), _thread(thread)
{
@ -266,6 +266,17 @@ JvmtiVTMSTransitionDisabler::JvmtiVTMSTransitionDisabler(jthread thread)
if (!sync_protocol_enabled_permanently()) {
JvmtiVTMSTransitionDisabler::inc_sync_protocol_enabled_count();
}
oop thread_oop = JNIHandles::resolve_external_guard(thread);
// Target can be virtual or platform thread.
// If target is a platform thread then we have to disable VTMS transitions for all threads.
// It is by several reasons:
// - carrier threads can mount virtual threads which may cause incorrect behavior
// - there is no mechanism to disable transitions for a specific carrier thread yet
if (!java_lang_VirtualThread::is_instance(thread_oop)) {
_thread = nullptr; // target is a platform thread, switch to disabling VTMS transitions for all threads
}
if (_thread != nullptr) {
VTMS_transition_disable_for_one(); // disable VTMS transitions for one virtual thread
} else {
@ -316,9 +327,8 @@ JvmtiVTMSTransitionDisabler::VTMS_transition_disable_for_one() {
JavaThread* thread = JavaThread::current();
HandleMark hm(thread);
Handle vth = Handle(thread, JNIHandles::resolve_external_guard(_thread));
if (!java_lang_VirtualThread::is_instance(vth())) {
return; // no-op if _thread is not a virtual thread
}
assert(java_lang_VirtualThread::is_instance(vth()), "sanity check");
MonitorLocker ml(JvmtiVTMSTransition_lock);
while (_SR_mode) { // suspender or resumer is a JvmtiVTMSTransitionDisabler monopolist
@ -468,7 +478,7 @@ JvmtiVTMSTransitionDisabler::start_VTMS_transition(jthread vthread, bool is_moun
JvmtiVTSuspender::is_vthread_suspended(thread_id)
) {
// Block while transitions are disabled or there are suspend requests.
if (ml.wait(10)) {
if (ml.wait(200)) {
attempts--;
}
DEBUG_ONLY(if (attempts == 0) break;)
@ -525,7 +535,7 @@ JvmtiVTMSTransitionDisabler::finish_VTMS_transition(jthread vthread, bool is_mou
(is_mount && JvmtiVTSuspender::is_vthread_suspended(thread_id))
) {
// Block while there are suspend requests.
if (ml.wait(10)) {
if (ml.wait(200)) {
attempts--;
}
DEBUG_ONLY(if (attempts == 0) break;)

View File

@ -56,6 +56,7 @@ import jdk.internal.vm.ThreadContainers;
import jdk.internal.vm.annotation.ChangesCurrentThread;
import jdk.internal.vm.annotation.Hidden;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.JvmtiHideEvents;
import jdk.internal.vm.annotation.JvmtiMountTransition;
import jdk.internal.vm.annotation.ReservedStackAccess;
import sun.nio.ch.Interruptible;
@ -213,8 +214,14 @@ final class VirtualThread extends BaseVirtualThread {
private static Runnable wrap(VirtualThread vthread, Runnable task) {
return new Runnable() {
@Hidden
@JvmtiHideEvents
public void run() {
vthread.run(task);
vthread.notifyJvmtiStart(); // notify JVMTI
try {
vthread.run(task);
} finally {
vthread.notifyJvmtiEnd(); // notify JVMTI
}
}
};
}
@ -389,9 +396,6 @@ final class VirtualThread extends BaseVirtualThread {
private void run(Runnable task) {
assert Thread.currentThread() == this && state == RUNNING;
// notify JVMTI, may post VirtualThreadStart event
notifyJvmtiStart();
// emit JFR event if enabled
if (VirtualThreadStartEvent.isTurnedOn()) {
var event = new VirtualThreadStartEvent();
@ -405,20 +409,14 @@ final class VirtualThread extends BaseVirtualThread {
} catch (Throwable exc) {
dispatchUncaughtException(exc);
} finally {
try {
// pop any remaining scopes from the stack, this may block
StackableScope.popAll();
// pop any remaining scopes from the stack, this may block
StackableScope.popAll();
// emit JFR event if enabled
if (VirtualThreadEndEvent.isTurnedOn()) {
var event = new VirtualThreadEndEvent();
event.javaThreadId = threadId();
event.commit();
}
} finally {
// notify JVMTI, may post VirtualThreadEnd event
notifyJvmtiEnd();
// emit JFR event if enabled
if (VirtualThreadEndEvent.isTurnedOn()) {
var event = new VirtualThreadEndEvent();
event.javaThreadId = threadId();
event.commit();
}
}
}

View File

@ -36,6 +36,7 @@ import java.util.function.Supplier;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.vm.annotation.Hidden;
import jdk.internal.vm.annotation.JvmtiHideEvents;
/**
* A one-shot delimited continuation.
@ -305,6 +306,7 @@ public class Continuation {
@Hidden
@DontInline
@IntrinsicCandidate
@JvmtiHideEvents
private static void enter(Continuation c, boolean isContinue) {
// This method runs in the "entry frame".
// A yield jumps to this method's caller as if returning from this method.
@ -316,6 +318,7 @@ public class Continuation {
}
@Hidden
@JvmtiHideEvents
private void enter0() {
target.run();
}
@ -340,6 +343,7 @@ public class Continuation {
* @throws IllegalStateException if not currently in the given {@code scope},
*/
@Hidden
@JvmtiHideEvents
public static boolean yield(ContinuationScope scope) {
Continuation cont = JLA.getContinuation(currentCarrierThread());
Continuation c;
@ -352,6 +356,7 @@ public class Continuation {
}
@Hidden
@JvmtiHideEvents
private boolean yield0(ContinuationScope scope, Continuation child) {
preempted = false;

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2024, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package jdk.internal.vm.annotation;
import java.lang.annotation.*;
/**
* A method may be annotated with JvmtiHideEvents to hint that JVMTI events
* should not be generated in context of the annotated method.
*
* @implNote
* This annotation is only used for some VirtualThread and Continuation methods.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface JvmtiHideEvents {
}

View File

@ -28,8 +28,11 @@ package jdk.internal.vm.annotation;
import java.lang.annotation.*;
/**
* A method is annotated as "jvmti mount transition" if it starts
* or ends virtual thread mount state transition (VTMS transition).
* A method may be annotated with JvmtiMountTransition to hint
* it is desirable to omit it from JVMTI stack traces.
* Normally, a method is annotated with @JvmtiMountTransition if it starts
* or ends Virtual Thread Mount State (VTMS) transition, so the thread
* identity is undefined or different at method entry and exit.
*
* @implNote
* This annotation is only used for VirtualThread methods.

View File

@ -79,7 +79,7 @@ public class framecnt01 {
}
// this is too fragile, implementation can change at any time.
checkFrames(vThread1, false, 13);
checkFrames(vThread1, false, 11);
LockSupport.unpark(vThread1);
vThread1.join();

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2024, 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.
*/
/*
* @test
* @bug 8341273
* @summary Verifies JVMTI properly hides frames which are in VTMS transition
* @run main/othervm/native -agentlib:CheckHiddenFrames CheckHiddenFrames
*/
public class CheckHiddenFrames {
static native boolean checkHidden(Thread t);
static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
}
}
public static void main(String[] args) throws Exception {
Thread thread = Thread.startVirtualThread(CheckHiddenFrames::test);
System.out.println("Started virtual thread: " + thread);
if (!checkHidden(thread)) {
thread.interrupt();
throw new RuntimeException("CheckHiddenFrames failed!");
}
thread.interrupt();
thread.join();
}
static void test() {
sleep(1000000);
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) 2024, 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.hpp"
extern "C" {
const int MAX_COUNT = 50;
static jvmtiEnv *jvmti = nullptr;
static char*
get_frame_method_name(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread, jint depth) {
jmethodID method = nullptr;
jlocation location = 0;
jvmtiError err = jvmti->GetFrameLocation(thread, 0, &method, &location);
check_jvmti_status(jni, err, "get_method_name_at_depth: error in JVMTI GetFrameLocation");
return get_method_name(jvmti, jni, method);
}
static bool
method_must_be_hidden(char* mname) {
return strcmp(mname, "yield") == 0 ||
strcmp(mname, "yield0") == 0;
}
static jboolean
check_top_frames_location(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread) {
jboolean status = JNI_TRUE;
for (int depth = 0; depth < 2; depth++) {
char* mname = get_frame_method_name(jvmti, jni, thread, depth);
if (method_must_be_hidden(mname)) {
LOG("Failed: GetFrameLocation returned info for frame expected to be hidden: frame[%d]=%s\n", depth, mname);
status = JNI_FALSE;
}
deallocate(jvmti, jni, mname);
}
return status;
}
static jboolean
check_top_frames_in_stack_trace(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread) {
jboolean status = JNI_TRUE;
jvmtiFrameInfo frameInfo[MAX_COUNT];
jint count1 = 0;
jint count2 = 0;
jvmtiError err = jvmti->GetStackTrace(thread, 0, MAX_COUNT, frameInfo, &count1);
check_jvmti_status(jni, err, "check_top_frames_in_stack_trace: error in JVMTI GetStackTrace");
for (int depth = 0; depth < 2; depth++) {
char* mname = get_method_name(jvmti, jni, frameInfo[depth].method);
if (method_must_be_hidden(mname)) {
LOG("Failed: GetStackTrace returned info for frame expected to be hidden: frame[%d]=%s\n", depth, mname);
status = JNI_FALSE;
}
deallocate(jvmti, jni, mname);
}
err = jvmti->GetFrameCount(thread, &count2);
check_jvmti_status(jni, err, "check_top_frames_in_stack_trace: error in JVMTI GetFrameCount");
if (count1 != count2) {
LOG("Failed: frame counts returned by GetStackTrace and GetFrameCount do not match: %d!=%d\n", count1, count2);
status = JNI_FALSE;
}
return status;
}
JNIEXPORT jboolean JNICALL
Java_CheckHiddenFrames_checkHidden(JNIEnv *jni, jclass clazz, jthread thread) {
jboolean status = JNI_TRUE;
wait_for_state(jvmti, jni, thread, JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT);
print_stack_trace(jvmti, jni, thread);
if (!check_top_frames_location(jvmti, jni, thread)) {
status = JNI_FALSE;
}
if (!check_top_frames_in_stack_trace(jvmti, jni, thread)) {
status = JNI_FALSE;
}
return status;
}
extern JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
LOG("Agent_OnLoad started\n");
if (jvm->GetEnv((void **)(&jvmti), JVMTI_VERSION) != JNI_OK) {
return JNI_ERR;
}
LOG("Agent_OnLoad finished\n");
return JNI_OK;
}
} // extern "C"