From 375743336dc15f9f945a03422eaa7ff773622cd8 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Tue, 3 Jan 2023 04:22:51 +0000 Subject: [PATCH] 8295974: jni_FatalError and Xcheck:jni warnings should print the native stack when there are no Java frames Reviewed-by: coleenp, rehn, sspitsyn --- make/test/JtregNativeHotspot.gmk | 3 +- src/hotspot/share/runtime/javaThread.cpp | 22 +++- src/hotspot/share/runtime/javaThread.hpp | 2 +- src/hotspot/share/utilities/vmError.hpp | 15 +-- .../jni/nativeStack/TestNativeStack.java | 82 +++++++++++++ .../runtime/jni/nativeStack/libnativeStack.c | 114 ++++++++++++++++++ 6 files changed, 221 insertions(+), 17 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/jni/nativeStack/TestNativeStack.java create mode 100644 test/hotspot/jtreg/runtime/jni/nativeStack/libnativeStack.c diff --git a/make/test/JtregNativeHotspot.gmk b/make/test/JtregNativeHotspot.gmk index ba21e1f60e6..83c9a2377ca 100644 --- a/make/test/JtregNativeHotspot.gmk +++ b/make/test/JtregNativeHotspot.gmk @@ -874,7 +874,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 libCompleteExit.c libTestPsig.c + BUILD_HOTSPOT_JTREG_EXCLUDE += exesigtest.c libterminatedThread.c libTestJNI.c libCompleteExit.c libTestPsig.c libnativeStack.c BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libatExit := jvm.lib BUILD_HOTSPOT_JTREG_EXECUTABLES_LIBS_exedaemonDestroy := jvm.lib else @@ -1515,6 +1515,7 @@ else BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libterminatedThread += -lpthread BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libatExit += -ljvm BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libCompleteExit += -lpthread + 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/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp index 0fa2776bca4..701b538ea7b 100644 --- a/src/hotspot/share/runtime/javaThread.cpp +++ b/src/hotspot/share/runtime/javaThread.cpp @@ -99,6 +99,7 @@ #include "utilities/macros.hpp" #include "utilities/preserveException.hpp" #include "utilities/spinYield.hpp" +#include "utilities/vmError.hpp" #if INCLUDE_JVMCI #include "jvmci/jvmci.hpp" #include "jvmci/jvmciEnv.hpp" @@ -1673,14 +1674,23 @@ oop JavaThread::current_park_blocker() { return NULL; } -// Print stack trace for checked JNI warnings and JNI fatal errors. -// This is the external format from above, but selecting the platform -// or vthread as applicable. +// Print current stack trace for checked JNI warnings and JNI fatal errors. +// This is the external format, selecting the platform or vthread +// as applicable, and allowing for a native-only stack. void JavaThread::print_jni_stack() { - if (is_vthread_mounted()) { - print_vthread_stack_on(tty); + 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, true /*print_source_info */, + -1 /* max stack */, buf, O_BUFLEN); } else { - print_stack_on(tty); + print_active_stack_on(tty); } } diff --git a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp index 10fc8b6efb5..1bd503b107b 100644 --- a/src/hotspot/share/runtime/javaThread.hpp +++ b/src/hotspot/share/runtime/javaThread.hpp @@ -945,7 +945,7 @@ private: void print_vthread_stack_on(outputStream* st); // This prints the active stack: either carrier/platform or virtual. void print_active_stack_on(outputStream* st); - // Print stack trace for checked JNI warnings and JNI fatal errors. + // Print current stack trace for checked JNI warnings and JNI fatal errors. // This is the external format from above, but selecting the platform // or vthread as applicable. void print_jni_stack(); diff --git a/src/hotspot/share/utilities/vmError.hpp b/src/hotspot/share/utilities/vmError.hpp index 402e0d0e396..c9b2b7f5474 100644 --- a/src/hotspot/share/utilities/vmError.hpp +++ b/src/hotspot/share/utilities/vmError.hpp @@ -103,15 +103,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:) - // 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, bool print_source_info, - int max_frames, char* buf, int buf_size); - NOT_PRODUCT(private:) - static const char* get_filename_only() { char separator = os::file_separator()[0]; const char* p = strrchr(_filename, separator); @@ -147,6 +138,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, bool print_source_info, + int max_frames, 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); + } +}