diff --git a/docs/reference-manual/native-image/JNI.md b/docs/reference-manual/native-image/JNI.md index c56fad7a9982..5918a794854a 100644 --- a/docs/reference-manual/native-image/JNI.md +++ b/docs/reference-manual/native-image/JNI.md @@ -19,6 +19,7 @@ The Native Image JNI implementation supports both approaches. ### Table of Contents +* [Loading Native Libraries](#loading-native-libraries) * [Reflection Metadata](#reflection-metadata) * [Object Handles](#object-handles) * [Java-to-Native Method Calls](#java-to-native-method-calls) @@ -29,6 +30,12 @@ The Native Image JNI implementation supports both approaches. * [Exceptions](#exceptions) * [Monitors](#monitors) +## Loading Native Libraries + +When loading native libraries using `System.loadLibrary()` (and related APIs), the native image will search the +directory containing the native image before searching the Java library path. So as long as the native libraries +to be loaded are in the same directory as the native image, no other settings should be necessary. + ## Reflection Metadata JNI supports looking up classes by their names, and looking up methods and fields by their names and signatures. @@ -187,4 +194,4 @@ For that reason, it can be beneficial to wrap synchronization in Java code. - [Interoperability with Native Code](InteropWithNativeCode.md) - [JNI Invocation API](JNIInvocationAPI.md) -- [Reachability Metadata: Java Native Interface](ReachabilityMetadata.md#java-native-interface) \ No newline at end of file +- [Reachability Metadata: Java Native Interface](ReachabilityMetadata.md#java-native-interface) diff --git a/docs/security/security-guide.md b/docs/security/security-guide.md index f4f0a5f5b766..bec35c4372fc 100644 --- a/docs/security/security-guide.md +++ b/docs/security/security-guide.md @@ -118,6 +118,8 @@ See the [documentation](../reference-manual/native-image/CertificateManagement.m In addition, developers can run the `native-image` builder in a dedicated environment, such as a container, that does not contain any sensitive information in the first place. +The directory containing the native image is part of the search path when loading native libraries using `System.loadLibrary()` at runtime. + ### Serialization in Native Image Native Image supports Serialization to help users deserialize the constructors for classes, contained in a native executable in the first place. diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ProcessPropertiesSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ProcessPropertiesSupport.java index c2c1d4300f5b..fec57ff274d6 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ProcessPropertiesSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ProcessPropertiesSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -44,6 +44,7 @@ import java.util.Map; import org.graalvm.nativeimage.c.function.CEntryPointLiteral; +import org.graalvm.word.PointerBase; public interface ProcessPropertiesSupport { String getExecutableName(); @@ -54,7 +55,14 @@ public interface ProcessPropertiesSupport { String getObjectFile(String symbol); - String getObjectFile(CEntryPointLiteral symbol); + default String getObjectFile(CEntryPointLiteral symbol) { + return getObjectFile(symbol.getFunctionPointer()); + } + + @SuppressWarnings("unused") + default String getObjectFile(PointerBase symbolAddress) { + return null; + } String setLocale(String category, String locale); diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index bb7ef03008ff..7d76702cc7d0 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -18,6 +18,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-19890) Native Image now sets up build environments for Windows users automatically. Running in an x64 Native Tools Command Prompt is no longer a requirement. * (GR-43410) Added support for the JFR event `ExecutionSample`. * (GR-44058) Red Hat added support for the JFR event `ObjectAllocationInNewTLAB`. +* (GR-42467) The search path for `System.loadLibrary()` by default includes the directory containing the native image. ## Version 22.3.0 * (GR-35721) Remove old build output style and the `-H:±BuildOutputUseNewStyle` option. diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java index 697f5890de6d..f3d2b586e41e 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java @@ -33,7 +33,6 @@ import java.util.Map.Entry; import java.util.stream.Collectors; -import org.graalvm.nativeimage.c.function.CEntryPointLiteral; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder; @@ -82,12 +81,30 @@ public int waitForProcessExit(long processID) { @Override public String getObjectFile(String symbol) { - return getObjectPathDefiningSymbol(symbol); + try (CTypeConversion.CCharPointerHolder symbolHolder = CTypeConversion.toCString(symbol)) { + PointerBase symbolAddress = Dlfcn.dlsym(Dlfcn.RTLD_DEFAULT(), symbolHolder.get()); + if (symbolAddress.isNull()) { + return null; + } + return getObjectFile(symbolAddress); + } } @Override - public String getObjectFile(CEntryPointLiteral symbol) { - return getObjectPathDefiningAddress(symbol.getFunctionPointer()); + public String getObjectFile(PointerBase symbolAddress) { + Dlfcn.Dl_info info = UnsafeStackValue.get(Dlfcn.Dl_info.class); + if (Dlfcn.dladdr(symbolAddress, info) == 0) { + return null; + } + CCharPointer realpath = Stdlib.realpath(info.dli_fname(), WordFactory.nullPointer()); + if (realpath.isNull()) { + return null; + } + try { + return CTypeConversion.toJavaString(realpath); + } finally { + LibC.free(realpath); + } } @Override @@ -133,32 +150,6 @@ public void exec(Path executable, String[] args, Map env) { } } - static String getObjectPathDefiningSymbol(String symbol) { - try (CTypeConversion.CCharPointerHolder symbolHolder = CTypeConversion.toCString(symbol)) { - PointerBase symbolAddress = Dlfcn.dlsym(Dlfcn.RTLD_DEFAULT(), symbolHolder.get()); - if (symbolAddress.isNull()) { - return null; - } - return getObjectPathDefiningAddress(symbolAddress); - } - } - - static String getObjectPathDefiningAddress(PointerBase symbolAddress) { - Dlfcn.Dl_info info = UnsafeStackValue.get(Dlfcn.Dl_info.class); - if (Dlfcn.dladdr(symbolAddress, info) == 0) { - return null; - } - CCharPointer realpath = Stdlib.realpath(info.dli_fname(), WordFactory.nullPointer()); - if (realpath.isNull()) { - return null; - } - try { - return CTypeConversion.toJavaString(realpath); - } finally { - LibC.free(realpath); - } - } - /** A platform-independent method to canonicalize a path. */ protected static String realpath(String path) { /* diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java index 528888b15f7a..7e67e701a33c 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java @@ -32,7 +32,6 @@ import java.util.List; import java.util.Map; -import org.graalvm.nativeimage.c.function.CEntryPointLiteral; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.impl.ProcessPropertiesSupport; @@ -47,16 +46,14 @@ import com.oracle.svm.core.windows.headers.Process; import com.oracle.svm.core.windows.headers.WinBase; import com.oracle.svm.core.windows.headers.WinBase.HANDLE; +import com.oracle.svm.core.windows.headers.WindowsLibC.WCharPointer; @AutomaticallyRegisteredImageSingleton(ProcessPropertiesSupport.class) public class WindowsProcessPropertiesSupport extends BaseProcessPropertiesSupport { @Override public String getExecutableName() { - CCharPointer path = UnsafeStackValue.get(WinBase.MAX_PATH, CCharPointer.class); - WinBase.HMODULE hModule = LibLoaderAPI.GetModuleHandleA(WordFactory.nullPointer()); - int result = LibLoaderAPI.GetModuleFileNameA(hModule, path, WinBase.MAX_PATH); - return result == 0 ? null : CTypeConversion.toJavaString(path); + return getModulePath(LibLoaderAPI.GetModuleHandleA(WordFactory.nullPointer())); } @Override @@ -117,21 +114,22 @@ public String getObjectFile(String symbol) { } @Override - public String getObjectFile(CEntryPointLiteral symbol) { - PointerBase symbolAddress = symbol.getFunctionPointer(); - return getObjectFile(symbolAddress); - } - - private static String getObjectFile(PointerBase symbolAddress) { + public String getObjectFile(PointerBase symbolAddress) { WinBase.HMODULEPointer module = UnsafeStackValue.get(WinBase.HMODULEPointer.class); if (LibLoaderAPI.GetModuleHandleExA(LibLoaderAPI.GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS() | LibLoaderAPI.GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT(), (CCharPointer) symbolAddress, module) == 0) { return null; } + return getModulePath(module.read()); + } - CCharPointer path = UnsafeStackValue.get(WinBase.MAX_PATH, CCharPointer.class); - int result = LibLoaderAPI.GetModuleFileNameA(module.read(), path, WinBase.MAX_PATH); - return result == 0 ? null : CTypeConversion.toJavaString(path); + private static String getModulePath(WinBase.HMODULE module) { + WCharPointer path = UnsafeStackValue.get(WinBase.MAX_PATH, WCharPointer.class); + int length = LibLoaderAPI.GetModuleFileNameW(module, path, WinBase.MAX_PATH); + if (length == 0 || length == WinBase.MAX_PATH) { + return null; + } + return WindowsSystemPropertiesSupport.toJavaString(path, length); } @Override diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSystemPropertiesSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSystemPropertiesSupport.java index e2596d7849bc..353855263cba 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSystemPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSystemPropertiesSupport.java @@ -182,7 +182,7 @@ protected String javaLibraryPathValue() { return libraryPath.toString(); } - private static String toJavaString(WCharPointer wcString, int length) { + static String toJavaString(WCharPointer wcString, int length) { return asCharBuffer(wcString, length).toString(); } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/LibLoaderAPI.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/LibLoaderAPI.java index 077ce9bf3049..1c8aea2d8090 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/LibLoaderAPI.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/LibLoaderAPI.java @@ -32,9 +32,9 @@ import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.word.PointerBase; -import com.oracle.svm.core.windows.headers.WindowsLibC.WCharPointer; import com.oracle.svm.core.windows.headers.WinBase.HMODULE; import com.oracle.svm.core.windows.headers.WinBase.HMODULEPointer; +import com.oracle.svm.core.windows.headers.WindowsLibC.WCharPointer; // Checkstyle: stop @@ -48,10 +48,6 @@ public class LibLoaderAPI { @CFunction(transition = NO_TRANSITION) public static native int FreeLibrary(HMODULE hLibModule); - /** Retrieves the fully qualified path of the file that contains the specified module. */ - @CFunction(transition = NO_TRANSITION) - public static native int GetModuleFileNameA(HMODULE hModule, CCharPointer lpFilename, int nSize); - /** Retrieves the fully qualified path of the file that contains the specified module. */ @CFunction(transition = NO_TRANSITION) public static native int GetModuleFileNameW(HMODULE hModule, WCharPointer lpFilename, int nSize); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/NativeLibrarySupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/NativeLibrarySupport.java index 5e626fd41ab5..f52a9a060921 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/NativeLibrarySupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/NativeLibrarySupport.java @@ -35,12 +35,17 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform.HOSTED_ONLY; import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.ProcessProperties; +import org.graalvm.nativeimage.impl.ProcessPropertiesSupport; import org.graalvm.word.PointerBase; import org.graalvm.word.WordFactory; +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.core.jdk.PlatformNativeLibrarySupport.NativeLibrary; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.jdk.PlatformNativeLibrarySupport.NativeLibrary; +import com.oracle.svm.core.snippets.KnownIntrinsics; @AutomaticallyRegisteredImageSingleton public final class NativeLibrarySupport { @@ -62,7 +67,10 @@ public static NativeLibrarySupport singleton() { private final Deque currentLoadContext = new ArrayDeque<>(); - private String[] paths; + /** The path of the directory containing the native image. */ + private String sysPath; + /** Paths derived from the {@code java.library.path} system property. */ + private String[] usrPaths; private LibraryInitializer libraryInitializer; @@ -97,17 +105,25 @@ public void loadLibraryRelative(String name) { if (loadLibrary0(new File(name), true)) { return; } - String libname = System.mapLibraryName(name); - if (paths == null) { + if (usrPaths == null) { + /* + * Note that `sysPath` will be `null` if we fail to get the image directory in which + * case we effectively fall back to using only `usrPaths`. + */ + sysPath = getImageDirectory(); String[] tokens = SubstrateUtil.split(System.getProperty("java.library.path", ""), File.pathSeparator); for (int i = 0; i < tokens.length; i++) { if (tokens[i].isEmpty()) { tokens[i] = "."; } } - paths = tokens; + usrPaths = tokens; + } + String libname = System.mapLibraryName(name); + if (sysPath != null && loadLibrary0(new File(sysPath, libname), false)) { + return; } - for (String path : paths) { + for (String path : usrPaths) { File libpath = new File(path, libname); if (loadLibrary0(libpath, false)) { return; @@ -120,6 +136,20 @@ public void loadLibraryRelative(String name) { throw new UnsatisfiedLinkError("no " + name + " in java.library.path"); } + /** Returns the directory containing the native image, or {@code null}. */ + @NeverInline("Reads the return address.") + private static String getImageDirectory() { + /* + * While one might expect code for shared libraries to work for executables as well, this is + * not necessarily the case. For example, `dladdr` on Linux returns `argv[0]` for + * executables, which is completely useless when running an executable from `$PATH`, since + * then `argv[0]` contains only the name of the executable. + */ + String image = !SubstrateOptions.SharedLibrary.getValue() ? ProcessProperties.getExecutableName() + : ImageSingletons.lookup(ProcessPropertiesSupport.class).getObjectFile(KnownIntrinsics.readReturnAddress()); + return image != null ? new File(image).getParent() : null; + } + private boolean loadLibrary0(File file, boolean asBuiltin) { String canonical; try {