| /* | |
| * 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. | |
| */ | |
| 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; | |
| } | |
| } | |
| } |