Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GR-42467] Add support for loading libraries from directory containing native image. #5932

Merged
merged 3 commits into from
Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/reference-manual/native-image/JNI.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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)
- [Reachability Metadata: Java Native Interface](ReachabilityMetadata.md#java-native-interface)
2 changes: 2 additions & 0 deletions docs/security/security-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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);

Expand Down
1 change: 1 addition & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -133,32 +150,6 @@ public void exec(Path executable, String[] args, Map<String, String> 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) {
/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -62,7 +67,10 @@ public static NativeLibrarySupport singleton() {

private final Deque<NativeLibrary> 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;

Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand Down