Skip to content

Commit 525b697

Browse files
committed
[GR-42467] Add support for loading libraries from directory containing native image.
PullRequest: graal/13695
2 parents 96f93d3 + d632222 commit 525b697

File tree

9 files changed

+92
-59
lines changed

9 files changed

+92
-59
lines changed

docs/reference-manual/native-image/JNI.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ The Native Image JNI implementation supports both approaches.
1919

2020
### Table of Contents
2121

22+
* [Loading Native Libraries](#loading-native-libraries)
2223
* [Reflection Metadata](#reflection-metadata)
2324
* [Object Handles](#object-handles)
2425
* [Java-to-Native Method Calls](#java-to-native-method-calls)
@@ -29,6 +30,12 @@ The Native Image JNI implementation supports both approaches.
2930
* [Exceptions](#exceptions)
3031
* [Monitors](#monitors)
3132

33+
## Loading Native Libraries
34+
35+
When loading native libraries using `System.loadLibrary()` (and related APIs), the native image will search the
36+
directory containing the native image before searching the Java library path. So as long as the native libraries
37+
to be loaded are in the same directory as the native image, no other settings should be necessary.
38+
3239
## Reflection Metadata
3340

3441
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.
187194
188195
- [Interoperability with Native Code](InteropWithNativeCode.md)
189196
- [JNI Invocation API](JNIInvocationAPI.md)
190-
- [Reachability Metadata: Java Native Interface](ReachabilityMetadata.md#java-native-interface)
197+
- [Reachability Metadata: Java Native Interface](ReachabilityMetadata.md#java-native-interface)

docs/security/security-guide.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ See the [documentation](../reference-manual/native-image/CertificateManagement.m
118118

119119
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.
120120

121+
The directory containing the native image is part of the search path when loading native libraries using `System.loadLibrary()` at runtime.
122+
121123
### Serialization in Native Image
122124

123125
Native Image supports Serialization to help users deserialize the constructors for classes, contained in a native executable in the first place.

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ProcessPropertiesSupport.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -44,6 +44,7 @@
4444
import java.util.Map;
4545

4646
import org.graalvm.nativeimage.c.function.CEntryPointLiteral;
47+
import org.graalvm.word.PointerBase;
4748

4849
public interface ProcessPropertiesSupport {
4950
String getExecutableName();
@@ -54,7 +55,14 @@ public interface ProcessPropertiesSupport {
5455

5556
String getObjectFile(String symbol);
5657

57-
String getObjectFile(CEntryPointLiteral<?> symbol);
58+
default String getObjectFile(CEntryPointLiteral<?> symbol) {
59+
return getObjectFile(symbol.getFunctionPointer());
60+
}
61+
62+
@SuppressWarnings("unused")
63+
default String getObjectFile(PointerBase symbolAddress) {
64+
return null;
65+
}
5866

5967
String setLocale(String category, String locale);
6068

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ This changelog summarizes major changes to GraalVM Native Image.
1818
* (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.
1919
* (GR-43410) Added support for the JFR event `ExecutionSample`.
2020
* (GR-44058) Red Hat added support for the JFR event `ObjectAllocationInNewTLAB`.
21+
* (GR-42467) The search path for `System.loadLibrary()` by default includes the directory containing the native image.
2122

2223
## Version 22.3.0
2324
* (GR-35721) Remove old build output style and the `-H:±BuildOutputUseNewStyle` option.

substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import java.util.Map.Entry;
3434
import java.util.stream.Collectors;
3535

36-
import org.graalvm.nativeimage.c.function.CEntryPointLiteral;
3736
import org.graalvm.nativeimage.c.type.CCharPointer;
3837
import org.graalvm.nativeimage.c.type.CTypeConversion;
3938
import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder;
@@ -82,12 +81,30 @@ public int waitForProcessExit(long processID) {
8281

8382
@Override
8483
public String getObjectFile(String symbol) {
85-
return getObjectPathDefiningSymbol(symbol);
84+
try (CTypeConversion.CCharPointerHolder symbolHolder = CTypeConversion.toCString(symbol)) {
85+
PointerBase symbolAddress = Dlfcn.dlsym(Dlfcn.RTLD_DEFAULT(), symbolHolder.get());
86+
if (symbolAddress.isNull()) {
87+
return null;
88+
}
89+
return getObjectFile(symbolAddress);
90+
}
8691
}
8792

8893
@Override
89-
public String getObjectFile(CEntryPointLiteral<?> symbol) {
90-
return getObjectPathDefiningAddress(symbol.getFunctionPointer());
94+
public String getObjectFile(PointerBase symbolAddress) {
95+
Dlfcn.Dl_info info = UnsafeStackValue.get(Dlfcn.Dl_info.class);
96+
if (Dlfcn.dladdr(symbolAddress, info) == 0) {
97+
return null;
98+
}
99+
CCharPointer realpath = Stdlib.realpath(info.dli_fname(), WordFactory.nullPointer());
100+
if (realpath.isNull()) {
101+
return null;
102+
}
103+
try {
104+
return CTypeConversion.toJavaString(realpath);
105+
} finally {
106+
LibC.free(realpath);
107+
}
91108
}
92109

93110
@Override
@@ -133,32 +150,6 @@ public void exec(Path executable, String[] args, Map<String, String> env) {
133150
}
134151
}
135152

136-
static String getObjectPathDefiningSymbol(String symbol) {
137-
try (CTypeConversion.CCharPointerHolder symbolHolder = CTypeConversion.toCString(symbol)) {
138-
PointerBase symbolAddress = Dlfcn.dlsym(Dlfcn.RTLD_DEFAULT(), symbolHolder.get());
139-
if (symbolAddress.isNull()) {
140-
return null;
141-
}
142-
return getObjectPathDefiningAddress(symbolAddress);
143-
}
144-
}
145-
146-
static String getObjectPathDefiningAddress(PointerBase symbolAddress) {
147-
Dlfcn.Dl_info info = UnsafeStackValue.get(Dlfcn.Dl_info.class);
148-
if (Dlfcn.dladdr(symbolAddress, info) == 0) {
149-
return null;
150-
}
151-
CCharPointer realpath = Stdlib.realpath(info.dli_fname(), WordFactory.nullPointer());
152-
if (realpath.isNull()) {
153-
return null;
154-
}
155-
try {
156-
return CTypeConversion.toJavaString(realpath);
157-
} finally {
158-
LibC.free(realpath);
159-
}
160-
}
161-
162153
/** A platform-independent method to canonicalize a path. */
163154
protected static String realpath(String path) {
164155
/*

substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import java.util.List;
3333
import java.util.Map;
3434

35-
import org.graalvm.nativeimage.c.function.CEntryPointLiteral;
3635
import org.graalvm.nativeimage.c.type.CCharPointer;
3736
import org.graalvm.nativeimage.c.type.CTypeConversion;
3837
import org.graalvm.nativeimage.impl.ProcessPropertiesSupport;
@@ -47,16 +46,14 @@
4746
import com.oracle.svm.core.windows.headers.Process;
4847
import com.oracle.svm.core.windows.headers.WinBase;
4948
import com.oracle.svm.core.windows.headers.WinBase.HANDLE;
49+
import com.oracle.svm.core.windows.headers.WindowsLibC.WCharPointer;
5050

5151
@AutomaticallyRegisteredImageSingleton(ProcessPropertiesSupport.class)
5252
public class WindowsProcessPropertiesSupport extends BaseProcessPropertiesSupport {
5353

5454
@Override
5555
public String getExecutableName() {
56-
CCharPointer path = UnsafeStackValue.get(WinBase.MAX_PATH, CCharPointer.class);
57-
WinBase.HMODULE hModule = LibLoaderAPI.GetModuleHandleA(WordFactory.nullPointer());
58-
int result = LibLoaderAPI.GetModuleFileNameA(hModule, path, WinBase.MAX_PATH);
59-
return result == 0 ? null : CTypeConversion.toJavaString(path);
56+
return getModulePath(LibLoaderAPI.GetModuleHandleA(WordFactory.nullPointer()));
6057
}
6158

6259
@Override
@@ -117,21 +114,22 @@ public String getObjectFile(String symbol) {
117114
}
118115

119116
@Override
120-
public String getObjectFile(CEntryPointLiteral<?> symbol) {
121-
PointerBase symbolAddress = symbol.getFunctionPointer();
122-
return getObjectFile(symbolAddress);
123-
}
124-
125-
private static String getObjectFile(PointerBase symbolAddress) {
117+
public String getObjectFile(PointerBase symbolAddress) {
126118
WinBase.HMODULEPointer module = UnsafeStackValue.get(WinBase.HMODULEPointer.class);
127119
if (LibLoaderAPI.GetModuleHandleExA(LibLoaderAPI.GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS() | LibLoaderAPI.GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT(),
128120
(CCharPointer) symbolAddress, module) == 0) {
129121
return null;
130122
}
123+
return getModulePath(module.read());
124+
}
131125

132-
CCharPointer path = UnsafeStackValue.get(WinBase.MAX_PATH, CCharPointer.class);
133-
int result = LibLoaderAPI.GetModuleFileNameA(module.read(), path, WinBase.MAX_PATH);
134-
return result == 0 ? null : CTypeConversion.toJavaString(path);
126+
private static String getModulePath(WinBase.HMODULE module) {
127+
WCharPointer path = UnsafeStackValue.get(WinBase.MAX_PATH, WCharPointer.class);
128+
int length = LibLoaderAPI.GetModuleFileNameW(module, path, WinBase.MAX_PATH);
129+
if (length == 0 || length == WinBase.MAX_PATH) {
130+
return null;
131+
}
132+
return WindowsSystemPropertiesSupport.toJavaString(path, length);
135133
}
136134

137135
@Override

substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSystemPropertiesSupport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ protected String javaLibraryPathValue() {
182182
return libraryPath.toString();
183183
}
184184

185-
private static String toJavaString(WCharPointer wcString, int length) {
185+
static String toJavaString(WCharPointer wcString, int length) {
186186
return asCharBuffer(wcString, length).toString();
187187
}
188188

substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/LibLoaderAPI.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232
import org.graalvm.nativeimage.c.type.CCharPointer;
3333
import org.graalvm.word.PointerBase;
3434

35-
import com.oracle.svm.core.windows.headers.WindowsLibC.WCharPointer;
3635
import com.oracle.svm.core.windows.headers.WinBase.HMODULE;
3736
import com.oracle.svm.core.windows.headers.WinBase.HMODULEPointer;
37+
import com.oracle.svm.core.windows.headers.WindowsLibC.WCharPointer;
3838

3939
// Checkstyle: stop
4040

@@ -48,10 +48,6 @@ public class LibLoaderAPI {
4848
@CFunction(transition = NO_TRANSITION)
4949
public static native int FreeLibrary(HMODULE hLibModule);
5050

51-
/** Retrieves the fully qualified path of the file that contains the specified module. */
52-
@CFunction(transition = NO_TRANSITION)
53-
public static native int GetModuleFileNameA(HMODULE hModule, CCharPointer lpFilename, int nSize);
54-
5551
/** Retrieves the fully qualified path of the file that contains the specified module. */
5652
@CFunction(transition = NO_TRANSITION)
5753
public static native int GetModuleFileNameW(HMODULE hModule, WCharPointer lpFilename, int nSize);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/NativeLibrarySupport.java

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,17 @@
3535
import org.graalvm.nativeimage.ImageSingletons;
3636
import org.graalvm.nativeimage.Platform.HOSTED_ONLY;
3737
import org.graalvm.nativeimage.Platforms;
38+
import org.graalvm.nativeimage.ProcessProperties;
39+
import org.graalvm.nativeimage.impl.ProcessPropertiesSupport;
3840
import org.graalvm.word.PointerBase;
3941
import org.graalvm.word.WordFactory;
4042

43+
import com.oracle.svm.core.NeverInline;
44+
import com.oracle.svm.core.SubstrateOptions;
4145
import com.oracle.svm.core.SubstrateUtil;
42-
import com.oracle.svm.core.jdk.PlatformNativeLibrarySupport.NativeLibrary;
4346
import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
47+
import com.oracle.svm.core.jdk.PlatformNativeLibrarySupport.NativeLibrary;
48+
import com.oracle.svm.core.snippets.KnownIntrinsics;
4449

4550
@AutomaticallyRegisteredImageSingleton
4651
public final class NativeLibrarySupport {
@@ -62,7 +67,10 @@ public static NativeLibrarySupport singleton() {
6267

6368
private final Deque<NativeLibrary> currentLoadContext = new ArrayDeque<>();
6469

65-
private String[] paths;
70+
/** The path of the directory containing the native image. */
71+
private String sysPath;
72+
/** Paths derived from the {@code java.library.path} system property. */
73+
private String[] usrPaths;
6674

6775
private LibraryInitializer libraryInitializer;
6876

@@ -97,17 +105,25 @@ public void loadLibraryRelative(String name) {
97105
if (loadLibrary0(new File(name), true)) {
98106
return;
99107
}
100-
String libname = System.mapLibraryName(name);
101-
if (paths == null) {
108+
if (usrPaths == null) {
109+
/*
110+
* Note that `sysPath` will be `null` if we fail to get the image directory in which
111+
* case we effectively fall back to using only `usrPaths`.
112+
*/
113+
sysPath = getImageDirectory();
102114
String[] tokens = SubstrateUtil.split(System.getProperty("java.library.path", ""), File.pathSeparator);
103115
for (int i = 0; i < tokens.length; i++) {
104116
if (tokens[i].isEmpty()) {
105117
tokens[i] = ".";
106118
}
107119
}
108-
paths = tokens;
120+
usrPaths = tokens;
121+
}
122+
String libname = System.mapLibraryName(name);
123+
if (sysPath != null && loadLibrary0(new File(sysPath, libname), false)) {
124+
return;
109125
}
110-
for (String path : paths) {
126+
for (String path : usrPaths) {
111127
File libpath = new File(path, libname);
112128
if (loadLibrary0(libpath, false)) {
113129
return;
@@ -120,6 +136,20 @@ public void loadLibraryRelative(String name) {
120136
throw new UnsatisfiedLinkError("no " + name + " in java.library.path");
121137
}
122138

139+
/** Returns the directory containing the native image, or {@code null}. */
140+
@NeverInline("Reads the return address.")
141+
private static String getImageDirectory() {
142+
/*
143+
* While one might expect code for shared libraries to work for executables as well, this is
144+
* not necessarily the case. For example, `dladdr` on Linux returns `argv[0]` for
145+
* executables, which is completely useless when running an executable from `$PATH`, since
146+
* then `argv[0]` contains only the name of the executable.
147+
*/
148+
String image = !SubstrateOptions.SharedLibrary.getValue() ? ProcessProperties.getExecutableName()
149+
: ImageSingletons.lookup(ProcessPropertiesSupport.class).getObjectFile(KnownIntrinsics.readReturnAddress());
150+
return image != null ? new File(image).getParent() : null;
151+
}
152+
123153
private boolean loadLibrary0(File file, boolean asBuiltin) {
124154
String canonical;
125155
try {

0 commit comments

Comments
 (0)