8387324: jlink --strip-debug should filter external debug symbols

Reviewed-by: sgehwolf, dbalek, clanger
This commit is contained in:
Arno Zeller 2026-07-30 07:17:04 +00:00
parent 94e1b11e7d
commit e04f4dd46f
2 changed files with 107 additions and 24 deletions

View File

@ -1,4 +1,5 @@
/*
* Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -27,12 +28,13 @@ package jdk.tools.jlink.internal.plugins;
import java.util.Map;
import jdk.tools.jlink.internal.Platform;
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;
import jdk.tools.jlink.plugin.ResourcePoolModule;
/**
* Combined debug stripping plugin: Java debug attributes and native debug
@ -43,6 +45,7 @@ public final class DefaultStripDebugPlugin extends AbstractPlugin {
private static final String STRIP_NATIVE_DEBUG_PLUGIN = "strip-native-debug-symbols";
private static final String EXCLUDE_DEBUGINFO = "exclude-debuginfo-files";
private static final String EXCLUDE_FILES_PLUGIN = "exclude-files";
private final Plugin javaStripPlugin;
private final NativePluginFactory stripNativePluginFactory;
@ -68,26 +71,46 @@ public final class DefaultStripDebugPlugin extends AbstractPlugin {
@Override
public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
Plugin stripNativePlugin = stripNativePluginFactory.create();
if (stripNativePlugin != null) {
Map<String, String> stripNativeConfig = Map.of(
STRIP_NATIVE_DEBUG_PLUGIN, EXCLUDE_DEBUGINFO);
stripNativePlugin.configure(stripNativeConfig);
if (!isJavaStripPluginEnabled) {
return stripNativePlugin.transform(in, out);
}
String pattern = debugFilePattern(in);
ExcludeFilesPlugin excludeFilesPlugin = new ExcludeFilesPlugin();
excludeFilesPlugin.configure(Map.of(EXCLUDE_FILES_PLUGIN, pattern));
ResourcePoolManager outRes =
new ResourcePoolManager(in.byteOrder(),
((ResourcePoolImpl)in).getStringTable());
ResourcePool strippedJava = javaStripPlugin.transform(in,
outRes.resourcePoolBuilder());
return stripNativePlugin.transform(strippedJava, out);
} else if (isJavaStripPluginEnabled) {
return javaStripPlugin.transform(in, out);
} else {
return in;
ResourcePool result = in;
if (isJavaStripPluginEnabled) {
result = pipe(result, javaStripPlugin);
}
if (stripNativePlugin != null) {
stripNativePlugin.configure(Map.of(STRIP_NATIVE_DEBUG_PLUGIN, EXCLUDE_DEBUGINFO));
result = pipe(result, stripNativePlugin);
}
return excludeFilesPlugin.transform(result, out);
}
// Returns the glob pattern for debug files matching the target platform.
// Mirrors the per-OS exclusion logic in make/CreateJmods.gmk.
private static String debugFilePattern(ResourcePool in) {
Platform platform;
try {
String tp = in.moduleView()
.findModule("java.base")
.map(ResourcePoolModule::targetPlatform)
.orElse(null);
platform = tp != null ? Platform.parsePlatform(tp) : Platform.runtime();
} catch (IllegalArgumentException e) {
platform = Platform.runtime();
}
return switch (platform.os()) {
case WINDOWS -> "**.pdb,**.map,**.diz";
case MACOS -> "**.dSYM/**,**.diz";
default -> "**.debuginfo,**.diz"; // Linux, AIX
};
}
private ResourcePool pipe(ResourcePool pool, Plugin plugin) {
ResourcePoolManager mgr = new ResourcePoolManager(
pool.byteOrder(), ((ResourcePoolManager.ResourcePoolImpl)pool).getStringTable());
return plugin.transform(pool, mgr.resourcePoolBuilder());
}
public interface NativePluginFactory {

View File

@ -1,4 +1,5 @@
/*
* Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -23,6 +24,7 @@
import java.util.Map;
import jdk.tools.jlink.internal.Platform;
import jdk.tools.jlink.internal.ResourcePoolManager;
import jdk.tools.jlink.internal.plugins.DefaultStripDebugPlugin;
import jdk.tools.jlink.internal.plugins.DefaultStripDebugPlugin.NativePluginFactory;
@ -51,12 +53,23 @@ public class DefaultStripDebugPluginTest {
DefaultStripDebugPlugin plugin = new DefaultStripDebugPlugin(javaPlugin,
nativeFactory);
ResourcePoolManager inManager = new ResourcePoolManager();
inManager.add(ResourcePoolEntry.create(MockStripPlugin.DEBUGINFO_PATH,
ResourcePoolEntry.Type.NATIVE_LIB, new byte[]{0, 1, 2, 3}));
inManager.add(ResourcePoolEntry.create(MockStripPlugin.DIZ_PATH,
ResourcePoolEntry.Type.NATIVE_LIB, new byte[]{0, 1, 2, 3}));
ResourcePoolManager outManager = new ResourcePoolManager();
ResourcePool pool = plugin.transform(inManager.resourcePool(),
inManager.resourcePoolBuilder());
outManager.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");
}
if (pool.findEntry(MockStripPlugin.DEBUGINFO_PATH).isPresent()) {
throw new AssertionError(".debuginfo file should have been excluded");
}
if (pool.findEntry(MockStripPlugin.DIZ_PATH).isPresent()) {
throw new AssertionError(".diz file should have been excluded");
}
}
public void testNoNativeStripPluginPresent() {
@ -66,11 +79,22 @@ public class DefaultStripDebugPluginTest {
DefaultStripDebugPlugin plugin = new DefaultStripDebugPlugin(javaPlugin,
nativeFactory);
ResourcePoolManager inManager = new ResourcePoolManager();
inManager.add(ResourcePoolEntry.create(MockStripPlugin.DEBUGINFO_PATH,
ResourcePoolEntry.Type.NATIVE_LIB, new byte[]{0, 1, 2, 3}));
inManager.add(ResourcePoolEntry.create(MockStripPlugin.DIZ_PATH,
ResourcePoolEntry.Type.NATIVE_LIB, new byte[]{0, 1, 2, 3}));
ResourcePoolManager outManager = new ResourcePoolManager();
ResourcePool pool = plugin.transform(inManager.resourcePool(),
inManager.resourcePoolBuilder());
outManager.resourcePoolBuilder());
if (!pool.findEntry(MockStripPlugin.JAVA_PATH).isPresent()) {
throw new AssertionError("Expected java strip plugin to get called");
}
if (pool.findEntry(MockStripPlugin.DEBUGINFO_PATH).isPresent()) {
throw new AssertionError(".debuginfo file should have been excluded");
}
if (pool.findEntry(MockStripPlugin.DIZ_PATH).isPresent()) {
throw new AssertionError(".diz file should have been excluded");
}
}
// Disable embedded strip Java plugin, with native plugin present.
@ -84,12 +108,23 @@ public class DefaultStripDebugPluginTest {
plugin.enableJavaStripPlugin(false);
ResourcePoolManager inManager = new ResourcePoolManager();
inManager.add(ResourcePoolEntry.create(MockStripPlugin.DEBUGINFO_PATH,
ResourcePoolEntry.Type.NATIVE_LIB, new byte[]{0, 1, 2, 3}));
inManager.add(ResourcePoolEntry.create(MockStripPlugin.DIZ_PATH,
ResourcePoolEntry.Type.NATIVE_LIB, new byte[]{0, 1, 2, 3}));
ResourcePoolManager outManager = new ResourcePoolManager();
ResourcePool pool = plugin.transform(inManager.resourcePool(),
inManager.resourcePoolBuilder());
outManager.resourcePoolBuilder());
if (pool.findEntry(MockStripPlugin.JAVA_PATH).isPresent() ||
!pool.findEntry(MockStripPlugin.NATIVE_PATH).isPresent()) {
throw new AssertionError("Expected only native to get called");
}
if (pool.findEntry(MockStripPlugin.DEBUGINFO_PATH).isPresent()) {
throw new AssertionError(".debuginfo file should have been excluded");
}
if (pool.findEntry(MockStripPlugin.DIZ_PATH).isPresent()) {
throw new AssertionError(".diz file should have been excluded");
}
}
// Disable embedded strip Java plugin, and without native plugin present.
@ -101,12 +136,23 @@ public class DefaultStripDebugPluginTest {
nativeFactory);
plugin.enableJavaStripPlugin(false);
ResourcePoolManager inManager = new ResourcePoolManager();
inManager.add(ResourcePoolEntry.create(MockStripPlugin.DEBUGINFO_PATH,
ResourcePoolEntry.Type.NATIVE_LIB, new byte[]{0, 1, 2, 3}));
inManager.add(ResourcePoolEntry.create(MockStripPlugin.DIZ_PATH,
ResourcePoolEntry.Type.NATIVE_LIB, new byte[]{0, 1, 2, 3}));
ResourcePoolManager outManager = new ResourcePoolManager();
ResourcePool pool = plugin.transform(inManager.resourcePool(),
inManager.resourcePoolBuilder());
outManager.resourcePoolBuilder());
if (pool.findEntry(MockStripPlugin.JAVA_PATH).isPresent() ||
pool.findEntry(MockStripPlugin.NATIVE_PATH).isPresent()) {
throw new AssertionError("Expected both native and java not called");
}
if (pool.findEntry(MockStripPlugin.DEBUGINFO_PATH).isPresent()) {
throw new AssertionError(".debuginfo file should have been excluded");
}
if (pool.findEntry(MockStripPlugin.DIZ_PATH).isPresent()) {
throw new AssertionError(".diz file should have been excluded");
}
}
public static void main(String[] args) {
@ -121,10 +167,25 @@ public class DefaultStripDebugPluginTest {
private static final String NATIVE_PATH = "/foo/lib/test.so.debug";
private static final String JAVA_PATH = "/foo/TestClass.class";
// Platform-appropriate debug file paths, matching the patterns chosen by
// DefaultStripDebugPlugin.debugFilePattern() for the runtime platform.
static final String DEBUGINFO_PATH = platformDebugPath();
static final String DIZ_PATH = "/foo/lib/libfoo.diz";
private static final String STRIP_NATIVE_NAME = "strip-native-debug-symbols";
private static final String OMIT_ARG = "exclude-debuginfo-files";
private final boolean isNative;
private static String platformDebugPath() {
String platform = Platform.runtime().toString();
if (platform.startsWith("windows")) {
return "/foo/bin/libfoo.pdb";
} else if (platform.startsWith("macos")) {
return "/foo/lib/libfoo.dylib.dSYM/Contents/Resources/DWARF/libfoo.dylib";
} else {
return "/foo/lib/libfoo.so.debuginfo";
}
}
MockStripPlugin(boolean isNative) {
this.isNative = isNative;
}
@ -153,8 +214,7 @@ public class DefaultStripDebugPluginTest {
resPath = NATIVE_PATH;
type = Type.NATIVE_LIB;
}
ResourcePoolEntry entry = createMockEntry(resPath, type);
out.add(entry);
out.add(createMockEntry(resPath, type));
return out.build();
}