--- old/make/common/TestFilesCompilation.gmk 2019-02-15 17:54:54.407518643 +0100 +++ new/make/common/TestFilesCompilation.gmk 2019-02-15 17:54:54.188518536 +0100 @@ -105,7 +105,7 @@ 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)) ) \ ) --- old/make/gensrc/Gensrc-jdk.jlink.gmk 2019-02-15 17:54:55.063518962 +0100 +++ new/make/gensrc/Gensrc-jdk.jlink.gmk 2019-02-15 17:54:54.870518868 +0100 @@ -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, \ )) --- old/make/test/JtregNativeJdk.gmk 2019-02-15 17:54:55.724519283 +0100 +++ new/make/test/JtregNativeJdk.gmk 2019-02-15 17:54:55.515519182 +0100 @@ -82,6 +82,14 @@ BUILD_JDK_JTREG_EXCLUDE += exeJniInvocationTest.c endif +ifeq ($(OPENJDK_TARGET_OS),linux) + # 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 + + $(eval $(call SetupTestFilesCompilation, BUILD_JDK_JTREG_LIBRARIES, \ TYPE := LIBRARY, \ SOURCE_DIRS := $(BUILD_JDK_JTREG_NATIVE_SRC), \ --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/DefaultStripDebugPlugin.java 2019-02-15 17:54:56.388519606 +0100 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/DefaultStripDebugPlugin.java 2019-02-15 17:54:56.173519502 +0100 @@ -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; @@ -38,8 +43,22 @@ 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 OMIT_DEBUGINFO = "omit-debuginfo"; + + private final Plugin javaStripPlugin; + private final NativePluginFactory stripNativePluginFactory; + + public DefaultStripDebugPlugin() { + this(new StripJavaDebugAttributesPlugin(), + new DefaultNativePluginFactory()); + } - private final Plugin javaStripPlugin = new StripJavaDebugAttributesPlugin(); + public DefaultStripDebugPlugin(Plugin javaStripPlugin, + NativePluginFactory nativeStripPluginFact) { + this.javaStripPlugin = javaStripPlugin; + this.stripNativePluginFactory = nativeStripPluginFact; + } @Override public String getName() { @@ -53,7 +72,34 @@ @Override public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { - return javaStripPlugin.transform(in, out); + Plugin stripNativePlugin = stripNativePluginFactory.create(); + if (stripNativePlugin != null) { + Map stripNativeConfig = Map.of( + STRIP_NATIVE_DEBUG_PLUGIN, OMIT_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()); + } + } } --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/PluginsResourceBundle.java 2019-02-15 17:54:57.054519930 +0100 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/PluginsResourceBundle.java 2019-02-15 17:54:56.855519833 +0100 @@ -62,7 +62,11 @@ } 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); } } --- old/test/jdk/tools/jlink/IntegrationTest.java 2019-02-15 17:54:57.682520235 +0100 +++ new/test/jdk/tools/jlink/IntegrationTest.java 2019-02-15 17:54:57.489520142 +0100 @@ -69,7 +69,7 @@ * jdk.jlink/jdk.tools.jimage * jdk.compiler * @build tests.* - * @run main IntegrationTest + * @run main/othervm -Xmx1g IntegrationTest */ public class IntegrationTest { --- /dev/null 2019-02-15 09:11:05.676000757 +0100 +++ new/src/jdk.jlink/linux/classes/jdk/tools/jlink/internal/plugins/StripNativeDebugSymbolsPlugin.java 2019-02-15 17:54:58.126520451 +0100 @@ -0,0 +1,392 @@ +/* + * 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.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.List; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +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 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"; + private static final String OMIT_DEBUG_INFO_ARG = "omit-debuginfo"; + 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) { + in.transformAndCopy((resource) -> { + ResourcePoolEntry res = resource; + if (resource.type() == ResourcePoolEntry.Type.NATIVE_LIB || + resource.type() == ResourcePoolEntry.Type.NATIVE_CMD) { + String moduleName = resource.moduleName(); + if (resource.path().endsWith(SHARED_LIBS_EXT) || + resource.path().startsWith("/" + moduleName + "/bin")) { + StrippedDebugInfoBinary strippedBin = stripBinary(resource); + if (strippedBin != null) { + res = resource.copyWithContent(strippedBin.strippedBinary); + if (includeDebugSymbols) { + String debugEntryPath = resource.path() + + "." + debuginfoExt; + ResourcePoolEntry debugEntry = ResourcePoolEntry.create( + debugEntryPath, + resource.type(), + strippedBin.debugSymbols); + out.add(debugEntry); + } + } + } + } + return res; + }, out); + + return out.build(); + } + + @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); + } + + // --strip-native-debug-symbols= + @Override + public void configure(Map config) { + doConfigure(true, config); + } + + // For testing so that validation can be turned off + public void doConfigure(boolean withChecks, Map config) { + String arg = config.get(NAME); + + stripBin = DEFAULT_STRIP_CMD; + debuginfoExt = DEFAULT_DEBUG_EXT; + + if (arg == null) { // will this ever be null? + return; + } + boolean hasOmitDebugInfo = false; + boolean hasKeepDebugInfo = false; + + if (KEEP_DEBUG_INFO_ARG.equals(arg)) { + // Case: --strip-native-debug-symbols keep-debug-info + hasKeepDebugInfo = true; + } else if (arg.startsWith(KEEP_DEBUG_INFO_ARG)) { + // Case: --strip-native-debug-symbols keep-debug-info=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 (OMIT_DEBUG_INFO_ARG.equals(arg)) { + // Case: --strip-native-debug-symbols omit-debug-info + hasOmitDebugInfo = true; + } + if (arg.startsWith(STRIP_CMD_ARG)) { + // Case: --strip-native-debug-symbols objcopy= + // --strip-native-debug-symbols keep-debug-info=ext:objcopy= + // --strip-native-debug-symbols omit-debug-info:objcopy= + String stripArg = config.get(STRIP_CMD_ARG); + if (stripArg != null && withChecks) { + validateStripArg(stripArg); + } + if (stripArg != null) { + stripBin = stripArg; + } + // Case (reversed combination) + // --strip-native-debug-symbols objcopy=:keep-debug-info=ext + // Note: cases like the following are not allowed by the parser + // --strip-native-debug-symbols objcopy=:keep-debug-info + // --strip-native-debug-symbols objcopy=:omit-debug-info + String keepDebugInfo = config.get(KEEP_DEBUG_INFO_ARG); + if (keepDebugInfo != null) { + hasKeepDebugInfo = true; + debuginfoExt = keepDebugInfo; + } + if (hasKeepDebugInfo && hasOmitDebugInfo) { + // cannot keep and omit debug info at the same time + throw new IllegalArgumentException( + PluginsResourceBundle.getMessage(resourceBundle, + NAME + ".iae.conflict", + NAME, + OMIT_DEBUG_INFO_ARG, + KEEP_DEBUG_INFO_ARG)); + } + includeDebugSymbols = hasKeepDebugInfo; + } + + private StrippedDebugInfoBinary stripBinary(ResourcePoolEntry resource) { + Path tempDir = null; + 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 + "." + debuginfoExt; + + Files.write(resourceFileBinary, resource.contentBytes()); + StrippedDebugInfoBinary strippedBin = new StrippedDebugInfoBinary(); + Path resourceFileDebugSymbols; + if (includeDebugSymbols) { + resourceFileDebugSymbols = tempDir.resolve(Paths.get(relativeDbgFileName)); + strippedBin.debugSymbols = createDebugSymbolsFile(resourceFileBinary, + resourceFileDebugSymbols, + relativeDbgFileName); + } + if (!stripBinary(resourceFileBinary)) { + return null; + } + if (includeDebugSymbols && !addGnuDebugLink(tempDir, + relativeFileName, + relativeDbgFileName)) { + return null; + } + strippedBin.strippedBinary = Files.readAllBytes(resourceFileBinary); + return strippedBin; + } 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.toFile().getAbsolutePath(); + List stripCmdLine = buildCmdLine(STRIP_DEBUG_SYMS_OPT, + filePath); + ProcessBuilder builder = new ProcessBuilder(stripCmdLine); + Process stripProc = builder.start(); + int retval = stripProc.waitFor(); + return retval == 0; + } + + private List buildCmdLine(String ...opts) { + List objCopyCmd = cmdBuilder.build(stripBin); + objCopyCmd.addAll(Arrays.asList(opts)); + return objCopyCmd; + } + + private void deleteDirRecursivelyIgnoreResult(Path tempDir) { + try { + Files.walkFileTree(tempDir, new SimpleFileVisitor() { + @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 + } + } + + /* + * 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 stripCmdLine = buildCmdLine(ADD_DEBUG_LINK_OPT + + "=" + relativeDbgFileName, + binFile); + ProcessBuilder builder = new ProcessBuilder(stripCmdLine); + 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.toFile().getAbsolutePath(); + String dbgPath = debugPath.toFile().getAbsolutePath(); + List stripCmdLine = buildCmdLine(ONLY_KEEP_DEBUG_SYMS_OPT, + filePath, + dbgPath); + ProcessBuilder builder = new ProcessBuilder(stripCmdLine); + Process stripProc = builder.start(); + int retval = stripProc.waitFor(); + if (retval != 0) { + return null; + } else { + return Files.readAllBytes(debugPath); + } + } + + 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 StrippedDebugInfoBinary { + byte[] strippedBinary; + byte[] debugSymbols; + } + + // For better testing using mocked objcopy + public static interface ObjCopyCmdBuilder { + List build(String objCopy); + } + + private static final class DefaultObjCopyCmdBuilder implements ObjCopyCmdBuilder { + + @Override + public List build(String objCopy) { + List cmdList = new ArrayList<>(); + cmdList.add(objCopy); + return cmdList; + } + + } + +} --- /dev/null 2019-02-15 09:11:05.676000757 +0100 +++ new/src/jdk.jlink/linux/classes/jdk/tools/jlink/resources/strip_native_debug_symbols_plugin.properties 2019-02-15 17:54:58.775520767 +0100 @@ -0,0 +1,44 @@ +# +# 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\ +\ \ \ omit-debuginfo: Do not keep debug info files. Defaults to true.\n\ +\ \ \ keep-debuginfo[=]: Keep debug info files in ..\n\ +\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Defaults to .debuginfo \n\ +\ Examples: --strip-native-debug-symbols objcopy=/usr/bin/objcopy\n\ +\ \ \ \ \ \ \ \ \ \ \ --strip-native-debug-symbols=omit-debuginfo\n\ +\ \ \ \ \ \ \ \ \ \ \ --strip-native-debug-symbols keep-debuginfo:objcopy=objcopy + +strip-native-debug-symbols.argument=\ + + +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 --- /dev/null 2019-02-15 09:11:05.676000757 +0100 +++ new/src/jdk.jlink/linux/classes/module-info.java.extra 2019-02-15 17:54:59.439521090 +0100 @@ -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; --- /dev/null 2019-02-15 09:11:05.676000757 +0100 +++ new/test/jdk/tools/jlink/plugins/DefaultStripDebugPluginTest.java 2019-02-15 17:55:00.122521422 +0100 @@ -0,0 +1,151 @@ +/* + * 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. + * @author Severin Gehwolf + * @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 = "omit-debuginfo"; + private final boolean isNative; + + MockStripPlugin(boolean isNative) { + this.isNative = isNative; + } + + @Override + public void configure(Map 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; + } + + } +} --- /dev/null 2019-02-15 09:11:05.676000757 +0100 +++ new/test/jdk/tools/jlink/plugins/strip-native-debug-symbols/FakeObjCopy.java 2019-02-15 17:55:00.781521742 +0100 @@ -0,0 +1,78 @@ +/* + * 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. + * + * @author Severin Gehwolf + */ +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.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); + } + +} --- /dev/null 2019-02-15 09:11:05.676000757 +0100 +++ new/test/jdk/tools/jlink/plugins/strip-native-debug-symbols/StripNativeDebugSymbolsPluginTest.java 2019-02-15 17:55:01.447522066 +0100 @@ -0,0 +1,750 @@ +/* + * 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 + * @author Severin Gehwolf + * @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 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(); + System.out.println("Test testConfigureFakeObjCopy() PASSED!"); + } + + private void configureObjcopyWithKeep() throws Exception { + String objcopyPath = "foobar"; + String debugExt = "debuginfo"; // that's the default value + Map config = Map.of( + StripNativeDebugSymbolsPlugin.NAME, "keep-debuginfo", + "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", debugExt + ); + doKeepDebugInfoFakeObjCopyTest(config, debugExt, objcopyPath); + System.out.println("DEBUG: configureObjcopyWithKeep() PASSED!"); + } + + private void configureObjcopyWithOmit() throws Exception { + String objcopyPath = "something-non-standard"; + Map config = Map.of( + StripNativeDebugSymbolsPlugin.NAME, "omit-debuginfo", + "objcopy", objcopyPath + ); + doOmitDebugInfoFakeObjCopyTest(config, objcopyPath); + System.out.println("DEBUG: configureObjcopyWithOmit() PASSED!"); + } + + private void configureConflictingOptions() throws Exception { + Map defaultConfig = Map.of( + StripNativeDebugSymbolsPlugin.NAME, "omit-debuginfo", + "keep-debuginfo", "foo-ext" + ); + try { + createAndConfigPlugin(defaultConfig); + throw new AssertionError("keep-debuginfo and omit-debuginfo " + + " should have conflicted!"); + } catch (IllegalArgumentException e) { + // pass + if (e.getMessage().contains("keep-debuginfo") && + e.getMessage().contains("omit-debuginfo")) { + System.out.println("DEBUG: test threw IAE " + e.getMessage() + + " as expected."); + } else { + throw new AssertionError("Unexpected IAE", e); + } + } + } + + public void testTransformFakeObjCopyNoDebugInfoFiles() throws Exception { + Map defaultConfig = Map.of( + StripNativeDebugSymbolsPlugin.NAME, "omit-debuginfo" + ); + doOmitDebugInfoFakeObjCopyTest(defaultConfig, DEFAULT_OBJCOPY_CMD); + System.out.println("testTransformFakeObjCopyNoDebugInfoFiles() PASSED!"); + } + + private void doOmitDebugInfoFakeObjCopyTest(Map 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 defaultConfig = Map.of( + StripNativeDebugSymbolsPlugin.NAME, "keep-debuginfo=" + DEBUG_EXTENSION + ); + doKeepDebugInfoFakeObjCopyTest(defaultConfig, + DEBUG_EXTENSION, + DEFAULT_OBJCOPY_CMD); + System.out.println("testTransformFakeObjCopyKeepDebugInfoFiles() PASSED!"); + } + + private void doKeepDebugInfoFakeObjCopyTest(Map 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=omit-debuginfo").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 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=" + 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 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 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 config, + String expectedObjcopy) + throws IOException { + TestObjCopyCmdBuilder cmdBuilder = new TestObjCopyCmdBuilder(expectedObjcopy); + return createAndConfigPlugin(config, cmdBuilder); + } + + private StripNativeDebugSymbolsPlugin createAndConfigPlugin( + Map config) throws IOException { + TestObjCopyCmdBuilder cmdBuilder = new TestObjCopyCmdBuilder(); + return createAndConfigPlugin(config, cmdBuilder); + } + + private StripNativeDebugSymbolsPlugin createAndConfigPlugin( + Map 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 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 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 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 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 nativeLibs = new ArrayList<>(); + private final List 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 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() { + @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 build(String objCopy) { + if (!expectedObjCopy.equals(objCopy)) { + throw new AssertionError("Expected objcopy to be '" + + expectedObjCopy + "' but was '" + + objCopy); + } + List 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); + return fakeObjCopy; + } + + } +} --- /dev/null 2019-02-15 09:11:05.676000757 +0100 +++ new/test/jdk/tools/jlink/plugins/strip-native-debug-symbols/src/fib/FibJNI.java 2019-02-15 17:55:02.096522382 +0100 @@ -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() + " "); + } + 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."); + } +} --- /dev/null 2019-02-15 09:11:05.676000757 +0100 +++ new/test/jdk/tools/jlink/plugins/strip-native-debug-symbols/src/libFib.c 2019-02-15 17:55:02.756522703 +0100 @@ -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 + +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); +}