package com.oracle.svm.hosted.image;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Formatter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.oracle.svm.core.c.libc.LibCBase;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.debug.Indent;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
import com.oracle.objectfile.ObjectFile;
import com.oracle.objectfile.macho.MachOSymtab;
import com.oracle.svm.core.LinkerInvocation;
import com.oracle.svm.core.OS;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.option.OptionUtils;
import com.oracle.svm.core.util.InterruptImageBuilding;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.FeatureImpl.BeforeImageWriteAccessImpl;
import com.oracle.svm.hosted.NativeImageOptions;
import com.oracle.svm.hosted.c.NativeLibraries;
import com.oracle.svm.hosted.c.util.FileUtils;
import com.oracle.svm.hosted.meta.HostedMetaAccess;
import com.oracle.svm.hosted.meta.HostedMethod;
import com.oracle.svm.hosted.meta.HostedUniverse;
public abstract class NativeBootImageViaCC extends NativeBootImage {
public NativeBootImageViaCC(NativeImageKind k, HostedUniverse universe, HostedMetaAccess metaAccess, NativeLibraries nativeLibs, NativeImageHeap heap, NativeImageCodeCache codeCache,
List<HostedMethod> entryPoints, ClassLoader imageClassLoader) {
super(k, universe, metaAccess, nativeLibs, heap, codeCache, entryPoints, imageClassLoader);
}
public NativeImageKind getOutputKind() {
return kind;
}
private static boolean removeUnusedSymbols() {
if (SubstrateOptions.RemoveUnusedSymbols.hasBeenSet()) {
return SubstrateOptions.RemoveUnusedSymbols.getValue();
}
return Platform.includedIn(Platform.LINUX.class);
}
class BinutilsCCLinkerInvocation extends CCLinkerInvocation {
private final boolean staticExecWithDynamicallyLinkLibC = SubstrateOptions.StaticExecutableWithDynamicLibC.getValue();
private final Set<String> libCLibaries = new HashSet<>(Arrays.asList("pthread", "dl", "rt", "m"));
BinutilsCCLinkerInvocation() {
additionalPreOptions.add("-z");
additionalPreOptions.add("noexecstack");
if (SubstrateOptions.ForceNoROSectionRelocations.getValue()) {
additionalPreOptions.add("-fuse-ld=gold");
additionalPreOptions.add("-Wl,--rosegment");
}
if (removeUnusedSymbols()) {
additionalPreOptions.add("-Wl,--gc-sections");
}
if (!SubstrateOptions.StaticExecutable.getValue()) {
try {
StringBuilder exportedSymbols = new StringBuilder();
exportedSymbols.append("{\n");
for (String symbol : getImageSymbols(true)) {
exportedSymbols.append('\"').append(symbol).append("\";\n");
}
exportedSymbols.append("};");
Path exportedSymbolsPath = nativeLibs.tempDirectory.resolve("exported_symbols.list");
Files.write(exportedSymbolsPath, Collections.singleton(exportedSymbols.toString()));
additionalPreOptions.add("-Wl,--dynamic-list");
additionalPreOptions.add("-Wl," + exportedSymbolsPath.toAbsolutePath());
additionalPreOptions.add("-Wl,--exclude-libs,ALL");
} catch (IOException e) {
VMError.shouldNotReachHere();
}
}
if (SubstrateOptions.DeleteLocalSymbols.getValue()) {
additionalPreOptions.add("-Wl,-x");
}
}
@Override
public List<String> getImageSymbols(boolean onlyGlobal) {
return codeCache.getSymbols(getOrCreateDebugObjectFile(), onlyGlobal).stream()
.map(ObjectFile.Symbol::getName)
.collect(Collectors.toList());
}
@Override
protected void setOutputKind(List<String> cmd) {
switch (kind) {
case EXECUTABLE:
break;
case STATIC_EXECUTABLE:
if (!staticExecWithDynamicallyLinkLibC) {
cmd.add("-static");
}
break;
case SHARED_LIBRARY:
cmd.add("-shared");
break;
default:
VMError.shouldNotReachHere();
}
}
@Override
protected List<String> getLibrariesCommand() {
List<String> cmd = new ArrayList<>();
for (String lib : libs) {
if (staticExecWithDynamicallyLinkLibC) {
String linkingMode = libCLibaries.contains(lib)
? "dynamic"
: "static";
cmd.add("-Wl,-B" + linkingMode);
}
cmd.add("-l" + lib);
}
if (staticExecWithDynamicallyLinkLibC) {
cmd.add("-static-libgcc");
}
return cmd;
}
}
class DarwinCCLinkerInvocation extends CCLinkerInvocation {
DarwinCCLinkerInvocation() {
if (!SubstrateOptions.useLLVMBackend()) {
additionalPreOptions.add("-Wl,-no_compact_unwind");
}
if (removeUnusedSymbols()) {
additionalPreOptions.add("-Wl,-dead_strip");
}
try {
Path exportedSymbolsPath = nativeLibs.tempDirectory.resolve("exported_symbols.list");
Files.write(exportedSymbolsPath, getImageSymbols(true));
additionalPreOptions.add("-Wl,-exported_symbols_list");
additionalPreOptions.add("-Wl," + exportedSymbolsPath.toAbsolutePath());
} catch (IOException e) {
VMError.shouldNotReachHere();
}
if (SubstrateOptions.DeleteLocalSymbols.getValue()) {
additionalPreOptions.add("-Wl,-x");
}
additionalPreOptions.add("-arch");
if (Platform.includedIn(Platform.AMD64.class)) {
additionalPreOptions.add("x86_64");
} else if (Platform.includedIn(Platform.AARCH64.class)) {
additionalPreOptions.add("arm64");
}
}
@Override
public List<String> getImageSymbols(boolean onlyGlobal) {
return codeCache.getSymbols(getOrCreateDebugObjectFile(), onlyGlobal).stream()
.map(symbol -> ((MachOSymtab.Entry) symbol).getNameInObject())
.collect(Collectors.toList());
}
@Override
protected void setOutputKind(List<String> cmd) {
switch (kind) {
case STATIC_EXECUTABLE:
throw UserError.abort("%s does not support building static executable images.", OS.getCurrent().name());
case SHARED_LIBRARY:
cmd.add("-shared");
if (Platform.includedIn(Platform.DARWIN.class)) {
cmd.add("-undefined");
cmd.add("dynamic_lookup");
}
break;
}
}
}
class WindowsCCLinkerInvocation extends CCLinkerInvocation {
@Override
protected void setOutputKind(List<String> cmd) {
switch (kind) {
case EXECUTABLE:
case STATIC_EXECUTABLE:
cmd.add("/MD");
break;
case SHARED_LIBRARY:
cmd.add("/MD");
cmd.add("/LD");
break;
default:
VMError.shouldNotReachHere();
}
}
@Override
public List<String> getImageSymbols(boolean onlyGlobal) {
return codeCache.getSymbols(getOrCreateDebugObjectFile(), onlyGlobal).stream()
.map(ObjectFile.Symbol::getName)
.collect(Collectors.toList());
}
@Override
public List<String> getCommand() {
List<String> compilerCmd = getCompilerCommand(additionalPreOptions);
List<String> cmd = new ArrayList<>(compilerCmd);
setOutputKind(cmd);
cmd.add("/Zi");
for (Path staticLibrary : nativeLibs.getStaticLibraries()) {
cmd.add(staticLibrary.toString());
}
cmd.add("/link");
cmd.add("/INCREMENTAL:NO");
cmd.add("/NODEFAULTLIB:LIBCMT");
if (SubstrateOptions.DeleteLocalSymbols.getValue()) {
String outputFileString = getOutputFile().toString();
String outputFileSuffix = getOutputKind().getFilenameSuffix();
String pdbFile = outputFileString.substring(0, outputFileString.length() - outputFileSuffix.length()) + ".stripped.pdb";
cmd.add("/PDBSTRIPPED:" + pdbFile);
}
if (removeUnusedSymbols()) {
cmd.add("/OPT:REF");
}
for (String libraryPath : nativeLibs.getLibraryPaths()) {
cmd.add("/LIBPATH:" + libraryPath);
}
for (String library : nativeLibs.getLibraries()) {
cmd.add(library + ".lib");
}
cmd.add("advapi32.lib");
cmd.add("ws2_32.lib");
cmd.add("secur32.lib");
cmd.add("iphlpapi.lib");
cmd.add("userenv.lib");
Collections.addAll(cmd, Options.NativeLinkerOption.getValue());
return cmd;
}
}
LinkerInvocation getLinkerInvocation(Path outputDirectory, Path tempDirectory, String imageName) {
CCLinkerInvocation inv;
switch (ObjectFile.getNativeFormat()) {
case MACH_O:
inv = new DarwinCCLinkerInvocation();
break;
case PECOFF:
inv = new WindowsCCLinkerInvocation();
break;
case ELF:
default:
inv = new BinutilsCCLinkerInvocation();
break;
}
if (SubstrateOptions.AdditionalLinkerOptions.hasBeenSet()) {
inv.additionalPreOptions.add(SubstrateOptions.AdditionalLinkerOptions.getValue());
}
Path outputFile = outputDirectory.resolve(imageName + getBootImageKind().getFilenameSuffix());
UserError.guarantee(!Files.isDirectory(outputFile), "Cannot write image to %s. Path exists as directory. (Use -H:Name=<image name>)", outputFile);
inv.setOutputFile(outputFile);
inv.setOutputKind(getOutputKind());
inv.setTempDirectory(tempDirectory);
inv.addLibPath(tempDirectory.toString());
for (String libraryPath : nativeLibs.getLibraryPaths()) {
inv.addLibPath(libraryPath);
}
for (String rPath : OptionUtils.flatten(",", SubstrateOptions.LinkerRPath.getValue())) {
inv.addRPath(rPath);
}
Collection<String> libraries = nativeLibs.getLibraries();
if (Platform.includedIn(Platform.LINUX.class) && ImageSingletons.lookup(LibCBase.class).getName().equals("bionic")) {
libraries = libraries.stream().filter(library -> !Arrays.asList("pthread", "rt").contains(library)).collect(Collectors.toList());
}
libraries.forEach(inv::addLinkedLibrary);
for (Path filename : codeCache.getCCInputFiles(tempDirectory, imageName)) {
inv.addInputFile(filename);
}
for (Path staticLibraryPath : nativeLibs.getStaticLibraries()) {
inv.addInputFile(staticLibraryPath);
}
return inv;
}
private static List<String> diagnoseLinkerFailure(String linkerOutput) {
List<String> potentialCauses = new ArrayList<>();
if (linkerOutput.contains("access beyond end of merged section")) {
potentialCauses.add("Native Image is using a linker that appears to be incompatible with the tool chain used to build the JDK static libraries. " +
"The latter is typically shown in the output of `java -Xinternalversion`.");
}
if (SubstrateOptions.ForceNoROSectionRelocations.getValue() && (linkerOutput.contains("fatal error: cannot find ") ||
linkerOutput.contains("error: invalid linker name in argument"))) {
potentialCauses.add(SubstrateOptions.ForceNoROSectionRelocations.getName() + " option cannot be used if ld.gold linker is missing from the host system");
}
Pattern p = Pattern.compile(".*cannot find -l([^\\s]+)\\s.*", Pattern.DOTALL);
Matcher m = p.matcher(linkerOutput);
if (m.matches()) {
OS os = OS.getCurrent();
String libPrefix = os == OS.WINDOWS ? "" : "lib";
String libSuffix = os == OS.WINDOWS ? ".lib" : ".a";
potentialCauses.add(String.format("It appears as though %s%s%s is missing. Please install it.", libPrefix, m.group(1), libSuffix));
}
return potentialCauses;
}
@Override
@SuppressWarnings("try")
public LinkerInvocation write(DebugContext debug, Path outputDirectory, Path tempDirectory, String imageName, BeforeImageWriteAccessImpl config) {
try (Indent indent = debug.logAndIndent("Writing native image")) {
write(debug, tempDirectory.resolve(imageName + ObjectFile.getFilenameSuffix()));
if (NativeImageOptions.ExitAfterRelocatableImageWrite.getValue()) {
return null;
}
LinkerInvocation inv = getLinkerInvocation(outputDirectory, tempDirectory, imageName);
for (Function<LinkerInvocation, LinkerInvocation> fn : config.getLinkerInvocationTransformers()) {
inv = fn.apply(inv);
}
List<String> cmd = inv.getCommand();
String commandLine = SubstrateUtil.getShellCommandString(cmd, false);
Process linkerProcess = null;
try {
ProcessBuilder linkerCommand = FileUtils.prepareCommand(cmd, inv.getTempDirectory());
linkerCommand.redirectErrorStream(true);
FileUtils.traceCommand(linkerCommand);
linkerProcess = linkerCommand.start();
List<String> lines;
try (InputStream inputStream = linkerProcess.getInputStream()) {
lines = FileUtils.readAllLines(inputStream);
FileUtils.traceCommandOutput(lines);
}
int status = linkerProcess.waitFor();
if (status != 0) {
String output = String.join(System.lineSeparator(), lines);
throw handleLinkerFailure("Linker command exited with " + status, commandLine, output);
}
} catch (IOException e) {
throw handleLinkerFailure(e.toString(), commandLine, null);
} catch (InterruptedException e) {
throw new InterruptImageBuilding("Interrupted during native-image linking step for " + imageName);
} finally {
if (linkerProcess != null) {
linkerProcess.destroy();
}
}
return inv;
}
}
private static RuntimeException handleLinkerFailure(String message, String commandLine, String output) {
Formatter buf = new Formatter();
buf.format("There was an error linking the native image: %s%n%n", message);
List<String> potentialCauses = output == null ? Collections.emptyList() : diagnoseLinkerFailure(output);
if (!potentialCauses.isEmpty()) {
int causeNum = 1;
buf.format("Based on the linker command output, possible reasons for this include:%n");
for (String cause : potentialCauses) {
buf.format("%d. %s%n", causeNum, cause);
causeNum++;
}
buf.format("%n");
}
buf.format("Linker command executed:%n%s", commandLine);
if (output != null) {
buf.format("%n%nLinker command output:%n%s", output);
}
throw new RuntimeException(buf.toString());
}
}