mirror of
https://github.com/darlinghq/darling-openjdk.git
synced 2024-11-26 22:00:25 +00:00
8214796: Create a jlink plugin for stripping debug info symbols from native libraries
Reviewed-by: alanb, mchung, erikj, ihse
This commit is contained in:
parent
2b55e4834e
commit
1f115c7f52
@ -103,7 +103,7 @@ define SetupTestFilesCompilationBody
|
||||
TOOLCHAIN := $(if $$(filter %.cpp, $$(file)), TOOLCHAIN_LINK_CXX, TOOLCHAIN_DEFAULT), \
|
||||
OPTIMIZATION := $$(if $$($1_OPTIMIZATION_$$(name)),$$($1_OPTIMIZATION_$$(name)),LOW), \
|
||||
COPY_DEBUG_SYMBOLS := false, \
|
||||
STRIP_SYMBOLS := false, \
|
||||
STRIP_SYMBOLS := $$(if $$($1_STRIP_SYMBOLS_$$(name)),$$($1_STRIP_SYMBOLS_$$(name)),false), \
|
||||
)) \
|
||||
$$(eval $1 += $$(BUILD_TEST_$$(name)) ) \
|
||||
)
|
||||
|
@ -24,13 +24,17 @@
|
||||
#
|
||||
|
||||
include GensrcCommonJdk.gmk
|
||||
include GensrcProperties.gmk
|
||||
include Modules.gmk
|
||||
|
||||
################################################################################
|
||||
|
||||
include GensrcProperties.gmk
|
||||
# Use wildcard so as to avoid getting non-existing directories back
|
||||
JLINK_RESOURCES_DIRS := $(wildcard $(addsuffix /jdk/tools/jlink/resources, \
|
||||
$(call FindModuleSrcDirs, jdk.jlink)))
|
||||
|
||||
$(eval $(call SetupCompileProperties, JLINK_PROPERTIES, \
|
||||
SRC_DIRS := $(TOPDIR)/src/jdk.jlink/share/classes/jdk/tools/jlink/resources, \
|
||||
SRC_DIRS := $(JLINK_RESOURCES_DIRS), \
|
||||
CLASS := ListResourceBundle, \
|
||||
))
|
||||
|
||||
|
@ -84,6 +84,13 @@ else
|
||||
BUILD_JDK_JTREG_EXCLUDE += exeJniInvocationTest.c
|
||||
endif
|
||||
|
||||
ifeq ($(call isTargetOs, linux), true)
|
||||
# Unconditionally compile with debug symbols and don't ever perform
|
||||
# stripping during the test libraries' build.
|
||||
BUILD_JDK_JTREG_LIBRARIES_CFLAGS_libFib := -g
|
||||
BUILD_JDK_JTREG_LIBRARIES_STRIP_SYMBOLS_libFib := false
|
||||
endif
|
||||
|
||||
# This evaluation is expensive and should only be done if this target was
|
||||
# explicitly called.
|
||||
ifneq ($(filter build-test-jdk-jtreg-native, $(MAKECMDGOALS)), )
|
||||
|
@ -0,0 +1,488 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Red Hat, Inc.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.tools.jlink.internal.plugins;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ProcessBuilder.Redirect;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import jdk.tools.jlink.plugin.Plugin;
|
||||
import jdk.tools.jlink.plugin.PluginException;
|
||||
import jdk.tools.jlink.plugin.ResourcePool;
|
||||
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
|
||||
import jdk.tools.jlink.plugin.ResourcePoolEntry;
|
||||
|
||||
/**
|
||||
* Platform specific jlink plugin for stripping debug symbols from native
|
||||
* libraries and binaries.
|
||||
*
|
||||
*/
|
||||
public final class StripNativeDebugSymbolsPlugin implements Plugin {
|
||||
|
||||
public static final String NAME = "strip-native-debug-symbols";
|
||||
private static final boolean DEBUG = Boolean.getBoolean("jlink.debug");
|
||||
private static final String DEFAULT_STRIP_CMD = "objcopy";
|
||||
private static final String STRIP_CMD_ARG = DEFAULT_STRIP_CMD;
|
||||
private static final String KEEP_DEBUG_INFO_ARG = "keep-debuginfo-files";
|
||||
private static final String EXCLUDE_DEBUG_INFO_ARG = "exclude-debuginfo-files";
|
||||
private static final String DEFAULT_DEBUG_EXT = "debuginfo";
|
||||
private static final String STRIP_DEBUG_SYMS_OPT = "-g";
|
||||
private static final String ONLY_KEEP_DEBUG_SYMS_OPT = "--only-keep-debug";
|
||||
private static final String ADD_DEBUG_LINK_OPT = "--add-gnu-debuglink";
|
||||
private static final ResourceBundle resourceBundle;
|
||||
private static final String SHARED_LIBS_EXT = ".so"; // for Linux/Unix
|
||||
|
||||
static {
|
||||
Locale locale = Locale.getDefault();
|
||||
try {
|
||||
resourceBundle = ResourceBundle.getBundle("jdk.tools.jlink."
|
||||
+ "resources.strip_native_debug_symbols_plugin", locale);
|
||||
} catch (MissingResourceException e) {
|
||||
throw new InternalError("Cannot find jlink plugin resource bundle (" +
|
||||
NAME + ") for locale " + locale);
|
||||
}
|
||||
}
|
||||
|
||||
private final ObjCopyCmdBuilder cmdBuilder;
|
||||
private boolean includeDebugSymbols;
|
||||
private String stripBin;
|
||||
private String debuginfoExt;
|
||||
|
||||
public StripNativeDebugSymbolsPlugin() {
|
||||
this(new DefaultObjCopyCmdBuilder());
|
||||
}
|
||||
|
||||
public StripNativeDebugSymbolsPlugin(ObjCopyCmdBuilder cmdBuilder) {
|
||||
this.cmdBuilder = cmdBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
|
||||
StrippedDebugInfoBinaryBuilder builder = new StrippedDebugInfoBinaryBuilder(
|
||||
includeDebugSymbols,
|
||||
debuginfoExt,
|
||||
cmdBuilder,
|
||||
stripBin);
|
||||
in.transformAndCopy((resource) -> {
|
||||
ResourcePoolEntry res = resource;
|
||||
if ((resource.type() == ResourcePoolEntry.Type.NATIVE_LIB &&
|
||||
resource.path().endsWith(SHARED_LIBS_EXT)) ||
|
||||
resource.type() == ResourcePoolEntry.Type.NATIVE_CMD) {
|
||||
Optional<StrippedDebugInfoBinary> strippedBin = builder.build(resource);
|
||||
if (strippedBin.isPresent()) {
|
||||
StrippedDebugInfoBinary sb = strippedBin.get();
|
||||
res = sb.strippedBinary();
|
||||
if (includeDebugSymbols) {
|
||||
Optional<ResourcePoolEntry> debugInfo = sb.debugSymbols();
|
||||
if (debugInfo.isEmpty()) {
|
||||
String key = NAME + ".error.debugfile";
|
||||
logError(resource, key);
|
||||
} else {
|
||||
out.add(debugInfo.get());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String key = NAME + ".error.file";
|
||||
logError(resource, key);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}, out);
|
||||
|
||||
return out.build();
|
||||
}
|
||||
|
||||
private void logError(ResourcePoolEntry resource, String msgKey) {
|
||||
String msg = PluginsResourceBundle.getMessage(resourceBundle,
|
||||
msgKey,
|
||||
NAME,
|
||||
resource.path());
|
||||
System.err.println(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getType() {
|
||||
return Category.TRANSFORMER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
String key = NAME + ".description";
|
||||
return PluginsResourceBundle.getMessage(resourceBundle, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArguments() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArgumentsDescription() {
|
||||
String key = NAME + ".argument";
|
||||
return PluginsResourceBundle.getMessage(resourceBundle, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(Map<String, String> config) {
|
||||
doConfigure(true, config);
|
||||
}
|
||||
|
||||
// For testing so that validation can be turned off
|
||||
public void doConfigure(boolean withChecks, Map<String, String> orig) {
|
||||
Map<String, String> config = new HashMap<>(orig);
|
||||
String arg = config.remove(NAME);
|
||||
|
||||
stripBin = DEFAULT_STRIP_CMD;
|
||||
debuginfoExt = DEFAULT_DEBUG_EXT;
|
||||
|
||||
// argument must never be null as it requires at least one
|
||||
// argument, since hasArguments() == true. This might change once
|
||||
// 8218761 is implemented.
|
||||
if (arg == null) {
|
||||
throw new InternalError();
|
||||
}
|
||||
boolean hasOmitDebugInfo = false;
|
||||
boolean hasKeepDebugInfo = false;
|
||||
|
||||
if (KEEP_DEBUG_INFO_ARG.equals(arg)) {
|
||||
// Case: --strip-native-debug-symbols keep-debuginfo-files
|
||||
hasKeepDebugInfo = true;
|
||||
} else if (arg.startsWith(KEEP_DEBUG_INFO_ARG)) {
|
||||
// Case: --strip-native-debug-symbols keep-debuginfo-files=foo
|
||||
String[] tokens = arg.split("=");
|
||||
if (tokens.length != 2 || !KEEP_DEBUG_INFO_ARG.equals(tokens[0])) {
|
||||
throw new IllegalArgumentException(
|
||||
PluginsResourceBundle.getMessage(resourceBundle,
|
||||
NAME + ".iae", NAME, arg));
|
||||
}
|
||||
hasKeepDebugInfo = true;
|
||||
debuginfoExt = tokens[1];
|
||||
}
|
||||
if (EXCLUDE_DEBUG_INFO_ARG.equals(arg) || arg.startsWith(EXCLUDE_DEBUG_INFO_ARG + "=")) {
|
||||
// Case: --strip-native-debug-symbols exclude-debuginfo-files[=something]
|
||||
hasOmitDebugInfo = true;
|
||||
}
|
||||
if (arg.startsWith(STRIP_CMD_ARG)) {
|
||||
// Case: --strip-native-debug-symbols objcopy=<path/to/objcopy
|
||||
String[] tokens = arg.split("=");
|
||||
if (tokens.length != 2 || !STRIP_CMD_ARG.equals(tokens[0])) {
|
||||
throw new IllegalArgumentException(
|
||||
PluginsResourceBundle.getMessage(resourceBundle,
|
||||
NAME + ".iae", NAME, arg));
|
||||
}
|
||||
if (withChecks) {
|
||||
validateStripArg(tokens[1]);
|
||||
}
|
||||
stripBin = tokens[1];
|
||||
}
|
||||
// Cases (combination of options):
|
||||
// --strip-native-debug-symbols keep-debuginfo-files:objcopy=</objcpy/path>
|
||||
// --strip-native-debug-symbols keep-debuginfo-files=ext:objcopy=</objcpy/path>
|
||||
// --strip-native-debug-symbols exclude-debuginfo-files:objcopy=</objcpy/path>
|
||||
String stripArg = config.remove(STRIP_CMD_ARG);
|
||||
if (stripArg != null && withChecks) {
|
||||
validateStripArg(stripArg);
|
||||
}
|
||||
if (stripArg != null) {
|
||||
stripBin = stripArg;
|
||||
}
|
||||
// Case (reversed combination)
|
||||
// --strip-native-debug-symbols objcopy=</objcpy/path>:keep-debuginfo-files=ext
|
||||
// Note: cases like the following are not allowed by the parser
|
||||
// --strip-native-debug-symbols objcopy=</objcpy/path>:keep-debuginfo-files
|
||||
// --strip-native-debug-symbols objcopy=</objcpy/path>:exclude-debuginfo-files
|
||||
String keepDebugInfo = config.remove(KEEP_DEBUG_INFO_ARG);
|
||||
if (keepDebugInfo != null) {
|
||||
hasKeepDebugInfo = true;
|
||||
debuginfoExt = keepDebugInfo;
|
||||
}
|
||||
if ((hasKeepDebugInfo || includeDebugSymbols) && hasOmitDebugInfo) {
|
||||
// Cannot keep and omit debug info at the same time. Note that
|
||||
// includeDebugSymbols might already be true if configure is being run
|
||||
// on the same plugin instance multiple times. Plugin option can
|
||||
// repeat.
|
||||
throw new IllegalArgumentException(
|
||||
PluginsResourceBundle.getMessage(resourceBundle,
|
||||
NAME + ".iae.conflict",
|
||||
NAME,
|
||||
EXCLUDE_DEBUG_INFO_ARG,
|
||||
KEEP_DEBUG_INFO_ARG));
|
||||
}
|
||||
if (!arg.startsWith(STRIP_CMD_ARG) &&
|
||||
!arg.startsWith(KEEP_DEBUG_INFO_ARG) &&
|
||||
!arg.startsWith(EXCLUDE_DEBUG_INFO_ARG)) {
|
||||
// unknown arg value; case --strip-native-debug-symbols foobar
|
||||
throw new IllegalArgumentException(
|
||||
PluginsResourceBundle.getMessage(resourceBundle,
|
||||
NAME + ".iae", NAME, arg));
|
||||
}
|
||||
if (!config.isEmpty()) {
|
||||
// extraneous values; --strip-native-debug-symbols keep-debuginfo-files:foo=bar
|
||||
throw new IllegalArgumentException(
|
||||
PluginsResourceBundle.getMessage(resourceBundle,
|
||||
NAME + ".iae", NAME,
|
||||
config.toString()));
|
||||
}
|
||||
includeDebugSymbols = hasKeepDebugInfo;
|
||||
}
|
||||
|
||||
private void validateStripArg(String stripArg) throws IllegalArgumentException {
|
||||
try {
|
||||
Path strip = Paths.get(stripArg); // verify it's a resonable path
|
||||
if (!Files.isExecutable(strip)) {
|
||||
throw new IllegalArgumentException(
|
||||
PluginsResourceBundle.getMessage(resourceBundle,
|
||||
NAME + ".invalidstrip",
|
||||
stripArg));
|
||||
}
|
||||
} catch (InvalidPathException e) {
|
||||
throw new IllegalArgumentException(
|
||||
PluginsResourceBundle.getMessage(resourceBundle,
|
||||
NAME + ".invalidstrip",
|
||||
e.getInput()));
|
||||
}
|
||||
}
|
||||
|
||||
private static class StrippedDebugInfoBinaryBuilder {
|
||||
|
||||
private final boolean includeDebug;
|
||||
private final String debugExt;
|
||||
private final ObjCopyCmdBuilder cmdBuilder;
|
||||
private final String strip;
|
||||
|
||||
private StrippedDebugInfoBinaryBuilder(boolean includeDebug,
|
||||
String debugExt,
|
||||
ObjCopyCmdBuilder cmdBuilder,
|
||||
String strip) {
|
||||
this.includeDebug = includeDebug;
|
||||
this.debugExt = debugExt;
|
||||
this.cmdBuilder = cmdBuilder;
|
||||
this.strip = strip;
|
||||
}
|
||||
|
||||
private Optional<StrippedDebugInfoBinary> build(ResourcePoolEntry resource) {
|
||||
Path tempDir = null;
|
||||
Optional<ResourcePoolEntry> debugInfo = Optional.empty();
|
||||
try {
|
||||
Path resPath = Paths.get(resource.path());
|
||||
String relativeFileName = resPath.getFileName().toString();
|
||||
tempDir = Files.createTempDirectory(NAME + relativeFileName);
|
||||
Path resourceFileBinary = tempDir.resolve(relativeFileName);
|
||||
String relativeDbgFileName = relativeFileName + "." + debugExt;
|
||||
|
||||
Files.write(resourceFileBinary, resource.contentBytes());
|
||||
Path resourceFileDebugSymbols;
|
||||
if (includeDebug) {
|
||||
resourceFileDebugSymbols = tempDir.resolve(Paths.get(relativeDbgFileName));
|
||||
String debugEntryPath = resource.path() + "." + debugExt;
|
||||
byte[] debugInfoBytes = createDebugSymbolsFile(resourceFileBinary,
|
||||
resourceFileDebugSymbols,
|
||||
relativeDbgFileName);
|
||||
if (debugInfoBytes != null) {
|
||||
ResourcePoolEntry debugEntry = ResourcePoolEntry.create(
|
||||
debugEntryPath,
|
||||
resource.type(),
|
||||
debugInfoBytes);
|
||||
debugInfo = Optional.of(debugEntry);
|
||||
}
|
||||
}
|
||||
if (!stripBinary(resourceFileBinary)) {
|
||||
if (DEBUG) {
|
||||
System.err.println("DEBUG: Stripping debug info failed.");
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
if (includeDebug && !addGnuDebugLink(tempDir,
|
||||
relativeFileName,
|
||||
relativeDbgFileName)) {
|
||||
if (DEBUG) {
|
||||
System.err.println("DEBUG: Creating debug link failed.");
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
byte[] strippedBytes = Files.readAllBytes(resourceFileBinary);
|
||||
ResourcePoolEntry strippedResource = resource.copyWithContent(strippedBytes);
|
||||
return Optional.of(new StrippedDebugInfoBinary(strippedResource, debugInfo));
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new PluginException(e);
|
||||
} finally {
|
||||
if (tempDir != null) {
|
||||
deleteDirRecursivelyIgnoreResult(tempDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Equivalent of 'objcopy -g binFile'. Returning true iff stripping of the binary
|
||||
* succeeded.
|
||||
*/
|
||||
private boolean stripBinary(Path binFile)
|
||||
throws InterruptedException, IOException {
|
||||
String filePath = binFile.toAbsolutePath().toString();
|
||||
List<String> stripCmdLine = cmdBuilder.build(strip, STRIP_DEBUG_SYMS_OPT,
|
||||
filePath);
|
||||
ProcessBuilder builder = createProcessBuilder(stripCmdLine);
|
||||
Process stripProc = builder.start();
|
||||
int retval = stripProc.waitFor();
|
||||
return retval == 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Equivalent of 'objcopy --add-gnu-debuglink=relativeDbgFileName binFile'.
|
||||
* Returning true iff adding the debug link succeeded.
|
||||
*/
|
||||
private boolean addGnuDebugLink(Path currDir,
|
||||
String binFile,
|
||||
String relativeDbgFileName)
|
||||
throws InterruptedException, IOException {
|
||||
List<String> addDbgLinkCmdLine = cmdBuilder.build(strip, ADD_DEBUG_LINK_OPT +
|
||||
"=" + relativeDbgFileName,
|
||||
binFile);
|
||||
ProcessBuilder builder = createProcessBuilder(addDbgLinkCmdLine);
|
||||
builder.directory(currDir.toFile());
|
||||
Process stripProc = builder.start();
|
||||
int retval = stripProc.waitFor();
|
||||
return retval == 0;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Equivalent of 'objcopy --only-keep-debug binPath debugPath'.
|
||||
* Returning the bytes of the file containing debug symbols.
|
||||
*/
|
||||
private byte[] createDebugSymbolsFile(Path binPath,
|
||||
Path debugPath,
|
||||
String dbgFileName) throws InterruptedException,
|
||||
IOException {
|
||||
String filePath = binPath.toAbsolutePath().toString();
|
||||
String dbgPath = debugPath.toAbsolutePath().toString();
|
||||
List<String> createLinkCmdLine = cmdBuilder.build(strip,
|
||||
ONLY_KEEP_DEBUG_SYMS_OPT,
|
||||
filePath,
|
||||
dbgPath);
|
||||
ProcessBuilder builder = createProcessBuilder(createLinkCmdLine);
|
||||
Process stripProc = builder.start();
|
||||
int retval = stripProc.waitFor();
|
||||
if (retval != 0) {
|
||||
if (DEBUG) {
|
||||
System.err.println("DEBUG: Creating debuginfo file failed.");
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return Files.readAllBytes(debugPath);
|
||||
}
|
||||
}
|
||||
|
||||
private ProcessBuilder createProcessBuilder(List<String> cmd) {
|
||||
ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||
builder.redirectError(Redirect.INHERIT);
|
||||
builder.redirectOutput(Redirect.INHERIT);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private void deleteDirRecursivelyIgnoreResult(Path tempDir) {
|
||||
try {
|
||||
Files.walkFileTree(tempDir, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file,
|
||||
BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir,
|
||||
IOException exc) throws IOException {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
// ignore deleting the temp dir
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class StrippedDebugInfoBinary {
|
||||
private final ResourcePoolEntry strippedBinary;
|
||||
private final Optional<ResourcePoolEntry> debugSymbols;
|
||||
|
||||
private StrippedDebugInfoBinary(ResourcePoolEntry strippedBinary,
|
||||
Optional<ResourcePoolEntry> debugSymbols) {
|
||||
this.strippedBinary = Objects.requireNonNull(strippedBinary);
|
||||
this.debugSymbols = Objects.requireNonNull(debugSymbols);
|
||||
}
|
||||
|
||||
public ResourcePoolEntry strippedBinary() {
|
||||
return strippedBinary;
|
||||
}
|
||||
|
||||
public Optional<ResourcePoolEntry> debugSymbols() {
|
||||
return debugSymbols;
|
||||
}
|
||||
}
|
||||
|
||||
// For better testing using mocked objcopy
|
||||
public static interface ObjCopyCmdBuilder {
|
||||
List<String> build(String objCopy, String...options);
|
||||
}
|
||||
|
||||
private static final class DefaultObjCopyCmdBuilder implements ObjCopyCmdBuilder {
|
||||
|
||||
@Override
|
||||
public List<String> build(String objCopy, String...options) {
|
||||
List<String> cmdList = new ArrayList<>();
|
||||
cmdList.add(objCopy);
|
||||
if (options.length > 0) {
|
||||
cmdList.addAll(Arrays.asList(options));
|
||||
}
|
||||
return cmdList;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
#
|
||||
# Copyright (c) 2019, Red Hat Inc.
|
||||
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
#
|
||||
# This code is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 2 only, as
|
||||
# published by the Free Software Foundation. Oracle designates this
|
||||
# particular file as subject to the "Classpath" exception as provided
|
||||
# by Oracle in the LICENSE file that accompanied this code.
|
||||
#
|
||||
# This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
# version 2 for more details (a copy is included in the LICENSE file that
|
||||
# accompanied this code).
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License version
|
||||
# 2 along with this work; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
# or visit www.oracle.com if you need additional information or have any
|
||||
# questions.
|
||||
#
|
||||
strip-native-debug-symbols.description=\
|
||||
Strip debug symbols from native libraries (if any). \n\
|
||||
\ This plugin requires at least one option: \n\
|
||||
\ \ \ objcopy: The path to the 'objcopy' binary. Defaults to 'objcopy' in PATH.\n\
|
||||
\ \ \ exclude-debuginfo-files: Exclude debug info files. Defaults to true.\n\
|
||||
\ \ \ keep-debuginfo-files[=<ext>]: Keep debug info files in <file>.<ext>.\n\
|
||||
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Defaults to <file>.debuginfo \n\
|
||||
\ Examples: --strip-native-debug-symbols keep-debuginfo-files:objcopy=/usr/bin/objcopy \n\
|
||||
\ \ \ \ \ \ \ \ \ \ \ --strip-native-debug-symbols=exclude-debuginfo-files\n
|
||||
|
||||
strip-native-debug-symbols.argument=\
|
||||
<exclude-debuginfo-files|keep-debuginfo-files|objcopy=/path/to/objcopy>
|
||||
|
||||
strip-native-debug-symbols.invalidstrip=Invalid objcopy command: {0}
|
||||
|
||||
strip-native-debug-symbols.iae={0}: Unrecognized argument ''{1}''
|
||||
|
||||
strip-native-debug-symbols.iae.conflict=\
|
||||
{0}: Cannot use ''{1}'' and ''{2}'' at the same time
|
||||
|
||||
strip-native-debug-symbols.error.file=Error: {0}: Stripping debug info for file ''{1}'' failed.
|
||||
strip-native-debug-symbols.error.debugfile=Error: {0}: Creating debug info file for ''{1}'' failed.
|
27
src/jdk.jlink/linux/classes/module-info.java.extra
Normal file
27
src/jdk.jlink/linux/classes/module-info.java.extra
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Red Hat, Inc. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
provides jdk.tools.jlink.plugin.Plugin with
|
||||
jdk.tools.jlink.internal.plugins.StripNativeDebugSymbolsPlugin;
|
@ -223,6 +223,8 @@ public final class TaskHelper {
|
||||
|
||||
private final class PluginsHelper {
|
||||
|
||||
// Duplicated here so as to avoid a direct dependency on platform specific plugin
|
||||
private static final String STRIP_NATIVE_DEBUG_SYMBOLS_NAME = "strip-native-debug-symbols";
|
||||
private ModuleLayer pluginsLayer = ModuleLayer.boot();
|
||||
private final List<Plugin> plugins;
|
||||
private String lastSorter;
|
||||
@ -424,6 +426,7 @@ public final class TaskHelper {
|
||||
}
|
||||
|
||||
List<Plugin> pluginsList = new ArrayList<>();
|
||||
Set<String> seenPlugins = new HashSet<>();
|
||||
for (Entry<Plugin, List<Map<String, String>>> entry : pluginToMaps.entrySet()) {
|
||||
Plugin plugin = entry.getKey();
|
||||
List<Map<String, String>> argsMaps = entry.getValue();
|
||||
@ -444,7 +447,17 @@ public final class TaskHelper {
|
||||
}
|
||||
|
||||
if (!Utils.isDisabled(plugin)) {
|
||||
// make sure that --strip-debug and --strip-native-debug-symbols
|
||||
// aren't being used at the same time. --strip-debug invokes --strip-native-debug-symbols on
|
||||
// platforms that support it, so it makes little sense to allow both at the same time.
|
||||
if ((plugin instanceof DefaultStripDebugPlugin && seenPlugins.contains(STRIP_NATIVE_DEBUG_SYMBOLS_NAME)) ||
|
||||
(STRIP_NATIVE_DEBUG_SYMBOLS_NAME.equals(plugin.getName()) && seenPlugins.contains(DefaultStripDebugPlugin.NAME))) {
|
||||
throw new BadArgs("err.plugin.conflicts", "--" + DefaultStripDebugPlugin.NAME,
|
||||
"-G",
|
||||
"--" + STRIP_NATIVE_DEBUG_SYMBOLS_NAME);
|
||||
}
|
||||
pluginsList.add(plugin);
|
||||
seenPlugins.add(plugin.getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,11 @@
|
||||
|
||||
package jdk.tools.jlink.internal.plugins;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jdk.tools.jlink.internal.PluginRepository;
|
||||
import jdk.tools.jlink.internal.ResourcePoolManager;
|
||||
import jdk.tools.jlink.internal.ResourcePoolManager.ResourcePoolImpl;
|
||||
import jdk.tools.jlink.plugin.Plugin;
|
||||
import jdk.tools.jlink.plugin.ResourcePool;
|
||||
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
|
||||
@ -37,8 +42,22 @@ import jdk.tools.jlink.plugin.ResourcePoolBuilder;
|
||||
public final class DefaultStripDebugPlugin implements Plugin {
|
||||
|
||||
public static final String NAME = "strip-debug";
|
||||
private static final String STRIP_NATIVE_DEBUG_PLUGIN = "strip-native-debug-symbols";
|
||||
private static final String EXCLUDE_DEBUGINFO = "exclude-debuginfo-files";
|
||||
|
||||
private final Plugin javaStripPlugin = new StripJavaDebugAttributesPlugin();
|
||||
private final Plugin javaStripPlugin;
|
||||
private final NativePluginFactory stripNativePluginFactory;
|
||||
|
||||
public DefaultStripDebugPlugin() {
|
||||
this(new StripJavaDebugAttributesPlugin(),
|
||||
new DefaultNativePluginFactory());
|
||||
}
|
||||
|
||||
public DefaultStripDebugPlugin(Plugin javaStripPlugin,
|
||||
NativePluginFactory nativeStripPluginFact) {
|
||||
this.javaStripPlugin = javaStripPlugin;
|
||||
this.stripNativePluginFactory = nativeStripPluginFact;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -52,7 +71,34 @@ public final class DefaultStripDebugPlugin implements Plugin {
|
||||
|
||||
@Override
|
||||
public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
|
||||
return javaStripPlugin.transform(in, out);
|
||||
Plugin stripNativePlugin = stripNativePluginFactory.create();
|
||||
if (stripNativePlugin != null) {
|
||||
Map<String, String> stripNativeConfig = Map.of(
|
||||
STRIP_NATIVE_DEBUG_PLUGIN, EXCLUDE_DEBUGINFO);
|
||||
stripNativePlugin.configure(stripNativeConfig);
|
||||
ResourcePoolManager outRes =
|
||||
new ResourcePoolManager(in.byteOrder(),
|
||||
((ResourcePoolImpl)in).getStringTable());
|
||||
ResourcePool strippedJava = javaStripPlugin.transform(in,
|
||||
outRes.resourcePoolBuilder());
|
||||
return stripNativePlugin.transform(strippedJava, out);
|
||||
} else {
|
||||
return javaStripPlugin.transform(in, out);
|
||||
}
|
||||
}
|
||||
|
||||
public interface NativePluginFactory {
|
||||
Plugin create();
|
||||
}
|
||||
|
||||
private static class DefaultNativePluginFactory implements NativePluginFactory {
|
||||
|
||||
@Override
|
||||
public Plugin create() {
|
||||
return PluginRepository.getPlugin(STRIP_NATIVE_DEBUG_PLUGIN,
|
||||
ModuleLayer.boot());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -62,7 +62,11 @@ public final class PluginsResourceBundle {
|
||||
}
|
||||
|
||||
public static String getMessage(String key, Object... args) throws MissingResourceException {
|
||||
String val = pluginsBundle.getString(key);
|
||||
return getMessage(pluginsBundle, key, args);
|
||||
}
|
||||
|
||||
public static String getMessage(ResourceBundle bundle, String key, Object... args) throws MissingResourceException {
|
||||
String val = bundle.getString(key);
|
||||
return MessageFormat.format(val, args);
|
||||
}
|
||||
}
|
||||
|
@ -209,6 +209,7 @@ err.no.such.plugin=No such plugin: {0}
|
||||
err.provider.not.functional=The provider {0} is not functional.
|
||||
|
||||
err.plugin.mutiple.options=More than one plugin enabled by {0} option
|
||||
err.plugin.conflicts={0} ({1}) conflicts with {2}. Please use one or the other, but not both.
|
||||
err.provider.additional.arg.error=Error in additional argument specification in {0} option: {1}
|
||||
|
||||
err.no.plugins.path=No plugins path argument.
|
||||
|
@ -69,7 +69,7 @@ import tests.JImageGenerator;
|
||||
* jdk.jlink/jdk.tools.jimage
|
||||
* jdk.compiler
|
||||
* @build tests.*
|
||||
* @run main IntegrationTest
|
||||
* @run main/othervm -Xmx1g IntegrationTest
|
||||
*/
|
||||
public class IntegrationTest {
|
||||
|
||||
|
150
test/jdk/tools/jlink/plugins/DefaultStripDebugPluginTest.java
Normal file
150
test/jdk/tools/jlink/plugins/DefaultStripDebugPluginTest.java
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Red Hat, Inc.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jdk.tools.jlink.internal.ResourcePoolManager;
|
||||
import jdk.tools.jlink.internal.plugins.DefaultStripDebugPlugin;
|
||||
import jdk.tools.jlink.internal.plugins.DefaultStripDebugPlugin.NativePluginFactory;
|
||||
import jdk.tools.jlink.plugin.Plugin;
|
||||
import jdk.tools.jlink.plugin.ResourcePool;
|
||||
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
|
||||
import jdk.tools.jlink.plugin.ResourcePoolEntry;
|
||||
import jdk.tools.jlink.plugin.ResourcePoolEntry.Type;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test for combination of java debug attributes stripping and
|
||||
* native debug symbols stripping.
|
||||
* @modules jdk.jlink/jdk.tools.jlink.internal
|
||||
* jdk.jlink/jdk.tools.jlink.internal.plugins
|
||||
* jdk.jlink/jdk.tools.jlink.plugin
|
||||
* @run main/othervm DefaultStripDebugPluginTest
|
||||
*/
|
||||
public class DefaultStripDebugPluginTest {
|
||||
|
||||
public void testWithNativeStripPresent() {
|
||||
MockStripPlugin javaPlugin = new MockStripPlugin(false);
|
||||
MockStripPlugin nativePlugin = new MockStripPlugin(true);
|
||||
TestNativeStripPluginFactory nativeFactory =
|
||||
new TestNativeStripPluginFactory(nativePlugin);
|
||||
DefaultStripDebugPlugin plugin = new DefaultStripDebugPlugin(javaPlugin,
|
||||
nativeFactory);
|
||||
ResourcePoolManager inManager = new ResourcePoolManager();
|
||||
ResourcePool pool = plugin.transform(inManager.resourcePool(),
|
||||
inManager.resourcePoolBuilder());
|
||||
if (!pool.findEntry(MockStripPlugin.JAVA_PATH).isPresent() ||
|
||||
!pool.findEntry(MockStripPlugin.NATIVE_PATH).isPresent()) {
|
||||
throw new AssertionError("Expected both native and java to get called");
|
||||
}
|
||||
}
|
||||
|
||||
public void testNoNativeStripPluginPresent() {
|
||||
MockStripPlugin javaPlugin = new MockStripPlugin(false);
|
||||
TestNativeStripPluginFactory nativeFactory =
|
||||
new TestNativeStripPluginFactory(null);
|
||||
DefaultStripDebugPlugin plugin = new DefaultStripDebugPlugin(javaPlugin,
|
||||
nativeFactory);
|
||||
ResourcePoolManager inManager = new ResourcePoolManager();
|
||||
ResourcePool pool = plugin.transform(inManager.resourcePool(),
|
||||
inManager.resourcePoolBuilder());
|
||||
if (!pool.findEntry(MockStripPlugin.JAVA_PATH).isPresent()) {
|
||||
throw new AssertionError("Expected java strip plugin to get called");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
DefaultStripDebugPluginTest test = new DefaultStripDebugPluginTest();
|
||||
test.testNoNativeStripPluginPresent();
|
||||
test.testWithNativeStripPresent();
|
||||
}
|
||||
|
||||
public static class MockStripPlugin implements Plugin {
|
||||
|
||||
private static final String NATIVE_PATH = "/foo/lib/test.so.debug";
|
||||
private static final String JAVA_PATH = "/foo/TestClass.class";
|
||||
private static final String STRIP_NATIVE_NAME = "strip-native-debug-symbols";
|
||||
private static final String OMIT_ARG = "exclude-debuginfo-files";
|
||||
private final boolean isNative;
|
||||
|
||||
MockStripPlugin(boolean isNative) {
|
||||
this.isNative = isNative;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(Map<String, String> config) {
|
||||
if (isNative) {
|
||||
if (config.get(STRIP_NATIVE_NAME) == null ||
|
||||
!config.get(STRIP_NATIVE_NAME).equals(OMIT_ARG)) {
|
||||
throw new AssertionError("Test failed!, Expected native " +
|
||||
"plugin to be properly configured.");
|
||||
} else {
|
||||
System.out.println("DEBUG: native plugin properly configured with: " +
|
||||
STRIP_NATIVE_NAME + "=" + config.get(STRIP_NATIVE_NAME));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourcePool transform(ResourcePool in,
|
||||
ResourcePoolBuilder out) {
|
||||
in.transformAndCopy((r) -> {return r; }, out); // identity
|
||||
String resPath = JAVA_PATH;
|
||||
ResourcePoolEntry.Type type = Type.CLASS_OR_RESOURCE;
|
||||
if (isNative) {
|
||||
resPath = NATIVE_PATH;
|
||||
type = Type.NATIVE_LIB;
|
||||
}
|
||||
ResourcePoolEntry entry = createMockEntry(resPath, type);
|
||||
out.add(entry);
|
||||
return out.build();
|
||||
}
|
||||
|
||||
private ResourcePoolEntry createMockEntry(String path,
|
||||
ResourcePoolEntry.Type type) {
|
||||
byte[] mockContent = new byte[] { 0, 1, 2, 3 };
|
||||
ResourcePoolEntry entry = ResourcePoolEntry.create(path,
|
||||
type,
|
||||
mockContent);
|
||||
return entry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TestNativeStripPluginFactory implements NativePluginFactory {
|
||||
|
||||
private final MockStripPlugin plugin;
|
||||
|
||||
TestNativeStripPluginFactory(MockStripPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin create() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Red Hat, Inc.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Fake objcopy used by StripNativeDebugSymbolsTest. It prints the
|
||||
* passed in arguments to a log and creates a fake debug info file
|
||||
* for --only-keep-debug invocation. Note that the first argument is
|
||||
* the path to the log file. This argument will be omitted when
|
||||
* logged.
|
||||
*
|
||||
* Callers need to ensure the log file is properly truncated.
|
||||
*
|
||||
*/
|
||||
public class FakeObjCopy {
|
||||
|
||||
private static final String OBJCOPY_ONLY_KEEP_DEBUG_OPT = "--only-keep-debug";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length < 1) {
|
||||
throw new AssertionError("At least one argument expected");
|
||||
}
|
||||
String[] objCopyArgs = new String[args.length - 1];
|
||||
System.arraycopy(args, 1, objCopyArgs, 0, objCopyArgs.length);
|
||||
String logFile = args[0];
|
||||
System.out.println("DEBUG: Fake objcopy called. Log file is: " + logFile);
|
||||
// Log options
|
||||
String line = Arrays.asList(objCopyArgs).stream().collect(Collectors.joining(" "));
|
||||
Files.write(Paths.get(logFile),
|
||||
List.<String>of(line),
|
||||
StandardOpenOption.APPEND,
|
||||
StandardOpenOption.CREATE);
|
||||
// Handle --only-keep-debug option as plugin attempts to read
|
||||
// debug info file after this utility being called.
|
||||
if (objCopyArgs.length == 3 && OBJCOPY_ONLY_KEEP_DEBUG_OPT.equals(objCopyArgs[0])) {
|
||||
handleOnlyKeepDebug(objCopyArgs[2]);
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleOnlyKeepDebug(String dbgFile) throws Exception {
|
||||
try (PrintWriter pw = new PrintWriter(new File(dbgFile))) {
|
||||
pw.println("Fake objcopy debug info file");
|
||||
}
|
||||
System.out.println("DEBUG: wrote fake debug file " + dbgFile);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,807 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Red Hat, Inc.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.spi.ToolProvider;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.test.lib.compiler.CompilerUtils;
|
||||
import jdk.tools.jlink.internal.ResourcePoolManager;
|
||||
import jdk.tools.jlink.internal.plugins.StripNativeDebugSymbolsPlugin;
|
||||
import jdk.tools.jlink.internal.plugins.StripNativeDebugSymbolsPlugin.ObjCopyCmdBuilder;
|
||||
import jdk.tools.jlink.plugin.ResourcePool;
|
||||
import jdk.tools.jlink.plugin.ResourcePoolEntry;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires os.family == "linux"
|
||||
* @bug 8214796
|
||||
* @summary Test --strip-native-debug-symbols plugin
|
||||
* @library /test/lib
|
||||
* @modules jdk.compiler
|
||||
* jdk.jlink/jdk.tools.jlink.internal.plugins
|
||||
* jdk.jlink/jdk.tools.jlink.internal
|
||||
* jdk.jlink/jdk.tools.jlink.plugin
|
||||
* @build jdk.test.lib.compiler.CompilerUtils FakeObjCopy
|
||||
* @run main/othervm -Xmx1g StripNativeDebugSymbolsPluginTest
|
||||
*/
|
||||
public class StripNativeDebugSymbolsPluginTest {
|
||||
|
||||
private static final String OBJCOPY = "objcopy";
|
||||
private static final String DEFAULT_OBJCOPY_CMD = OBJCOPY;
|
||||
private static final String PLUGIN_NAME = "strip-native-debug-symbols";
|
||||
private static final String MODULE_NAME_WITH_NATIVE = "fib";
|
||||
private static final String JAVA_HOME = System.getProperty("java.home");
|
||||
private static final String NATIVE_LIB_NAME = "libFib.so";
|
||||
private static final Path JAVA_LIB_PATH = Paths.get(System.getProperty("java.library.path"));
|
||||
private static final Path LIB_FIB_SRC = JAVA_LIB_PATH.resolve(NATIVE_LIB_NAME);
|
||||
private static final String FIBJNI_CLASS_NAME = "FibJNI.java";
|
||||
private static final Path JAVA_SRC_DIR = Paths.get(System.getProperty("test.src"))
|
||||
.resolve("src")
|
||||
.resolve(MODULE_NAME_WITH_NATIVE);
|
||||
private static final Path FIBJNI_JAVA_CLASS = JAVA_SRC_DIR.resolve(FIBJNI_CLASS_NAME);
|
||||
private static final String DEBUG_EXTENSION = "debug";
|
||||
private static final long ORIG_LIB_FIB_SIZE = LIB_FIB_SRC.toFile().length();
|
||||
private static final String FAKE_OBJ_COPY_LOG_FILE = "objcopy.log";
|
||||
private static final String OBJCOPY_ONLY_DEBUG_SYMS_OPT = "-g";
|
||||
private static final String OBJCOPY_ONLY_KEEP_DEBUG_SYMS_OPT = "--only-keep-debug";
|
||||
private static final String OBJCOPY_ADD_DEBUG_LINK_OPT = "--add-gnu-debuglink";
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Tests which do NOT rely on objcopy being present on the test system
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void testPluginLoaded() {
|
||||
List<String> output =
|
||||
JLink.run("--list-plugins").output();
|
||||
if (output.stream().anyMatch(s -> s.contains(PLUGIN_NAME))) {
|
||||
System.out.println("DEBUG: " + PLUGIN_NAME + " plugin loaded as expected.");
|
||||
} else {
|
||||
throw new AssertionError("strip-native-debug-symbols plugin not in " +
|
||||
"--list-plugins output.");
|
||||
}
|
||||
}
|
||||
|
||||
public void testConfigureFakeObjCopy() throws Exception {
|
||||
configureConflictingOptions();
|
||||
configureObjcopyWithOmit();
|
||||
configureObjcopyWithKeep();
|
||||
configureUnknownOptions();
|
||||
configureMultipleTimesSamePlugin();
|
||||
System.out.println("Test testConfigureFakeObjCopy() PASSED!");
|
||||
}
|
||||
|
||||
private void configureMultipleTimesSamePlugin() throws Exception {
|
||||
Map<String, String> keepDebug = Map.of(
|
||||
StripNativeDebugSymbolsPlugin.NAME, "keep-debuginfo-files"
|
||||
);
|
||||
Map<String, String> excludeDebug = Map.of(
|
||||
StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files"
|
||||
);
|
||||
StripNativeDebugSymbolsPlugin plugin = createAndConfigPlugin(keepDebug);
|
||||
try {
|
||||
plugin.doConfigure(false, excludeDebug);
|
||||
throw new AssertionError("should have thrown IAE for broken config: " +
|
||||
keepDebug + " and " + excludeDebug);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// pass
|
||||
System.out.println("DEBUG: test threw IAE " + e.getMessage() +
|
||||
" as expected.");
|
||||
}
|
||||
}
|
||||
|
||||
private void configureUnknownOptions() throws Exception {
|
||||
Map<String, String> config = Map.of(
|
||||
StripNativeDebugSymbolsPlugin.NAME, "foobar"
|
||||
);
|
||||
doConfigureUnknownOption(config);
|
||||
config = Map.of(
|
||||
StripNativeDebugSymbolsPlugin.NAME, "keep-debuginfo-files",
|
||||
"foo", "bar" // unknown value
|
||||
);
|
||||
doConfigureUnknownOption(config);
|
||||
}
|
||||
|
||||
private void doConfigureUnknownOption(Map<String, String> config) throws Exception {
|
||||
try {
|
||||
createAndConfigPlugin(config);
|
||||
throw new AssertionError("should have thrown IAE for broken config: " + config);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// pass
|
||||
System.out.println("DEBUG: test threw IAE " + e.getMessage() +
|
||||
" as expected.");
|
||||
}
|
||||
}
|
||||
|
||||
private void configureObjcopyWithKeep() throws Exception {
|
||||
String objcopyPath = "foobar";
|
||||
String debugExt = "debuginfo"; // that's the default value
|
||||
Map<String, String> config = Map.of(
|
||||
StripNativeDebugSymbolsPlugin.NAME, "keep-debuginfo-files",
|
||||
"objcopy", objcopyPath
|
||||
);
|
||||
doKeepDebugInfoFakeObjCopyTest(config, debugExt, objcopyPath);
|
||||
// Do it again combining options the other way round
|
||||
debugExt = "testme";
|
||||
config = Map.of(
|
||||
StripNativeDebugSymbolsPlugin.NAME, "objcopy=" + objcopyPath,
|
||||
"keep-debuginfo-files", debugExt
|
||||
);
|
||||
doKeepDebugInfoFakeObjCopyTest(config, debugExt, objcopyPath);
|
||||
System.out.println("DEBUG: configureObjcopyWithKeep() PASSED!");
|
||||
}
|
||||
|
||||
private void configureObjcopyWithOmit() throws Exception {
|
||||
String objcopyPath = "something-non-standard";
|
||||
Map<String, String> config = Map.of(
|
||||
StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files",
|
||||
"objcopy", objcopyPath
|
||||
);
|
||||
doOmitDebugInfoFakeObjCopyTest(config, objcopyPath);
|
||||
System.out.println("DEBUG: configureObjcopyWithOmit() PASSED!");
|
||||
}
|
||||
|
||||
private void configureConflictingOptions() throws Exception {
|
||||
Map<String, String> config = Map.of(
|
||||
StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files",
|
||||
"keep-debuginfo-files", "foo-ext"
|
||||
);
|
||||
doConfigureConflictingOptions(config);
|
||||
config = Map.of(
|
||||
StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files=bar",
|
||||
"keep-debuginfo-files", "foo-ext"
|
||||
);
|
||||
doConfigureConflictingOptions(config);
|
||||
}
|
||||
|
||||
private void doConfigureConflictingOptions(Map<String, String> config) throws Exception {
|
||||
try {
|
||||
createAndConfigPlugin(config);
|
||||
throw new AssertionError("keep-debuginfo-files and exclude-debuginfo-files " +
|
||||
" should have conflicted!");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// pass
|
||||
if (e.getMessage().contains("keep-debuginfo-files") &&
|
||||
e.getMessage().contains("exclude-debuginfo-files")) {
|
||||
System.out.println("DEBUG: test threw IAE " + e.getMessage() +
|
||||
" as expected.");
|
||||
} else {
|
||||
throw new AssertionError("Unexpected IAE", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testTransformFakeObjCopyNoDebugInfoFiles() throws Exception {
|
||||
Map<String, String> defaultConfig = Map.of(
|
||||
StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files"
|
||||
);
|
||||
doOmitDebugInfoFakeObjCopyTest(defaultConfig, DEFAULT_OBJCOPY_CMD);
|
||||
System.out.println("testTransformFakeObjCopyNoDebugInfoFiles() PASSED!");
|
||||
}
|
||||
|
||||
private void doOmitDebugInfoFakeObjCopyTest(Map<String, String> config,
|
||||
String expectedObjCopy) throws Exception {
|
||||
StripNativeDebugSymbolsPlugin plugin = createAndConfigPlugin(config, expectedObjCopy);
|
||||
String binFile = "mybin";
|
||||
String path = "/fib/bin/" + binFile;
|
||||
ResourcePoolEntry debugEntry = createMockEntry(path,
|
||||
ResourcePoolEntry.Type.NATIVE_CMD);
|
||||
ResourcePoolManager inResources = new ResourcePoolManager();
|
||||
ResourcePoolManager outResources = new ResourcePoolManager();
|
||||
inResources.add(debugEntry);
|
||||
ResourcePool output = plugin.transform(
|
||||
inResources.resourcePool(),
|
||||
outResources.resourcePoolBuilder());
|
||||
// expect entry to be present
|
||||
if (output.findEntry(path).isPresent()) {
|
||||
System.out.println("DEBUG: File " + path + " present as exptected.");
|
||||
} else {
|
||||
throw new AssertionError("Test failed. Binary " + path +
|
||||
" not present after stripping!");
|
||||
}
|
||||
verifyFakeObjCopyCalled(binFile);
|
||||
}
|
||||
|
||||
public void testTransformFakeObjCopyKeepDebugInfoFiles() throws Exception {
|
||||
Map<String, String> defaultConfig = Map.of(
|
||||
StripNativeDebugSymbolsPlugin.NAME,
|
||||
"keep-debuginfo-files=" + DEBUG_EXTENSION
|
||||
);
|
||||
doKeepDebugInfoFakeObjCopyTest(defaultConfig,
|
||||
DEBUG_EXTENSION,
|
||||
DEFAULT_OBJCOPY_CMD);
|
||||
System.out.println("testTransformFakeObjCopyKeepDebugInfoFiles() PASSED!");
|
||||
}
|
||||
|
||||
private void doKeepDebugInfoFakeObjCopyTest(Map<String, String> config,
|
||||
String debugExt,
|
||||
String expectedObjCopy) throws Exception {
|
||||
StripNativeDebugSymbolsPlugin plugin = createAndConfigPlugin(config, expectedObjCopy);
|
||||
String sharedLib = "myLib.so";
|
||||
String path = "/fib/lib/" + sharedLib;
|
||||
ResourcePoolEntry debugEntry = createMockEntry(path,
|
||||
ResourcePoolEntry.Type.NATIVE_LIB);
|
||||
ResourcePoolManager inResources = new ResourcePoolManager();
|
||||
ResourcePoolManager outResources = new ResourcePoolManager();
|
||||
inResources.add(debugEntry);
|
||||
ResourcePool output = plugin.transform(
|
||||
inResources.resourcePool(),
|
||||
outResources.resourcePoolBuilder());
|
||||
// expect entry + debug info entry to be present
|
||||
String debugPath = path + "." + debugExt;
|
||||
if (output.findEntry(path).isPresent() &&
|
||||
output.findEntry(debugPath).isPresent()) {
|
||||
System.out.println("DEBUG: Files " + path + "{,." + debugExt +
|
||||
"} present as exptected.");
|
||||
} else {
|
||||
throw new AssertionError("Test failed. Binary files " + path +
|
||||
"{,." + debugExt +"} not present after " +
|
||||
"stripping!");
|
||||
}
|
||||
verifyFakeObjCopyCalledMultiple(sharedLib, debugExt);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Tests which DO rely on objcopy being present on the test system.
|
||||
// Skipped otherwise.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void testStripNativeLibraryDefaults() throws Exception {
|
||||
if (!hasJmods()) return;
|
||||
|
||||
Path libFibJmod = createLibFibJmod();
|
||||
|
||||
Path imageDir = Paths.get("stripped-native-libs");
|
||||
JLink.run("--output", imageDir.toString(),
|
||||
"--verbose",
|
||||
"--module-path", modulePathWith(libFibJmod),
|
||||
"--add-modules", MODULE_NAME_WITH_NATIVE,
|
||||
"--strip-native-debug-symbols=exclude-debuginfo-files").output();
|
||||
Path libDir = imageDir.resolve("lib");
|
||||
Path postStripLib = libDir.resolve(NATIVE_LIB_NAME);
|
||||
long postStripSize = postStripLib.toFile().length();
|
||||
|
||||
if (postStripSize == 0) {
|
||||
throw new AssertionError("Lib file size 0. Test error?!");
|
||||
}
|
||||
// Heuristic: libLib.so is smaller post debug info stripping
|
||||
if (postStripSize >= ORIG_LIB_FIB_SIZE) {
|
||||
throw new AssertionError("Expected native library stripping to " +
|
||||
"reduce file size. Expected < " +
|
||||
ORIG_LIB_FIB_SIZE + ", got: " + postStripSize);
|
||||
} else {
|
||||
System.out.println("DEBUG: File size of " + postStripLib.toString() +
|
||||
" " + postStripSize + " < " + ORIG_LIB_FIB_SIZE + " as expected." );
|
||||
}
|
||||
verifyFibModule(imageDir); // Sanity check fib module which got libFib.so stripped
|
||||
System.out.println("DEBUG: testStripNativeLibraryDefaults() PASSED!");
|
||||
}
|
||||
|
||||
public void testOptionsInvalidObjcopy() throws Exception {
|
||||
if (!hasJmods()) return;
|
||||
|
||||
Path libFibJmod = createLibFibJmod();
|
||||
|
||||
String notExists = "/do/not/exist/objcopy";
|
||||
|
||||
Path imageDir = Paths.get("invalid-objcopy-command");
|
||||
String[] jlinkCmdArray = new String[] {
|
||||
JAVA_HOME + File.separator + "bin" + File.separator + "jlink",
|
||||
"--output", imageDir.toString(),
|
||||
"--verbose",
|
||||
"--module-path", modulePathWith(libFibJmod),
|
||||
"--add-modules", MODULE_NAME_WITH_NATIVE,
|
||||
"--strip-native-debug-symbols", "objcopy=" + notExists,
|
||||
};
|
||||
List<String> jlinkCmd = Arrays.asList(jlinkCmdArray);
|
||||
System.out.println("Debug: command: " + jlinkCmd.stream().collect(
|
||||
Collectors.joining(" ")));
|
||||
ProcessBuilder builder = new ProcessBuilder(jlinkCmd);
|
||||
Process p = builder.start();
|
||||
int status = p.waitFor();
|
||||
if (status == 0) {
|
||||
throw new AssertionError("Expected jlink to fail!");
|
||||
} else {
|
||||
verifyInvalidObjcopyError(p.getInputStream(), notExists);
|
||||
System.out.println("DEBUG: testOptionsInvalidObjcopy() PASSED!");
|
||||
}
|
||||
}
|
||||
|
||||
public void testStripNativeLibsDebugSymsIncluded() throws Exception {
|
||||
if (!hasJmods()) return;
|
||||
|
||||
Path libFibJmod = createLibFibJmod();
|
||||
|
||||
Path imageDir = Paths.get("stripped-native-libs-with-debug");
|
||||
JLink.run("--output", imageDir.toString(),
|
||||
"--verbose",
|
||||
"--module-path", modulePathWith(libFibJmod),
|
||||
"--add-modules", MODULE_NAME_WITH_NATIVE,
|
||||
"--strip-native-debug-symbols",
|
||||
"keep-debuginfo-files=" + DEBUG_EXTENSION);
|
||||
|
||||
Path libDir = imageDir.resolve("lib");
|
||||
Path postStripLib = libDir.resolve(NATIVE_LIB_NAME);
|
||||
long postStripSize = postStripLib.toFile().length();
|
||||
|
||||
if (postStripSize == 0) {
|
||||
throw new AssertionError("Lib file size 0. Test error?!");
|
||||
}
|
||||
// Heuristic: libLib.so is smaller post debug info stripping
|
||||
if (postStripSize >= ORIG_LIB_FIB_SIZE) {
|
||||
throw new AssertionError("Expected native library stripping to " +
|
||||
"reduce file size. Expected < " +
|
||||
ORIG_LIB_FIB_SIZE + ", got: " + postStripSize);
|
||||
} else {
|
||||
System.out.println("DEBUG: File size of " + postStripLib.toString() +
|
||||
" " + postStripSize + " < " + ORIG_LIB_FIB_SIZE + " as expected." );
|
||||
}
|
||||
// stripped with option to preserve debug symbols file
|
||||
verifyDebugInfoSymbolFilePresent(imageDir);
|
||||
System.out.println("DEBUG: testStripNativeLibsDebugSymsIncluded() PASSED!");
|
||||
}
|
||||
|
||||
private void verifyFakeObjCopyCalledMultiple(String expectedFile,
|
||||
String dbgExt) throws Exception {
|
||||
// transform of the StripNativeDebugSymbolsPlugin created objcopy.log
|
||||
// with our stubbed FakeObjCopy. See FakeObjCopy.java
|
||||
List<String> allLines = Files.readAllLines(Paths.get(FAKE_OBJ_COPY_LOG_FILE));
|
||||
if (allLines.size() != 3) {
|
||||
throw new AssertionError("Expected 3 calls to objcopy");
|
||||
}
|
||||
// 3 calls to objcopy are as follows:
|
||||
// 1. Only keep debug symbols
|
||||
// 2. Strip debug symbols
|
||||
// 3. Add debug link to stripped file
|
||||
String onlyKeepDebug = allLines.get(0);
|
||||
String stripSymbolsLine = allLines.get(1);
|
||||
String addGnuDebugLink = allLines.get(2);
|
||||
System.out.println("DEBUG: Inspecting fake objcopy calls: " + allLines);
|
||||
boolean passed = stripSymbolsLine.startsWith(OBJCOPY_ONLY_DEBUG_SYMS_OPT);
|
||||
passed &= stripSymbolsLine.endsWith(expectedFile);
|
||||
String[] tokens = onlyKeepDebug.split("\\s");
|
||||
passed &= tokens[0].equals(OBJCOPY_ONLY_KEEP_DEBUG_SYMS_OPT);
|
||||
passed &= tokens[1].endsWith(expectedFile);
|
||||
passed &= tokens[2].endsWith(expectedFile + "." + dbgExt);
|
||||
tokens = addGnuDebugLink.split("\\s");
|
||||
String[] addDbgTokens = tokens[0].split("=");
|
||||
passed &= addDbgTokens[1].equals(expectedFile + "." + dbgExt);
|
||||
passed &= addDbgTokens[0].equals(OBJCOPY_ADD_DEBUG_LINK_OPT);
|
||||
passed &= tokens[1].endsWith(expectedFile);
|
||||
if (!passed) {
|
||||
throw new AssertionError("Test failed! objcopy not properly called " +
|
||||
"with expected options!");
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyFakeObjCopyCalled(String expectedFile) throws Exception {
|
||||
// transform of the StripNativeDebugSymbolsPlugin created objcopy.log
|
||||
// with our stubbed FakeObjCopy. See FakeObjCopy.java
|
||||
List<String> allLines = Files.readAllLines(Paths.get(FAKE_OBJ_COPY_LOG_FILE));
|
||||
if (allLines.size() != 1) {
|
||||
throw new AssertionError("Expected 1 call to objcopy only");
|
||||
}
|
||||
String optionLine = allLines.get(0);
|
||||
System.out.println("DEBUG: Inspecting fake objcopy arguments: " + optionLine);
|
||||
boolean passed = optionLine.startsWith(OBJCOPY_ONLY_DEBUG_SYMS_OPT);
|
||||
passed &= optionLine.endsWith(expectedFile);
|
||||
if (!passed) {
|
||||
throw new AssertionError("Test failed! objcopy not called with " +
|
||||
"expected options!");
|
||||
}
|
||||
}
|
||||
|
||||
private ResourcePoolEntry createMockEntry(String path,
|
||||
ResourcePoolEntry.Type type) {
|
||||
byte[] mockContent = new byte[] { 0, 1, 2, 3 };
|
||||
ResourcePoolEntry entry = ResourcePoolEntry.create(
|
||||
path,
|
||||
type,
|
||||
mockContent);
|
||||
return entry;
|
||||
}
|
||||
|
||||
private StripNativeDebugSymbolsPlugin createAndConfigPlugin(
|
||||
Map<String, String> config,
|
||||
String expectedObjcopy)
|
||||
throws IOException {
|
||||
TestObjCopyCmdBuilder cmdBuilder = new TestObjCopyCmdBuilder(expectedObjcopy);
|
||||
return createAndConfigPlugin(config, cmdBuilder);
|
||||
}
|
||||
|
||||
private StripNativeDebugSymbolsPlugin createAndConfigPlugin(
|
||||
Map<String, String> config) throws IOException {
|
||||
TestObjCopyCmdBuilder cmdBuilder = new TestObjCopyCmdBuilder();
|
||||
return createAndConfigPlugin(config, cmdBuilder);
|
||||
}
|
||||
|
||||
private StripNativeDebugSymbolsPlugin createAndConfigPlugin(
|
||||
Map<String, String> config,
|
||||
TestObjCopyCmdBuilder builder) throws IOException {
|
||||
StripNativeDebugSymbolsPlugin plugin =
|
||||
new StripNativeDebugSymbolsPlugin(builder);
|
||||
plugin.doConfigure(false, config);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
// Create the jmod with the native library
|
||||
private Path createLibFibJmod() throws IOException {
|
||||
JmodFileBuilder jmodBuilder = new JmodFileBuilder(MODULE_NAME_WITH_NATIVE);
|
||||
jmodBuilder.javaClass(FIBJNI_JAVA_CLASS);
|
||||
jmodBuilder.nativeLib(LIB_FIB_SRC);
|
||||
return jmodBuilder.build();
|
||||
}
|
||||
|
||||
private String modulePathWith(Path jmod) {
|
||||
return Paths.get(JAVA_HOME, "jmods").toString() +
|
||||
File.pathSeparator + jmod.getParent().toString();
|
||||
}
|
||||
|
||||
private boolean hasJmods() {
|
||||
if (!Files.exists(Paths.get(JAVA_HOME, "jmods"))) {
|
||||
System.err.println("Test skipped. NO jmods directory");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void verifyInvalidObjcopyError(InputStream errInput, String match) {
|
||||
boolean foundMatch = false;
|
||||
try (Scanner scanner = new Scanner(errInput)) {
|
||||
while (scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine();
|
||||
System.out.println("DEBUG: >>>> " + line);
|
||||
if (line.contains(match)) {
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundMatch) {
|
||||
throw new AssertionError("Expected to find " + match +
|
||||
" in error stream.");
|
||||
} else {
|
||||
System.out.println("DEBUG: Found string " + match + " as expected.");
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyDebugInfoSymbolFilePresent(Path image)
|
||||
throws IOException, InterruptedException {
|
||||
Path debugSymsFile = image.resolve("lib/libFib.so.debug");
|
||||
if (!Files.exists(debugSymsFile)) {
|
||||
throw new AssertionError("Expected stripped debug info file " +
|
||||
debugSymsFile.toString() + " to exist.");
|
||||
}
|
||||
long debugSymsSize = debugSymsFile.toFile().length();
|
||||
if (debugSymsSize <= 0) {
|
||||
throw new AssertionError("sanity check for fib.FibJNI failed " +
|
||||
"post-stripping!");
|
||||
} else {
|
||||
System.out.println("DEBUG: Debug symbols stripped from libFib.so " +
|
||||
"present (" + debugSymsFile.toString() + ") as expected.");
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyFibModule(Path image)
|
||||
throws IOException, InterruptedException {
|
||||
System.out.println("DEBUG: sanity checking fib module...");
|
||||
Path launcher = image.resolve("bin/java");
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add(launcher.toString());
|
||||
args.add("--add-modules");
|
||||
args.add(MODULE_NAME_WITH_NATIVE);
|
||||
args.add("fib.FibJNI");
|
||||
args.add("7");
|
||||
args.add("13"); // fib(7) == 13
|
||||
System.out.println("DEBUG: [command] " +
|
||||
args.stream().collect(Collectors.joining(" ")));
|
||||
Process proc = new ProcessBuilder(args).inheritIO().start();
|
||||
int status = proc.waitFor();
|
||||
if (status == 0) {
|
||||
System.out.println("DEBUG: sanity checking fib module... PASSED!");
|
||||
} else {
|
||||
throw new AssertionError("sanity check for fib.FibJNI failed post-" +
|
||||
"stripping!");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isObjcopyPresent() throws Exception {
|
||||
String[] objcopyVersion = new String[] {
|
||||
OBJCOPY, "--version",
|
||||
};
|
||||
List<String> command = Arrays.asList(objcopyVersion);
|
||||
try {
|
||||
ProcessBuilder builder = new ProcessBuilder(command);
|
||||
builder.inheritIO();
|
||||
Process p = builder.start();
|
||||
int status = p.waitFor();
|
||||
if (status != 0) {
|
||||
System.out.println("Debug: objcopy binary doesn't seem to be " +
|
||||
"present or functional.");
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.out.println("Debug: objcopy binary doesn't seem to be present " +
|
||||
"or functional.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
StripNativeDebugSymbolsPluginTest test = new StripNativeDebugSymbolsPluginTest();
|
||||
if (isObjcopyPresent()) {
|
||||
test.testStripNativeLibraryDefaults();
|
||||
test.testStripNativeLibsDebugSymsIncluded();
|
||||
test.testOptionsInvalidObjcopy();
|
||||
} else {
|
||||
System.out.println("DEBUG: objcopy binary not available. " +
|
||||
"Running reduced set of tests.");
|
||||
}
|
||||
test.testTransformFakeObjCopyNoDebugInfoFiles();
|
||||
test.testTransformFakeObjCopyKeepDebugInfoFiles();
|
||||
test.testConfigureFakeObjCopy();
|
||||
test.testPluginLoaded();
|
||||
}
|
||||
|
||||
static class JLink {
|
||||
static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink")
|
||||
.orElseThrow(() ->
|
||||
new RuntimeException("jlink tool not found")
|
||||
);
|
||||
|
||||
static JLink run(String... options) {
|
||||
JLink jlink = new JLink();
|
||||
if (jlink.execute(options) != 0) {
|
||||
throw new AssertionError("Jlink expected to exit with 0 return code");
|
||||
}
|
||||
return jlink;
|
||||
}
|
||||
|
||||
final List<String> output = new ArrayList<>();
|
||||
private int execute(String... options) {
|
||||
System.out.println("jlink " +
|
||||
Stream.of(options).collect(Collectors.joining(" ")));
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(writer);
|
||||
int rc = JLINK_TOOL.run(pw, pw, options);
|
||||
System.out.println(writer.toString());
|
||||
Stream.of(writer.toString().split("\\v"))
|
||||
.map(String::trim)
|
||||
.forEach(output::add);
|
||||
return rc;
|
||||
}
|
||||
|
||||
boolean contains(String s) {
|
||||
return output.contains(s);
|
||||
}
|
||||
|
||||
List<String> output() {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder to create JMOD file
|
||||
*/
|
||||
private static class JmodFileBuilder {
|
||||
|
||||
private static final ToolProvider JMOD_TOOL = ToolProvider
|
||||
.findFirst("jmod")
|
||||
.orElseThrow(() ->
|
||||
new RuntimeException("jmod tool not found")
|
||||
);
|
||||
private static final Path SRC_DIR = Paths.get("src");
|
||||
private static final Path MODS_DIR = Paths.get("mod");
|
||||
private static final Path JMODS_DIR = Paths.get("jmods");
|
||||
private static final Path LIBS_DIR = Paths.get("libs");
|
||||
|
||||
private final String name;
|
||||
private final List<Path> nativeLibs = new ArrayList<>();
|
||||
private final List<Path> javaClasses = new ArrayList<>();
|
||||
|
||||
private JmodFileBuilder(String name) throws IOException {
|
||||
this.name = name;
|
||||
|
||||
deleteDirectory(MODS_DIR);
|
||||
deleteDirectory(SRC_DIR);
|
||||
deleteDirectory(LIBS_DIR);
|
||||
deleteDirectory(JMODS_DIR);
|
||||
Path msrc = SRC_DIR.resolve(name);
|
||||
if (Files.exists(msrc)) {
|
||||
deleteDirectory(msrc);
|
||||
}
|
||||
}
|
||||
|
||||
JmodFileBuilder nativeLib(Path libFileSrc) {
|
||||
nativeLibs.add(libFileSrc);
|
||||
return this;
|
||||
}
|
||||
|
||||
JmodFileBuilder javaClass(Path srcPath) {
|
||||
javaClasses.add(srcPath);
|
||||
return this;
|
||||
}
|
||||
|
||||
Path build() throws IOException {
|
||||
compileModule();
|
||||
return createJmodFile();
|
||||
}
|
||||
|
||||
private void compileModule() throws IOException {
|
||||
Path msrc = SRC_DIR.resolve(name);
|
||||
Files.createDirectories(msrc);
|
||||
// copy class using native lib to expected path
|
||||
if (javaClasses.size() > 0) {
|
||||
for (Path srcPath: javaClasses) {
|
||||
Path targetPath = msrc.resolve(srcPath.getFileName());
|
||||
Files.copy(srcPath, targetPath);
|
||||
}
|
||||
}
|
||||
// generate module-info file.
|
||||
Path minfo = msrc.resolve("module-info.java");
|
||||
try (BufferedWriter bw = Files.newBufferedWriter(minfo);
|
||||
PrintWriter writer = new PrintWriter(bw)) {
|
||||
writer.format("module %s { }%n", name);
|
||||
}
|
||||
|
||||
if (!CompilerUtils.compile(msrc, MODS_DIR,
|
||||
"--module-source-path",
|
||||
SRC_DIR.toString())) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Path createJmodFile() throws IOException {
|
||||
Path mclasses = MODS_DIR.resolve(name);
|
||||
Files.createDirectories(JMODS_DIR);
|
||||
Path outfile = JMODS_DIR.resolve(name + ".jmod");
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add("create");
|
||||
// add classes
|
||||
args.add("--class-path");
|
||||
args.add(mclasses.toString());
|
||||
// native libs
|
||||
if (nativeLibs.size() > 0) {
|
||||
// Copy the JNI library to the expected path
|
||||
Files.createDirectories(LIBS_DIR);
|
||||
for (Path srcLib: nativeLibs) {
|
||||
Path targetLib = LIBS_DIR.resolve(srcLib.getFileName());
|
||||
Files.copy(srcLib, targetLib);
|
||||
}
|
||||
args.add("--libs");
|
||||
args.add(LIBS_DIR.toString());
|
||||
}
|
||||
args.add(outfile.toString());
|
||||
|
||||
if (Files.exists(outfile)) {
|
||||
Files.delete(outfile);
|
||||
}
|
||||
|
||||
System.out.println("jmod " +
|
||||
args.stream().collect(Collectors.joining(" ")));
|
||||
|
||||
int rc = JMOD_TOOL.run(System.out, System.out,
|
||||
args.toArray(new String[args.size()]));
|
||||
if (rc != 0) {
|
||||
throw new AssertionError("jmod failed: rc = " + rc);
|
||||
}
|
||||
return outfile;
|
||||
}
|
||||
|
||||
private static void deleteDirectory(Path dir) throws IOException {
|
||||
try {
|
||||
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file,
|
||||
BasicFileAttributes attrs)
|
||||
throws IOException
|
||||
{
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir,
|
||||
IOException exc)
|
||||
throws IOException
|
||||
{
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
} catch (NoSuchFileException e) {
|
||||
// ignore non-existing files
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestObjCopyCmdBuilder implements ObjCopyCmdBuilder {
|
||||
|
||||
private final String expectedObjCopy;
|
||||
private final String logFile;
|
||||
|
||||
TestObjCopyCmdBuilder() {
|
||||
this(DEFAULT_OBJCOPY_CMD);
|
||||
}
|
||||
TestObjCopyCmdBuilder(String exptectedObjCopy) {
|
||||
Path logFilePath = Paths.get(FAKE_OBJ_COPY_LOG_FILE);
|
||||
try {
|
||||
Files.deleteIfExists(logFilePath);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.logFile = logFilePath.toFile().getAbsolutePath();
|
||||
this.expectedObjCopy = exptectedObjCopy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> build(String objCopy, String... options) {
|
||||
if (!expectedObjCopy.equals(objCopy)) {
|
||||
throw new AssertionError("Expected objcopy to be '" +
|
||||
expectedObjCopy + "' but was '" +
|
||||
objCopy);
|
||||
}
|
||||
List<String> fakeObjCopy = new ArrayList<>();
|
||||
fakeObjCopy.add(JAVA_HOME + File.separator + "bin" + File.separator + "java");
|
||||
fakeObjCopy.add("-cp");
|
||||
fakeObjCopy.add(System.getProperty("test.classes"));
|
||||
fakeObjCopy.add("FakeObjCopy");
|
||||
// Note that adding the gnu debug link changes the PWD of the
|
||||
// java process calling FakeObjCopy. As such we need to pass in the
|
||||
// log file path this way. Relative paths won't work as it would be
|
||||
// relative to the temporary directory which gets deleted post
|
||||
// adding the debug link
|
||||
fakeObjCopy.add(logFile);
|
||||
if (options.length > 0) {
|
||||
fakeObjCopy.addAll(Arrays.asList(options));
|
||||
}
|
||||
return fakeObjCopy;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Red Hat, Inc
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package fib;
|
||||
public class FibJNI {
|
||||
|
||||
static {
|
||||
// Native lib used for debug symbols stripping
|
||||
System.loadLibrary("Fib");
|
||||
}
|
||||
|
||||
private final int num;
|
||||
private final long expected;
|
||||
|
||||
public FibJNI(int num, long expected) {
|
||||
this.num = num;
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
public void callNative() {
|
||||
callJNI(this, num);
|
||||
}
|
||||
|
||||
// Called from JNI library libFib
|
||||
private void callback(long val) {
|
||||
System.out.println("Debug: result was: " + val);
|
||||
if (val != expected) {
|
||||
throw new RuntimeException("Expected " + expected + " but got: " +val);
|
||||
}
|
||||
}
|
||||
|
||||
public static native void callJNI(Object target, int num);
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2) {
|
||||
System.err.println("Usage: " + FibJNI.class.getName() + " <input> <expectedResult>");
|
||||
}
|
||||
int input = Integer.parseInt(args[0]);
|
||||
long expected = Long.parseLong(args[1]);
|
||||
FibJNI fib = new FibJNI(input, expected);
|
||||
fib.callNative();
|
||||
System.out.println("DEBUG: Sanity check for " + FibJNI.class.getSimpleName() + " passed.");
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Red Hat, Inc
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
static jlong fib(jint num) {
|
||||
if (num == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (num <= 2) {
|
||||
return 1;
|
||||
}
|
||||
return fib(num - 2) + fib(num -1);
|
||||
}
|
||||
|
||||
static void callCallback(JNIEnv *env, jclass cls, jobject target, jlong result) {
|
||||
jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "(J)V");
|
||||
if (mid == NULL) {
|
||||
jclass nsme = (jclass) (*env)->NewGlobalRef(env,
|
||||
(*env)->FindClass(env,
|
||||
"java/lang/NoSuchMethodException"));
|
||||
if (nsme != NULL) {
|
||||
(*env)->ThrowNew(env, nsme, "Can't find method callback()");
|
||||
}
|
||||
return;
|
||||
}
|
||||
(*env)->CallVoidMethod(env, target, mid, result);
|
||||
}
|
||||
|
||||
static void calculateAndCallCallback(JNIEnv *env, jclass cls, jobject target, jint num) {
|
||||
jlong result = -1;
|
||||
result = fib(num);
|
||||
callCallback(env, cls, target, result);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_fib_FibJNI_callJNI(JNIEnv *env, jclass cls, jobject target, jint num) {
|
||||
calculateAndCallCallback(env, cls, target, num);
|
||||
}
|
Loading…
Reference in New Issue
Block a user