8214796: Create a jlink plugin for stripping debug info symbols from native libraries

Reviewed-by: alanb, mchung, erikj, ihse
This commit is contained in:
Severin Gehwolf 2019-03-14 14:04:39 +01:00
parent 2b55e4834e
commit 1f115c7f52
16 changed files with 1799 additions and 7 deletions

View File

@ -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)) ) \
)

View File

@ -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, \
))

View File

@ -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)), )

View File

@ -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;
}
}
}

View File

@ -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.

View 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;

View File

@ -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());
}
}

View File

@ -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());
}
}
}

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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 {

View 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;
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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.");
}
}

View File

@ -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);
}