diff --git a/make/test/JtregNativeHotspot.gmk b/make/test/JtregNativeHotspot.gmk index 58390110251..a55cce7ab08 100644 --- a/make/test/JtregNativeHotspot.gmk +++ b/make/test/JtregNativeHotspot.gmk @@ -871,7 +871,7 @@ BUILD_HOTSPOT_JTREG_EXECUTABLES_LIBS_exesigtest := -ljvm ifeq ($(call isTargetOs, windows), true) BUILD_HOTSPOT_JTREG_EXECUTABLES_CFLAGS_exeFPRegs := -MT - BUILD_HOTSPOT_JTREG_EXCLUDE += exesigtest.c libterminatedThread.c libTestJNI.c + BUILD_HOTSPOT_JTREG_EXCLUDE += exesigtest.c libterminatedThread.c libTestJNI.c libnativeStack.c BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libatExit := jvm.lib else BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libbootclssearch_agent += -lpthread @@ -1508,6 +1508,7 @@ else BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libgetphase002 += -lpthread BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libterminatedThread += -lpthread BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libatExit += -ljvm + BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libnativeStack += -lpthread endif # This evaluation is expensive and should only be done if this target was diff --git a/src/hotspot/share/prims/jni.cpp b/src/hotspot/share/prims/jni.cpp index cd011524841..e42206050ba 100644 --- a/src/hotspot/share/prims/jni.cpp +++ b/src/hotspot/share/prims/jni.cpp @@ -625,7 +625,7 @@ JNI_ENTRY(void, jni_FatalError(JNIEnv *env, const char *msg)) HOTSPOT_JNI_FATALERROR_ENTRY(env, (char *) msg); tty->print_cr("FATAL ERROR in native method: %s", msg); - thread->print_stack(); + thread->print_jni_stack(); os::abort(); // Dump core and abort JNI_END diff --git a/src/hotspot/share/prims/jniCheck.cpp b/src/hotspot/share/prims/jniCheck.cpp index 34b05d339b0..52fa413fa1f 100644 --- a/src/hotspot/share/prims/jniCheck.cpp +++ b/src/hotspot/share/prims/jniCheck.cpp @@ -145,7 +145,7 @@ static const char * fatal_non_utf8_class_name2 = "\""; // When in VM state: static void ReportJNIWarning(JavaThread* thr, const char *msg) { tty->print_cr("WARNING in native method: %s", msg); - thr->print_stack(); + thr->print_jni_stack(); } // When in NATIVE state: @@ -196,7 +196,7 @@ check_pending_exception(JavaThread* thr) { IN_VM( tty->print_cr("WARNING in native method: JNI call made without checking exceptions when required to from %s", thr->get_pending_jni_exception_check()); - thr->print_stack(); + thr->print_jni_stack(); ) thr->clear_pending_jni_exception_check(); // Just complain once } diff --git a/src/hotspot/share/prims/jniCheck.hpp b/src/hotspot/share/prims/jniCheck.hpp index d5de95ea8e4..3dc8c5c5939 100644 --- a/src/hotspot/share/prims/jniCheck.hpp +++ b/src/hotspot/share/prims/jniCheck.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -35,7 +35,7 @@ extern "C" { // When in VM state: static inline void ReportJNIFatalError(JavaThread* thr, const char *msg) { tty->print_cr("FATAL ERROR in native method: %s", msg); - thr->print_stack(); + thr->print_jni_stack(); os::abort(true); } } diff --git a/src/hotspot/share/runtime/thread.cpp b/src/hotspot/share/runtime/thread.cpp index 385f7a3fa99..41b04139cbd 100644 --- a/src/hotspot/share/runtime/thread.cpp +++ b/src/hotspot/share/runtime/thread.cpp @@ -2284,6 +2284,25 @@ oop JavaThread::current_park_blocker() { return NULL; } +// Print current stack trace for checked JNI warnings and JNI fatal errors. +// This is the external format, selecting the platform +// as applicable, and allowing for a native-only stack. +void JavaThread::print_jni_stack() { + assert(this == JavaThread::current(), "Can't print stack of other threads"); + if (!has_last_Java_frame()) { + ResourceMark rm(this); + char* buf = NEW_RESOURCE_ARRAY_RETURN_NULL(char, O_BUFLEN); + if (buf == nullptr) { + tty->print_cr("Unable to print native stack - out of memory"); + return; + } + frame f = os::current_frame(); + VMError::print_native_stack(tty, f, this, + buf, O_BUFLEN); + } else { + print_stack_on(tty); + } +} void JavaThread::print_stack_on(outputStream* st) { if (!has_last_Java_frame()) return; diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp index 8cd0964019f..741e8e45143 100644 --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.hpp @@ -1428,6 +1428,10 @@ class JavaThread: public Thread { // Print stack trace in external format void print_stack_on(outputStream* st); void print_stack() { print_stack_on(tty); } + // Print current stack trace for checked JNI warnings and JNI fatal errors. + // This is the external format from above, but selecting the platform + // as applicable. + void print_jni_stack(); // Print stack traces in various internal formats void trace_stack() PRODUCT_RETURN; diff --git a/src/hotspot/share/utilities/vmError.hpp b/src/hotspot/share/utilities/vmError.hpp index 8a6819d308b..9dfb6db2b9a 100644 --- a/src/hotspot/share/utilities/vmError.hpp +++ b/src/hotspot/share/utilities/vmError.hpp @@ -99,12 +99,6 @@ class VMError : public AllStatic { static void print_stack_trace(outputStream* st, JavaThread* jt, char* buf, int buflen, bool verbose = false); - // public for use by the internal non-product debugger. - NOT_PRODUCT(public:) - static void print_native_stack(outputStream* st, frame fr, Thread* t, - char* buf, int buf_size); - NOT_PRODUCT(private:) - static bool should_report_bug(unsigned int id) { return (id != OOM_MALLOC_ERROR) && (id != OOM_MMAP_ERROR); } @@ -134,6 +128,12 @@ class VMError : public AllStatic { public: + // print_source_info: if true, we try to resolve the source information on platforms that support it + // (useful but may slow down, timeout or misfunction in error situations) + // max_frames: if not -1, overrides StackPrintLimit + static void print_native_stack(outputStream* st, frame fr, Thread* t, + char* buf, int buf_size); + // return a string to describe the error static char* error_string(char* buf, int buflen); diff --git a/test/hotspot/jtreg/runtime/jni/nativeStack/TestNativeStack.java b/test/hotspot/jtreg/runtime/jni/nativeStack/TestNativeStack.java new file mode 100644 index 00000000000..36880fe256c --- /dev/null +++ b/test/hotspot/jtreg/runtime/jni/nativeStack/TestNativeStack.java @@ -0,0 +1,82 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8295974 + * @requires os.family != "windows" + * @library /test/lib + * @summary Generate a JNI Fatal error, or a warning, in a launched VM and check + * the native stack is present as expected. + * @comment The native code only supports POSIX so no windows testing + * @run driver TestNativeStack + */ + +import jdk.test.lib.Utils; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +public class TestNativeStack { + + /** + * Create a native thread that will execute native code that + * will either trigger a JNI warning (with -Xcheck:jni) or a JNI + * error, depending on the value of `warning`. + */ + static native void triggerJNIStackTrace(boolean warning); + + static { + System.loadLibrary("nativeStack"); + } + + public static void main(String[] args) throws Throwable { + // case 1: Trigger a JNI warning with Xcheck:jni + OutputAnalyzer oa = + ProcessTools.executeTestJvm("-Xcheck:jni", + "-Djava.library.path=" + Utils.TEST_NATIVE_PATH, + "TestNativeStack$Main"); + oa.shouldHaveExitValue(0); + oa.shouldContain("WARNING in native method"); + oa.shouldContain("thread_start"); + oa.reportDiagnosticSummary(); + + // Case 2: Trigger a JNI FatalError call + oa = ProcessTools.executeTestJvm("-XX:-CreateCoredumpOnCrash", + "-Djava.library.path=" + Utils.TEST_NATIVE_PATH, + "TestNativeStack$Main", + "error"); + oa.shouldNotHaveExitValue(0); + oa.shouldContain("FATAL ERROR in native method"); + oa.shouldContain("thread_start"); + oa.reportDiagnosticSummary(); + } + + static class Main { + public static void main(String[] args) throws Throwable { + boolean doWarning = args.length == 0; + System.out.println("Triggering a JNI " + + (doWarning ? "warning" : "fatal error")); + triggerJNIStackTrace(doWarning); + } + } +} diff --git a/test/hotspot/jtreg/runtime/jni/nativeStack/libnativeStack.c b/test/hotspot/jtreg/runtime/jni/nativeStack/libnativeStack.c new file mode 100644 index 00000000000..c8b9c9a999e --- /dev/null +++ b/test/hotspot/jtreg/runtime/jni/nativeStack/libnativeStack.c @@ -0,0 +1,114 @@ +/* + * 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 +#include + +#include +#include + +#include "jni.h" + +JavaVM* jvm; +jboolean warning = 0; + +void generateWarning(JNIEnv *env) { + jclass class_id; + jmethodID method_id; + + printf("About to trigger JNI Warning\n"); + + // Just call Thread.currentThread() twice in succession without checking + // for an exception in between. + + class_id = (*env)->FindClass (env, "java/lang/Thread"); + if (class_id == NULL) { + fprintf(stderr, "Test ERROR. Can't load class Thread\n"); + exit(1); + } + + method_id = (*env)->GetStaticMethodID(env, class_id, "currentThread", + "()Ljava/lang/Thread;"); + if (method_id == NULL) { + fprintf(stderr, "Test ERROR. Can't find method currentThread\n"); + exit(1); + } + + jobject nativeThread = (*env)->CallStaticObjectMethod(env, class_id, method_id, NULL); + nativeThread = (*env)->CallStaticObjectMethod(env, class_id, method_id, NULL); +} + +void generateError(JNIEnv *env) { + printf("About to trigger JNI FatalError\n"); + (*env)->FatalError(env, "Fatal error generated in test code"); +} + +static void * thread_start(void* unused) { + JNIEnv *env; + int res; + + printf("Native thread is running and attaching as daemon ...\n"); + + res = (*jvm)->AttachCurrentThreadAsDaemon(jvm, (void **)&env, NULL); + if (res != JNI_OK) { + fprintf(stderr, "Test ERROR. Can't attach current thread: %d\n", res); + exit(1); + } + + if (warning != 0) { + generateWarning(env); + } else { + generateError(env); + } + + if ((*env)->ExceptionOccurred(env) != NULL) { + (*env)->ExceptionDescribe(env); + exit(1); + } + printf("Native thread terminating\n"); + + return NULL; +} + +JNIEXPORT void JNICALL +Java_TestNativeStack_triggerJNIStackTrace +(JNIEnv *env, jclass cls, jboolean warn) { + pthread_t thread; + int res = (*env)->GetJavaVM(env, &jvm); + if (res != JNI_OK) { + fprintf(stderr, "Test ERROR. Can't extract JavaVM: %d\n", res); + exit(1); + } + + warning = warn; + + if ((res = pthread_create(&thread, NULL, thread_start, NULL)) != 0) { + fprintf(stderr, "TEST ERROR: pthread_create failed: %s (%d)\n", strerror(res), res); + exit(1); + } + + if ((res = pthread_join(thread, NULL)) != 0) { + fprintf(stderr, "TEST ERROR: pthread_join failed: %s (%d)\n", strerror(res), res); + exit(1); + } +}