From 22f109a8f69ee42111a9f318d83ddc2228c11d91 Mon Sep 17 00:00:00 2001 From: Doug Simon Date: Fri, 6 Oct 2023 21:14:05 +0200 Subject: [PATCH 1/2] don't run NativePRNG tear down hook if platform native libs not yet initialized (GR-48717) --- .../com/oracle/svm/core/PosixSunSecuritySubstitutions.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/PosixSunSecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/PosixSunSecuritySubstitutions.java index 31399dec6032..a39047ada6a9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/PosixSunSecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/PosixSunSecuritySubstitutions.java @@ -29,6 +29,7 @@ import java.io.InputStream; import java.io.OutputStream; +import jdk.internal.misc.Unsafe; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -39,6 +40,10 @@ final class NativeSecureRandomFilesCloserTearDownHook implements RuntimeSupport.Hook { @Override public void execute(boolean isFirstIsolate) { + if (Unsafe.getUnsafe().shouldBeInitialized(Target_sun_security_provider_NativePRNG.class)) { + // Avoid NativePRNG tear down hooks if platform native libs not initialized + return; + } Target_sun_security_provider_NativePRNG_RandomIO instance = Target_sun_security_provider_NativePRNG.INSTANCE; if (instance != null) { close(instance.nextIn); From caca48065236a667d549d0479fe9d73f4f9976c8 Mon Sep 17 00:00:00 2001 From: Doug Simon Date: Fri, 6 Oct 2023 21:19:47 +0200 Subject: [PATCH 2/2] added support for _createvm_errorstr argument to CreateJavaVM for returning an error message --- .../svm/core/posix/PosixLibCSupport.java | 6 + .../svm/core/windows/WindowsLibCSupport.java | 6 + .../core/c/function/CEntryPointErrors.java | 123 +++++++++++++++++- .../src/com/oracle/svm/core/headers/LibC.java | 5 + .../oracle/svm/core/headers/LibCSupport.java | 3 + .../jni/functions/JNIInvocationInterface.java | 51 +++++--- .../core/log/FunctionPointerLogHandler.java | 5 - 7 files changed, 172 insertions(+), 27 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibCSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibCSupport.java index 48b7da2ee12a..528c09c8f9fa 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibCSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixLibCSupport.java @@ -95,6 +95,12 @@ public UnsignedWord strlen(CCharPointer str) { return PosixLibC.strlen(str); } + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public CCharPointer strdup(CCharPointer str) { + return PosixLibC.strdup(str); + } + @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public int strcmp(CCharPointer s1, CCharPointer s2) { diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibCSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibCSupport.java index 179a459e7fbb..2f67d1a6dc18 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibCSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsLibCSupport.java @@ -109,6 +109,12 @@ public UnsignedWord strlen(CCharPointer str) { return WindowsLibC.strlen(str); } + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public CCharPointer strdup(CCharPointer str) { + return WindowsLibC.strdup(str); + } + @Override @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public int strcmp(CCharPointer s1, CCharPointer s2) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointErrors.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointErrors.java index d1993af1fceb..758b108ed827 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointErrors.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointErrors.java @@ -24,15 +24,29 @@ */ package com.oracle.svm.core.c.function; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Objects; +import com.oracle.svm.core.util.HostedByteBufferPointer; import org.graalvm.nativeimage.AnnotationAccess; - +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; import com.oracle.svm.core.util.VMError; /** @@ -125,7 +139,7 @@ private CEntryPointErrors() { @Description("The isolate arguments could not be parsed.") // public static final int ARGUMENT_PARSING_FAILED = 22; - @Description("Current target does not support the following CPU features that are required by the image.") // + @Description("Current target does not support the CPU features that are required by the image.") // public static final int CPU_FEATURE_CHECK_FAILED = 23; @Description("Image page size is incompatible with run-time page size. Rebuild image with -H:PageSize=[pagesize] to set appropriately.") // @@ -166,7 +180,53 @@ public static String getDescription(int code) { return result; } + /** + * Gets the description for {@code code} as a C string. + * + * @param code an error code + * @return the description for {@code} or a null pointer if no description is available + */ + @Uninterruptible(reason = "Called when isolate creation fails.") + public static CCharPointer getDescriptionAsCString(int code) { + return (CCharPointer) getDescriptionAsCString(code, (Pointer) CSTRING_DESCRIPTIONS.get()); + } + + /** + * Searches {@code cstrings} for a description of {@code code}. + * + * @return the description for {@code} or a null pointer if no description is available + */ + @Uninterruptible(reason = "Called when isolate creation fails.") + private static Pointer getDescriptionAsCString(int code, Pointer cstrings) { + int offset = 0; + int startOffset = 0; + int codeIndex = 0; + while (true) { + byte ch = cstrings.readByte(offset); + if (ch == 1) { + break; + } else if (ch == 0) { + startOffset = offset + 1; + codeIndex++; + } else if (code == codeIndex) { + return cstrings.add(startOffset); + } + offset++; + } + return WordFactory.nullPointer(); + } + private static final String[] DESCRIPTIONS; + + /** + * The error descriptions as C strings in a single contiguous chunk of global memory. This is + * required to be able to access descriptions when errors occur during isolate creation. Without + * an isolate, static fields are not usable. + * + * @see #toCStrings + */ + private static final CGlobalData CSTRING_DESCRIPTIONS; + static { try { String[] array = new String[16]; @@ -183,9 +243,64 @@ public static String getDescription(int code) { } array[value] = description; } - DESCRIPTIONS = Arrays.copyOf(array, maxValue + 1); - } catch (IllegalAccessException e) { + String[] descriptions = Arrays.copyOf(array, maxValue + 1); + byte[] cstrings = toCStrings(descriptions); + DESCRIPTIONS = descriptions; + CSTRING_DESCRIPTIONS = CGlobalDataFactory.createBytes(() -> cstrings); + } catch (IllegalAccessException | IOException e) { throw VMError.shouldNotReachHere(e); } } + + /** + * Converts all entries in {@code descriptions} to 0-terminated C strings and concatenates them + * into a single byte array with a final terminator of 1. Null entries are converted to 0 length + * C strings. + */ + @Platforms(Platform.HOSTED_ONLY.class) + private static byte[] toCStrings(String[] descriptions) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int code = 0; code < descriptions.length; code++) { + String description = descriptions[code]; + if (description != null) { + baos.write(description.getBytes(StandardCharsets.UTF_8)); + } + baos.write(0); + } + baos.write(1); + return checkedCStrings(descriptions, baos.toByteArray()); + } + + @Platforms(Platform.HOSTED_ONLY.class) + private static byte[] checkedCStrings(String[] descriptions, byte[] cstrings) { + for (int i = 0; i < cstrings.length; i++) { + byte ch = cstrings[i]; + VMError.guarantee(ch != 1 || i == cstrings.length - 1, "only last byte in cstrings may be 1, got %d at index %d", ch, i); + } + for (int code = 0; code < descriptions.length; code++) { + String expect = descriptions[code]; + Pointer cstringsPointer = new HostedByteBufferPointer(ByteBuffer.wrap(cstrings), 0); + String actual = toJavaString(getDescriptionAsCString(code, cstringsPointer)); + if (!Objects.equals(expect, actual)) { + throw VMError.shouldNotReachHere("code %d: expected %s, got %s", code, expect, actual); + } + } + return cstrings; + } + + @Platforms(Platform.HOSTED_ONLY.class) + private static String toJavaString(Pointer res) { + if (res.isNonNull()) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int i = 0; + while (true) { + byte ch = res.readByte(i++); + if (ch == 0) { + return new String(baos.toByteArray(), StandardCharsets.UTF_8); + } + baos.write(ch); + } + } + return null; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java index 8c6a7fb88464..8be6fd22a24b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibC.java @@ -107,6 +107,11 @@ public static UnsignedWord strlen(CCharPointer str) { return libc().strlen(str); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static CCharPointer strdup(CCharPointer str) { + return libc().strdup(str); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static int strcmp(CCharPointer s1, CCharPointer s2) { return libc().strcmp(s1, s2); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibCSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibCSupport.java index e0fbd5e2b139..6965ee9b356d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibCSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/LibCSupport.java @@ -69,6 +69,9 @@ public interface LibCSupport { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) UnsignedWord strlen(CCharPointer str); + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + CCharPointer strdup(CCharPointer str); + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) int strcmp(CCharPointer s1, CCharPointer s2); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java index 310308d0e42a..4fd47af1e1f1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java @@ -123,6 +123,7 @@ static int enter(JNIJavaVMPointer vmBuf, JNIEnvironmentPointer penv, JNIJavaVMIn boolean hasSpecialVmOptions = false; CEntryPointCreateIsolateParameters params = WordFactory.nullPointer(); + CCharPointerPointer errorstr = WordFactory.nullPointer(); if (vmArgs.isNonNull()) { int vmArgc = vmArgs.getNOptions(); if (vmArgc > 0) { @@ -145,7 +146,9 @@ static int enter(JNIJavaVMPointer vmBuf, JNIEnvironmentPointer penv, JNIJavaVMIn if (optionString.isNonNull()) { // Filter all special VM options as those must be parsed differently // after the isolate creation. - if (Support.isSpecialVMOption(optionString)) { + if (LibC.strcmp(optionString, Support.CREATEVM_ERRORSTR_OPTION.get()) == 0) { + errorstr = (CCharPointerPointer) option.getExtraInfo(); + } else if (Support.isSpecialVMOption(optionString)) { hasSpecialVmOptions = true; } else { argv.addressOf(argc).write(optionString); @@ -174,6 +177,11 @@ static int enter(JNIJavaVMPointer vmBuf, JNIEnvironmentPointer penv, JNIJavaVMIn // The isolate was created successfully, so we can finish the initialization. return Support.finishInitialization(vmBuf, penv, vmArgs, hasSpecialVmOptions); } + if (errorstr.isNonNull()) { + CCharPointer msg = CEntryPointErrors.getDescriptionAsCString(code); + CCharPointer msgCopy = msg.isNonNull() ? LibC.strdup(msg) : WordFactory.nullPointer(); + errorstr.write(msgCopy); + } return JNIFunctions.Support.convertCEntryPointErrorToJNIError(code, true); } } @@ -182,23 +190,28 @@ static int enter(JNIJavaVMPointer vmBuf, JNIEnvironmentPointer penv, JNIJavaVMIn * This method supports the non-standard option strings detailed in the table below. * *
-         | optionString  |                         meaning                                                   |
-         |===============|===================================================================================|
-         | _log          | extraInfo is a pointer to a "void(const char *buf, size_t count)" function.       |
-         |               | Formatted low level log messages are sent to this function.                       |
-         |               | If present, then _flush_log is also required to be present.                       |
-         |---------------|-----------------------------------------------------------------------------------|
-         | _fatal_log    | extraInfo is a pointer to a "void(const char *buf, size_t count)" function.       |
-         |               | Formatted low level log messages are sent to this function.                       |
-         |               | This log function is used for logging fatal crash data.                           |
-         |---------------|-----------------------------------------------------------------------------------|
-         | _flush_log    | extraInfo is a pointer to a "void()" function.                                    |
-         |               | This function is called when the low level log stream should be flushed.          |
-         |               | If present, then _log is also required to be present.                             |
-         |---------------|-----------------------------------------------------------------------------------|
-         | _fatal        | extraInfo is a pointer to a "void()" function.                                    |
-         |               | This function is called when a non-recoverable, fatal error occurs.               |
-         |---------------|-----------------------------------------------------------------------------------|
+         | optionString       |                         meaning                                                   |
+         |====================|===================================================================================|
+         | _log               | extraInfo is a pointer to a "void(const char *buf, size_t count)" function.       |
+         |                    | Formatted low level log messages are sent to this function.                       |
+         |                    | If present, then _flush_log is also required to be present.                       |
+         |--------------------|-----------------------------------------------------------------------------------|
+         | _fatal_log         | extraInfo is a pointer to a "void(const char *buf, size_t count)" function.       |
+         |                    | Formatted low level log messages are sent to this function.                       |
+         |                    | This log function is used for logging fatal crash data.                           |
+         |--------------------|-----------------------------------------------------------------------------------|
+         | _flush_log         | extraInfo is a pointer to a "void()" function.                                    |
+         |                    | This function is called when the low level log stream should be flushed.          |
+         |                    | If present, then _log is also required to be present.                             |
+         |--------------------|-----------------------------------------------------------------------------------|
+         | _fatal             | extraInfo is a pointer to a "void()" function.                                    |
+         |                    | This function is called when a non-recoverable, fatal error occurs.               |
+         |--------------------|-----------------------------------------------------------------------------------|
+         | _createvm_errorstr | extraInfo is a "const char**" value.                                              |
+         |                    | If CreateJavaVM returns non-zero, then extraInfo is assigned a newly malloc'ed    |
+         |                    | 0-terminated C string describing the error if a description is available,         |
+         |                    | otherwise extraInfo is set to null.                                               |
+         |--------------------|-----------------------------------------------------------------------------------|
          * 
* * @see LogHandler @@ -297,7 +310,9 @@ static int GetEnv(JNIJavaVM vm, WordPointer env, int version) { * methods must match JNI invocation API functions. */ static class Support { + private static final CGlobalData JAVA_VM_ID_OPTION = CGlobalDataFactory.createCString("_javavm_id"); + private static final CGlobalData CREATEVM_ERRORSTR_OPTION = CGlobalDataFactory.createCString("_createvm_errorstr"); static class JNIGetEnvPrologue implements CEntryPointOptions.Prologue { @Uninterruptible(reason = "prologue") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/FunctionPointerLogHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/FunctionPointerLogHandler.java index ceeef1430677..2f594f667f3a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/FunctionPointerLogHandler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/log/FunctionPointerLogHandler.java @@ -137,11 +137,6 @@ interface VoidFunctionPointer extends CFunctionPointer { void invoke(); } - interface FatalContextFunctionPointer extends CFunctionPointer { - @InvokeCFunctionPointer - boolean invoke(CodePointer callerIP, String msg, Throwable ex); - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean isJniVMOption(CCharPointer optionString) { return LibC.strcmp(optionString, LOG_OPTION.get()) == 0 ||