Skip to content

Commit a57ef4e

Browse files
committed
Add support for dynamic linking of AWT libraries on Linux
1 parent 9a5f311 commit a57ef4e

File tree

3 files changed

+119
-65
lines changed

3 files changed

+119
-65
lines changed

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/CCLinkerInvocation.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import com.oracle.svm.hosted.c.NativeLibraries;
5858
import com.oracle.svm.hosted.c.codegen.CCompilerInvoker;
5959
import com.oracle.svm.hosted.c.libc.HostedLibCBase;
60+
import com.oracle.svm.hosted.jdk.JNIRegistrationSupport;
6061

6162
public abstract class CCLinkerInvocation implements LinkerInvocation {
6263

@@ -269,9 +270,8 @@ private static class BinutilsCCLinkerInvocation extends CCLinkerInvocation {
269270
exportedSymbols.append("{\n");
270271
/* Only exported symbols are global ... */
271272
exportedSymbols.append("global:\n");
272-
for (String symbol : getImageSymbols(true)) {
273-
exportedSymbols.append('\"').append(symbol).append("\";\n");
274-
}
273+
Stream.concat(getImageSymbols(true).stream(), JNIRegistrationSupport.getShimLibrarySymbols())
274+
.forEach(symbol -> exportedSymbols.append('\"').append(symbol).append("\";\n"));
275275
/* ... everything else is local. */
276276
exportedSymbols.append("local: *;\n");
277277
exportedSymbols.append("};");

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationAWTSupport.java

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,46 +24,65 @@
2424
*/
2525
package com.oracle.svm.hosted.jdk;
2626

27+
import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
2728
import org.graalvm.nativeimage.Platform;
2829
import org.graalvm.nativeimage.Platforms;
2930

30-
import com.oracle.svm.core.feature.InternalFeature;
3131
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
32+
import com.oracle.svm.core.feature.InternalFeature;
33+
import com.oracle.svm.core.jdk.JNIRegistrationUtil;
3234
import com.oracle.svm.hosted.FeatureImpl.BeforeImageWriteAccessImpl;
3335

34-
@Platforms(Platform.WINDOWS.class)
36+
@Platforms({Platform.WINDOWS.class, Platform.LINUX.class})
3537
@AutomaticallyRegisteredFeature
36-
public class JNIRegistrationAWTSupport implements InternalFeature {
38+
public class JNIRegistrationAWTSupport extends JNIRegistrationUtil implements InternalFeature {
3739
@Override
3840
public void afterAnalysis(AfterAnalysisAccess access) {
3941
JNIRegistrationSupport jniRegistrationSupport = JNIRegistrationSupport.singleton();
4042
if (jniRegistrationSupport.isRegisteredLibrary("awt")) {
4143
jniRegistrationSupport.addJvmShimExports(
42-
"JVM_CurrentTimeMillis",
43-
"JVM_RaiseSignal",
4444
"jio_snprintf");
4545
jniRegistrationSupport.addJavaShimExports(
46-
"JDK_LoadSystemLibrary",
4746
"JNU_CallMethodByName",
48-
"JNU_CallMethodByNameV",
4947
"JNU_CallStaticMethodByName",
50-
"JNU_ClassString",
5148
"JNU_GetEnv",
52-
"JNU_GetFieldByName",
5349
"JNU_GetStaticFieldByName",
5450
"JNU_IsInstanceOfByName",
5551
"JNU_NewObjectByName",
5652
"JNU_NewStringPlatform",
5753
"JNU_SetFieldByName",
5854
"JNU_ThrowArrayIndexOutOfBoundsException",
5955
"JNU_ThrowByName",
60-
"JNU_ThrowIOException",
6156
"JNU_ThrowIllegalArgumentException",
6257
"JNU_ThrowInternalError",
6358
"JNU_ThrowNullPointerException",
64-
"JNU_ThrowOutOfMemoryError",
65-
"getEncodingFromLangID",
66-
"getJavaIDFromLangID");
59+
"JNU_ThrowOutOfMemoryError");
60+
if (isWindows()) {
61+
jniRegistrationSupport.addJvmShimExports(
62+
"JVM_CurrentTimeMillis",
63+
"JVM_RaiseSignal");
64+
jniRegistrationSupport.addJavaShimExports(
65+
"JDK_LoadSystemLibrary",
66+
"JNU_CallMethodByNameV",
67+
"JNU_ClassString",
68+
"JNU_GetFieldByName",
69+
"JNU_ThrowIOException",
70+
"getEncodingFromLangID",
71+
"getJavaIDFromLangID");
72+
} else {
73+
jniRegistrationSupport.addJvmShimExports(
74+
"jio_fprintf");
75+
jniRegistrationSupport.addJavaShimExports(
76+
"JNU_GetStringPlatformChars",
77+
"JNU_ReleaseStringPlatformChars");
78+
if (JavaVersionUtil.JAVA_SPEC == 11) {
79+
jniRegistrationSupport.addJavaShimExports(
80+
"JNU_ThrowNoSuchFieldError");
81+
}
82+
/* Since `awt` loads either `awt_headless` or `awt_xawt`, we register them both. */
83+
jniRegistrationSupport.registerLibrary("awt_headless");
84+
jniRegistrationSupport.registerLibrary("awt_xawt");
85+
}
6786
}
6887
if (jniRegistrationSupport.isRegisteredLibrary("javaaccessbridge")) {
6988
/* Dependency on `jawt` is not expressed in Java, so we register it manually here. */
@@ -73,14 +92,20 @@ public void afterAnalysis(AfterAnalysisAccess access) {
7392
jniRegistrationSupport.addJavaShimExports(
7493
"JNU_GetEnv",
7594
"JNU_ThrowByName",
76-
"JNU_ThrowNullPointerException",
77-
"jio_snprintf");
95+
"JNU_ThrowNullPointerException");
96+
if (isWindows()) {
97+
jniRegistrationSupport.addJavaShimExports(
98+
"jio_snprintf");
99+
} else {
100+
jniRegistrationSupport.addJvmShimExports(
101+
"jio_snprintf");
102+
}
78103
}
79104
}
80105

81106
@Override
82107
public void beforeImageWrite(BeforeImageWriteAccess access) {
83-
if (JNIRegistrationSupport.singleton().isRegisteredLibrary("awt")) {
108+
if (isWindows() && JNIRegistrationSupport.singleton().isRegisteredLibrary("awt")) {
84109
((BeforeImageWriteAccessImpl) access).registerLinkerInvocationTransformer(linkerInvocation -> {
85110
/*
86111
* Add a Windows library that is pulled in as a side effect of exporting the

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationSupport.java

Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,15 @@
3131
import java.nio.file.Files;
3232
import java.nio.file.NoSuchFileException;
3333
import java.nio.file.Path;
34-
import java.nio.file.Paths;
35-
import java.util.Arrays;
3634
import java.util.Collection;
37-
import java.util.Collections;
3835
import java.util.List;
3936
import java.util.SortedMap;
4037
import java.util.SortedSet;
4138
import java.util.TreeMap;
4239
import java.util.TreeSet;
4340
import java.util.concurrent.ConcurrentHashMap;
4441
import java.util.concurrent.ConcurrentMap;
42+
import java.util.stream.Stream;
4543

4644
import org.graalvm.compiler.debug.DebugContext;
4745
import org.graalvm.compiler.debug.DebugContext.Activation;
@@ -60,6 +58,7 @@
6058

6159
import com.oracle.svm.core.BuildArtifacts;
6260
import com.oracle.svm.core.ParsingReason;
61+
import com.oracle.svm.core.SubstrateOptions;
6362
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
6463
import com.oracle.svm.core.feature.InternalFeature;
6564
import com.oracle.svm.core.jdk.JNIRegistrationUtil;
@@ -161,48 +160,62 @@ void addJavaShimExports(String... exports) {
161160

162161
private void addShimExports(String shimName, String... exports) {
163162
assert exports != null && exports.length > 0;
164-
shimExports.computeIfAbsent(shimName, s -> new TreeSet<>()).addAll(Arrays.asList(exports));
163+
shimExports.computeIfAbsent(shimName, s -> new TreeSet<>()).addAll(List.of(exports));
164+
}
165+
166+
/** Returns symbols that are re-exported by shim libraries. */
167+
public static Stream<String> getShimLibrarySymbols() {
168+
if (ImageSingletons.contains(JNIRegistrationSupport.class)) {
169+
return singleton().getShimExports();
170+
}
171+
return Stream.empty();
165172
}
166173

167174
@Override
168175
public void beforeImageWrite(BeforeImageWriteAccess access) {
176+
if (SubstrateOptions.StaticExecutable.getValue() || isDarwin()) {
177+
return; /* Not supported. */
178+
}
179+
169180
if (shimExports.containsKey("jvm") || Options.CreateJvmShim.getValue()) {
170181
/* When making a `jvm` shim, also re-export the JNI functions that VM exports. */
171182
addJvmShimExports("JNI_CreateJavaVM", "JNI_GetCreatedJavaVMs", "JNI_GetDefaultJavaVMInitArgs");
172183
}
173184

174-
if (isWindows()) {
175-
((BeforeImageWriteAccessImpl) access).registerLinkerInvocationTransformer(linkerInvocation -> {
176-
/* Make sure the native image exports all the symbols necessary for shim DLLs. */
177-
shimExports.values().stream()
178-
.flatMap(Collection::stream)
179-
.distinct()
180-
.map("/export:"::concat)
181-
.forEach(linkerInvocation::addNativeLinkerOption);
182-
return linkerInvocation;
183-
});
184-
}
185+
((BeforeImageWriteAccessImpl) access).registerLinkerInvocationTransformer(linkerInvocation -> {
186+
/* Make sure the native image contains all symbols necessary for shim libraries. */
187+
getShimExports().map(isWindows() ? "/export:"::concat : "-Wl,-u,"::concat)
188+
.forEach(linkerInvocation::addNativeLinkerOption);
189+
return linkerInvocation;
190+
});
191+
}
192+
193+
private Stream<String> getShimExports() {
194+
return shimExports.values().stream()
195+
.flatMap(Collection::stream)
196+
.distinct();
185197
}
186198

187199
private AfterImageWriteAccessImpl accessImpl;
188200

189201
@Override
190202
@SuppressWarnings("try")
191203
public void afterImageWrite(AfterImageWriteAccess access) {
204+
if (SubstrateOptions.StaticExecutable.getValue() || isDarwin()) {
205+
return; /* Not supported. */
206+
}
207+
192208
accessImpl = (AfterImageWriteAccessImpl) access;
193209
try (Scope s = accessImpl.getDebugContext().scope("JDKLibs")) {
194-
if (isWindows()) {
195-
/* On Windows, JDK libraries are in `<java.home>\bin` directory. */
196-
Path jdkLibDir = Paths.get(System.getProperty("java.home"), "bin");
197-
/* Copy JDK libraries needed to run the native image. */
198-
copyJDKLibraries(jdkLibDir);
199-
/*
200-
* JDK libraries can depend on `jvm.dll` and `java.dll`, so to satisfy their
201-
* dependencies, we create shim DLLs that re-export the actual functions from the
202-
* native image itself.
203-
*/
204-
makeShimDLLs();
205-
}
210+
/* On Windows, JDK libraries are in `<java.home>\bin` directory. */
211+
Path jdkLibDir = Path.of(System.getProperty("java.home"), isWindows() ? "bin" : "lib");
212+
/* Copy JDK libraries needed to run the native image. */
213+
copyJDKLibraries(jdkLibDir);
214+
/*
215+
* JDK libraries can depend on `libjvm` and `libjava`, so to satisfy their dependencies
216+
* we use shim libraries to re-export the actual functions from the native image itself.
217+
*/
218+
makeShimLibraries();
206219
} finally {
207220
accessImpl = null;
208221
}
@@ -247,35 +260,50 @@ private void copyJDKLibraries(Path jdkLibDir) {
247260
}
248261
}
249262

250-
/** Makes shim DLLs to satisfy dependencies of JDK libraries. */
263+
/** Makes shim libraries that are necessary to satisfy dependencies of JDK libraries. */
251264
@SuppressWarnings("try")
252-
private void makeShimDLLs() {
265+
private void makeShimLibraries() {
253266
for (String shimName : shimExports.keySet()) {
254267
DebugContext debug = accessImpl.getDebugContext();
255-
try (Scope s = debug.scope(shimName + "ShimDLL")) {
268+
try (Scope s = debug.scope(shimName + "Shim")) {
256269
if (debug.isLogEnabled(DebugContext.INFO_LEVEL)) {
257270
debug.log("exports: %s", String.join(", ", shimExports.get(shimName)));
258271
}
259-
makeShimDLL(shimName);
272+
makeShimLibrary(shimName);
260273
}
261274
}
262275
}
263276

264-
/** Makes a shim DLL by re-exporting the actual functions from the native image itself. */
277+
/** Makes a shim library that re-exports functions from the native image. */
265278
@SuppressWarnings("try")
266-
private void makeShimDLL(String shimName) {
267-
Path shimDLL = accessImpl.getImagePath().resolveSibling(shimName + ".dll");
268-
/* Dependencies are the native image (so we can re-export from it) and C Runtime. */
269-
Path[] shimDLLDependencies = {getImageImportLib(), Paths.get("msvcrt.lib")};
270-
279+
private void makeShimLibrary(String shimName) {
271280
assert ImageSingletons.contains(CCompilerInvoker.class);
272-
List<String> linkerCommand = ImageSingletons.lookup(CCompilerInvoker.class)
273-
.createCompilerCommand(Collections.emptyList(), shimDLL, shimDLLDependencies);
274-
/* First add linker options ... */
275-
linkerCommand.addAll(Arrays.asList("/link", "/dll", "/implib:" + shimName + ".lib"));
276-
/* ... and then the exports that were added for re-export. */
277-
for (String export : shimExports.get(shimName)) {
278-
linkerCommand.add("/export:" + export);
281+
282+
List<String> linkerCommand;
283+
Path image = accessImpl.getImagePath();
284+
Path shimLibrary = image.resolveSibling(System.mapLibraryName(shimName));
285+
if (isWindows()) {
286+
/* Dependencies are the native image (so we can re-export from it) and C Runtime. */
287+
linkerCommand = ImageSingletons.lookup(CCompilerInvoker.class)
288+
.createCompilerCommand(List.of(), shimLibrary, getImageImportLib(), Path.of("msvcrt.lib"));
289+
/* First add linker options ... */
290+
linkerCommand.addAll(List.of("/link", "/dll", "/implib:" + shimName + ".lib"));
291+
/* ... and then the exports that were added for re-export. */
292+
for (String export : shimExports.get(shimName)) {
293+
linkerCommand.add("/export:" + export);
294+
}
295+
} else {
296+
/*
297+
* To satisfy the dynamic loader and enable re-export it is enough to have a library
298+
* with the expected name. So we just create an empty one ...
299+
*/
300+
linkerCommand = ImageSingletons.lookup(CCompilerInvoker.class)
301+
.createCompilerCommand(List.of("-shared", "-x", "c"), shimLibrary, Path.of("/dev/null"));
302+
/* ... and add an explicit dependency on the native image if it is a shared library. */
303+
if (!accessImpl.getImageKind().isExecutable) {
304+
linkerCommand.addAll(List.of("-Wl,-no-as-needed", "-L" + image.getParent(), "-l:" + image.getFileName(),
305+
"-Wl,--enable-new-dtags", "-Wl,-rpath,$ORIGIN"));
306+
}
279307
}
280308

281309
DebugContext debug = accessImpl.getDebugContext();
@@ -284,8 +312,8 @@ private void makeShimDLL(String shimName) {
284312
if (FileUtils.executeCommand(linkerCommand) != 0) {
285313
VMError.shouldNotReachHere();
286314
}
287-
BuildArtifacts.singleton().add(ArtifactType.JDK_LIBRARY_SHIM, shimDLL);
288-
debug.log("%s.dll: OK", shimName);
315+
BuildArtifacts.singleton().add(ArtifactType.JDK_LIBRARY_SHIM, shimLibrary);
316+
debug.log("%s: OK", shimLibrary.getFileName());
289317
} catch (InterruptedException e) {
290318
throw new InterruptImageBuilding();
291319
} catch (IOException e) {
@@ -295,6 +323,7 @@ private void makeShimDLL(String shimName) {
295323

296324
/** Returns the import library of the native image. */
297325
private Path getImageImportLib() {
326+
assert isWindows();
298327
Path image = accessImpl.getImagePath();
299328
String imageName = String.valueOf(image.getFileName());
300329
String importLibName = imageName.substring(0, imageName.lastIndexOf('.')) + ".lib";

0 commit comments

Comments
 (0)