mirror of
https://github.com/PaperMC/Paper.git
synced 2025-07-26 09:42:06 -07:00
Deobfuscate stacktraces in log messages, crash reports, and etc.
This commit is contained in:
@@ -61,6 +61,7 @@ dependencies {
|
|||||||
mockitoAgent("org.mockito:mockito-core:5.14.1") { isTransitive = false } // Paper - configure mockito agent that is needed in newer java versions
|
mockitoAgent("org.mockito:mockito-core:5.14.1") { isTransitive = false } // Paper - configure mockito agent that is needed in newer java versions
|
||||||
testImplementation("org.ow2.asm:asm-tree:9.7.1")
|
testImplementation("org.ow2.asm:asm-tree:9.7.1")
|
||||||
testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest
|
testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest
|
||||||
|
implementation("net.neoforged:srgutils:1.0.9") // Paper - mappings handling
|
||||||
}
|
}
|
||||||
|
|
||||||
paperweight {
|
paperweight {
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
--- a/net/minecraft/CrashReport.java
|
--- a/net/minecraft/CrashReport.java
|
||||||
+++ b/net/minecraft/CrashReport.java
|
+++ b/net/minecraft/CrashReport.java
|
||||||
@@ -36,6 +36,7 @@
|
@@ -34,8 +34,10 @@
|
||||||
|
private final SystemReport systemReport = new SystemReport();
|
||||||
|
|
||||||
public CrashReport(String message, Throwable cause) {
|
public CrashReport(String message, Throwable cause) {
|
||||||
|
+ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper
|
||||||
this.title = message;
|
this.title = message;
|
||||||
this.exception = cause;
|
this.exception = cause;
|
||||||
+ this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit
|
+ this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
--- a/net/minecraft/CrashReportCategory.java
|
||||||
|
+++ b/net/minecraft/CrashReportCategory.java
|
||||||
|
@@ -110,6 +110,7 @@
|
||||||
|
} else {
|
||||||
|
this.stackTrace = new StackTraceElement[stackTraceElements.length - 3 - ignoredCallCount];
|
||||||
|
System.arraycopy(stackTraceElements, 3 + ignoredCallCount, this.stackTrace, 0, this.stackTrace.length);
|
||||||
|
+ this.stackTrace = io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(this.stackTrace); // Paper
|
||||||
|
return this.stackTrace.length;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,22 @@
|
|||||||
--- a/net/minecraft/network/Connection.java
|
--- a/net/minecraft/network/Connection.java
|
||||||
+++ b/net/minecraft/network/Connection.java
|
+++ b/net/minecraft/network/Connection.java
|
||||||
|
@@ -82,13 +82,13 @@
|
||||||
|
marker.add(Connection.PACKET_MARKER);
|
||||||
|
});
|
||||||
|
public static final Supplier<NioEventLoopGroup> NETWORK_WORKER_GROUP = Suppliers.memoize(() -> {
|
||||||
|
- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build());
|
||||||
|
+ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
|
||||||
|
});
|
||||||
|
public static final Supplier<EpollEventLoopGroup> NETWORK_EPOLL_WORKER_GROUP = Suppliers.memoize(() -> {
|
||||||
|
- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build());
|
||||||
|
+ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
|
||||||
|
});
|
||||||
|
public static final Supplier<DefaultEventLoopGroup> LOCAL_WORKER_GROUP = Suppliers.memoize(() -> {
|
||||||
|
- return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build());
|
||||||
|
+ return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
|
||||||
|
});
|
||||||
|
private static final ProtocolInfo<ServerHandshakePacketListener> INITIAL_PROTOCOL = HandshakeProtocols.SERVERBOUND;
|
||||||
|
private final PacketFlow receiving;
|
||||||
@@ -96,6 +96,11 @@
|
@@ -96,6 +96,11 @@
|
||||||
private final Queue<Consumer<Connection>> pendingActions = Queues.newConcurrentLinkedQueue();
|
private final Queue<Consumer<Connection>> pendingActions = Queues.newConcurrentLinkedQueue();
|
||||||
public Channel channel;
|
public Channel channel;
|
||||||
@@ -46,7 +63,7 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) throwable.printStackTrace(); // Spigot
|
+ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) io.papermc.paper.util.TraceUtil.printStackTrace(throwable); // Spigot // Paper
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet<?> packet) {
|
protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet<?> packet) {
|
||||||
|
@@ -143,7 +143,7 @@
|
|||||||
thread.setDaemon(true);
|
thread.setDaemon(true);
|
||||||
thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER));
|
thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER));
|
||||||
thread.start();
|
thread.start();
|
||||||
@@ -126,13 +204,22 @@
|
@@ -126,13 +204,23 @@
|
||||||
this.setPreventProxyConnections(dedicatedserverproperties.preventProxyConnections);
|
this.setPreventProxyConnections(dedicatedserverproperties.preventProxyConnections);
|
||||||
this.setLocalIp(dedicatedserverproperties.serverIp);
|
this.setLocalIp(dedicatedserverproperties.serverIp);
|
||||||
}
|
}
|
||||||
@@ -152,6 +152,7 @@
|
|||||||
+ org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings"));
|
+ org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings"));
|
||||||
+ org.spigotmc.SpigotConfig.registerCommands();
|
+ org.spigotmc.SpigotConfig.registerCommands();
|
||||||
+ // Spigot end
|
+ // Spigot end
|
||||||
|
+ io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc.
|
||||||
+ // Paper start - initialize global and world-defaults configuration
|
+ // Paper start - initialize global and world-defaults configuration
|
||||||
+ this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess());
|
+ this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess());
|
||||||
+ this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
|
+ this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
|
||||||
@@ -167,7 +168,7 @@
|
|||||||
DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode);
|
DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode);
|
||||||
InetAddress inetaddress = null;
|
InetAddress inetaddress = null;
|
||||||
|
|
||||||
@@ -156,10 +243,23 @@
|
@@ -156,10 +244,23 @@
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +193,7 @@
|
|||||||
DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file.");
|
DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +270,7 @@
|
@@ -170,7 +271,7 @@
|
||||||
if (!OldUsersConverter.serverReadyAfterUserconversion(this)) {
|
if (!OldUsersConverter.serverReadyAfterUserconversion(this)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -201,7 +202,7 @@
|
|||||||
this.debugSampleSubscriptionTracker = new DebugSampleSubscriptionTracker(this.getPlayerList());
|
this.debugSampleSubscriptionTracker = new DebugSampleSubscriptionTracker(this.getPlayerList());
|
||||||
this.tickTimeLogger = new RemoteSampleLogger(TpsDebugDimensions.values().length, this.debugSampleSubscriptionTracker, RemoteDebugSampleType.TICK_TIME);
|
this.tickTimeLogger = new RemoteSampleLogger(TpsDebugDimensions.values().length, this.debugSampleSubscriptionTracker, RemoteDebugSampleType.TICK_TIME);
|
||||||
long i = Util.getNanos();
|
long i = Util.getNanos();
|
||||||
@@ -178,13 +278,13 @@
|
@@ -178,13 +279,13 @@
|
||||||
SkullBlockEntity.setup(this.services, this);
|
SkullBlockEntity.setup(this.services, this);
|
||||||
GameProfileCache.setUsesAuthentication(this.usesAuthentication());
|
GameProfileCache.setUsesAuthentication(this.usesAuthentication());
|
||||||
DedicatedServer.LOGGER.info("Preparing level \"{}\"", this.getLevelIdName());
|
DedicatedServer.LOGGER.info("Preparing level \"{}\"", this.getLevelIdName());
|
||||||
@@ -217,7 +218,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dedicatedserverproperties.enableQuery) {
|
if (dedicatedserverproperties.enableQuery) {
|
||||||
@@ -197,7 +297,7 @@
|
@@ -197,7 +298,7 @@
|
||||||
this.rconThread = RconThread.create(this);
|
this.rconThread = RconThread.create(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +227,7 @@
|
|||||||
Thread thread1 = new Thread(new ServerWatchdog(this));
|
Thread thread1 = new Thread(new ServerWatchdog(this));
|
||||||
|
|
||||||
thread1.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(DedicatedServer.LOGGER));
|
thread1.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(DedicatedServer.LOGGER));
|
||||||
@@ -293,6 +393,7 @@
|
@@ -293,6 +394,7 @@
|
||||||
this.queryThreadGs4.stop();
|
this.queryThreadGs4.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +235,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -302,8 +403,8 @@
|
@@ -302,8 +404,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -245,7 +246,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void handleConsoleInput(String command, CommandSourceStack commandSource) {
|
public void handleConsoleInput(String command, CommandSourceStack commandSource) {
|
||||||
@@ -311,12 +412,22 @@
|
@@ -311,12 +413,22 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleConsoleInputs() {
|
public void handleConsoleInputs() {
|
||||||
@@ -269,7 +270,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -383,7 +494,7 @@
|
@@ -383,7 +495,7 @@
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUnderSpawnProtection(ServerLevel world, BlockPos pos, Player player) {
|
public boolean isUnderSpawnProtection(ServerLevel world, BlockPos pos, Player player) {
|
||||||
@@ -278,7 +279,7 @@
|
|||||||
return false;
|
return false;
|
||||||
} else if (this.getPlayerList().getOps().isEmpty()) {
|
} else if (this.getPlayerList().getOps().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -541,16 +652,52 @@
|
@@ -541,16 +653,52 @@
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPluginNames() {
|
public String getPluginNames() {
|
||||||
@@ -335,7 +336,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void storeUsingWhiteList(boolean useWhitelist) {
|
public void storeUsingWhiteList(boolean useWhitelist) {
|
||||||
@@ -660,4 +807,15 @@
|
@@ -660,4 +808,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,18 @@
|
|||||||
--- a/net/minecraft/server/network/ServerConnectionListener.java
|
--- a/net/minecraft/server/network/ServerConnectionListener.java
|
||||||
+++ b/net/minecraft/server/network/ServerConnectionListener.java
|
+++ b/net/minecraft/server/network/ServerConnectionListener.java
|
||||||
|
@@ -52,10 +52,10 @@
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogUtils.getLogger();
|
||||||
|
public static final Supplier<NioEventLoopGroup> SERVER_EVENT_GROUP = Suppliers.memoize(() -> {
|
||||||
|
- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build());
|
||||||
|
+ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
|
||||||
|
});
|
||||||
|
public static final Supplier<EpollEventLoopGroup> SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize(() -> {
|
||||||
|
- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build());
|
||||||
|
+ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
|
||||||
|
});
|
||||||
|
final MinecraftServer server;
|
||||||
|
public volatile boolean running;
|
||||||
@@ -100,16 +100,26 @@
|
@@ -100,16 +100,26 @@
|
||||||
|
|
||||||
Connection.configureSerialization(channelpipeline, PacketFlow.SERVERBOUND, false, (BandwidthDebugMonitor) null);
|
Connection.configureSerialization(channelpipeline, PacketFlow.SERVERBOUND, false, (BandwidthDebugMonitor) null);
|
||||||
|
@@ -65,7 +65,7 @@
|
|||||||
+ try {
|
+ try {
|
||||||
+ root = NbtIo.readCompressed(new java.io.FileInputStream(file5), NbtAccounter.unlimitedHeap());
|
+ root = NbtIo.readCompressed(new java.io.FileInputStream(file5), NbtAccounter.unlimitedHeap());
|
||||||
+ } catch (Exception exception) {
|
+ } catch (Exception exception) {
|
||||||
+ exception.printStackTrace();
|
+ io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Paper
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ if (root != null) {
|
+ if (root != null) {
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
+ try {
|
+ try {
|
||||||
+ NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2));
|
+ NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2));
|
||||||
+ } catch (Exception exception) {
|
+ } catch (Exception exception) {
|
||||||
+ exception.printStackTrace();
|
+ io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Paper
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+ // CraftBukkit end
|
+ // CraftBukkit end
|
||||||
|
@@ -0,0 +1,66 @@
|
|||||||
|
package io.papermc.paper.logging;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.VarHandle;
|
||||||
|
import org.apache.logging.log4j.core.Core;
|
||||||
|
import org.apache.logging.log4j.core.LogEvent;
|
||||||
|
import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
|
||||||
|
import org.apache.logging.log4j.core.config.plugins.Plugin;
|
||||||
|
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
|
||||||
|
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
||||||
|
@Plugin(
|
||||||
|
name = "StacktraceDeobfuscatingRewritePolicy",
|
||||||
|
category = Core.CATEGORY_NAME,
|
||||||
|
elementType = "rewritePolicy",
|
||||||
|
printObject = true
|
||||||
|
)
|
||||||
|
public final class StacktraceDeobfuscatingRewritePolicy implements RewritePolicy {
|
||||||
|
private static final MethodHandle DEOBFUSCATE_THROWABLE;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
final Class<?> cls = Class.forName("io.papermc.paper.util.StacktraceDeobfuscator");
|
||||||
|
final MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||||
|
final VarHandle instanceHandle = lookup.findStaticVarHandle(cls, "INSTANCE", cls);
|
||||||
|
final Object deobfuscator = instanceHandle.get();
|
||||||
|
DEOBFUSCATE_THROWABLE = lookup
|
||||||
|
.unreflect(cls.getDeclaredMethod("deobfuscateThrowable", Throwable.class))
|
||||||
|
.bindTo(deobfuscator);
|
||||||
|
} catch (final ReflectiveOperationException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private StacktraceDeobfuscatingRewritePolicy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull LogEvent rewrite(final @NonNull LogEvent rewrite) {
|
||||||
|
final Throwable thrown = rewrite.getThrown();
|
||||||
|
if (thrown != null) {
|
||||||
|
deobfuscateThrowable(thrown);
|
||||||
|
return new Log4jLogEvent.Builder(rewrite)
|
||||||
|
.setThrownProxy(null)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return rewrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deobfuscateThrowable(final Throwable thrown) {
|
||||||
|
try {
|
||||||
|
DEOBFUSCATE_THROWABLE.invoke(thrown);
|
||||||
|
} catch (final Error e) {
|
||||||
|
throw e;
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginFactory
|
||||||
|
public static @NonNull StacktraceDeobfuscatingRewritePolicy createPolicy() {
|
||||||
|
return new StacktraceDeobfuscatingRewritePolicy();
|
||||||
|
}
|
||||||
|
}
|
156
paper-server/src/main/java/io/papermc/paper/util/ObfHelper.java
Normal file
156
paper-server/src/main/java/io/papermc/paper/util/ObfHelper.java
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
package io.papermc.paper.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import net.neoforged.srgutils.IMappingFile;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||||
|
|
||||||
|
@DefaultQualifier(NonNull.class)
|
||||||
|
public enum ObfHelper {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
private final @Nullable Map<String, ClassMapping> mappingsByObfName;
|
||||||
|
private final @Nullable Map<String, ClassMapping> mappingsByMojangName;
|
||||||
|
|
||||||
|
ObfHelper() {
|
||||||
|
final @Nullable Set<ClassMapping> maps = loadMappingsIfPresent();
|
||||||
|
if (maps != null) {
|
||||||
|
this.mappingsByObfName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::obfName, map -> map));
|
||||||
|
this.mappingsByMojangName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::mojangName, map -> map));
|
||||||
|
} else {
|
||||||
|
this.mappingsByObfName = null;
|
||||||
|
this.mappingsByMojangName = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Map<String, ClassMapping> mappingsByObfName() {
|
||||||
|
return this.mappingsByObfName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Map<String, ClassMapping> mappingsByMojangName() {
|
||||||
|
return this.mappingsByMojangName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to get the obf name for a given class by its Mojang name. Will
|
||||||
|
* return the input string if mappings are not present.
|
||||||
|
*
|
||||||
|
* @param fullyQualifiedMojangName fully qualified class name (dotted)
|
||||||
|
* @return mapped or original fully qualified (dotted) class name
|
||||||
|
*/
|
||||||
|
public String reobfClassName(final String fullyQualifiedMojangName) {
|
||||||
|
if (this.mappingsByMojangName == null) {
|
||||||
|
return fullyQualifiedMojangName;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ClassMapping map = this.mappingsByMojangName.get(fullyQualifiedMojangName);
|
||||||
|
if (map == null) {
|
||||||
|
return fullyQualifiedMojangName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return map.obfName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to get the Mojang name for a given class by its obf name. Will
|
||||||
|
* return the input string if mappings are not present.
|
||||||
|
*
|
||||||
|
* @param fullyQualifiedObfName fully qualified class name (dotted)
|
||||||
|
* @return mapped or original fully qualified (dotted) class name
|
||||||
|
*/
|
||||||
|
public String deobfClassName(final String fullyQualifiedObfName) {
|
||||||
|
if (this.mappingsByObfName == null) {
|
||||||
|
return fullyQualifiedObfName;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ClassMapping map = this.mappingsByObfName.get(fullyQualifiedObfName);
|
||||||
|
if (map == null) {
|
||||||
|
return fullyQualifiedObfName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return map.mojangName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable Set<ClassMapping> loadMappingsIfPresent() {
|
||||||
|
try (final @Nullable InputStream mappingsInputStream = ObfHelper.class.getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny")) {
|
||||||
|
if (mappingsInputStream == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final IMappingFile mappings = IMappingFile.load(mappingsInputStream); // Mappings are mojang->spigot
|
||||||
|
final Set<ClassMapping> classes = new HashSet<>();
|
||||||
|
|
||||||
|
final StringPool pool = new StringPool();
|
||||||
|
for (final IMappingFile.IClass cls : mappings.getClasses()) {
|
||||||
|
final Map<String, String> methods = new HashMap<>();
|
||||||
|
final Map<String, String> fields = new HashMap<>();
|
||||||
|
final Map<String, String> strippedMethods = new HashMap<>();
|
||||||
|
|
||||||
|
for (final IMappingFile.IMethod methodMapping : cls.getMethods()) {
|
||||||
|
methods.put(
|
||||||
|
pool.string(methodKey(
|
||||||
|
Objects.requireNonNull(methodMapping.getMapped()),
|
||||||
|
Objects.requireNonNull(methodMapping.getMappedDescriptor())
|
||||||
|
)),
|
||||||
|
pool.string(Objects.requireNonNull(methodMapping.getOriginal()))
|
||||||
|
);
|
||||||
|
|
||||||
|
strippedMethods.put(
|
||||||
|
pool.string(pool.string(strippedMethodKey(
|
||||||
|
methodMapping.getMapped(),
|
||||||
|
methodMapping.getDescriptor()
|
||||||
|
))),
|
||||||
|
pool.string(methodMapping.getOriginal())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (final IMappingFile.IField field : cls.getFields()) {
|
||||||
|
fields.put(
|
||||||
|
pool.string(field.getMapped()),
|
||||||
|
pool.string(field.getOriginal())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ClassMapping map = new ClassMapping(
|
||||||
|
Objects.requireNonNull(cls.getMapped()).replace('/', '.'),
|
||||||
|
Objects.requireNonNull(cls.getOriginal()).replace('/', '.'),
|
||||||
|
Map.copyOf(methods),
|
||||||
|
Map.copyOf(fields),
|
||||||
|
Map.copyOf(strippedMethods)
|
||||||
|
);
|
||||||
|
classes.add(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Set.copyOf(classes);
|
||||||
|
} catch (final IOException ex) {
|
||||||
|
System.err.println("Failed to load mappings.");
|
||||||
|
ex.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String strippedMethodKey(final String methodName, final String methodDescriptor) {
|
||||||
|
final String methodKey = methodKey(methodName, methodDescriptor);
|
||||||
|
final int returnDescriptorEnd = methodKey.indexOf(')');
|
||||||
|
return methodKey.substring(0, returnDescriptorEnd + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String methodKey(final String methodName, final String methodDescriptor) {
|
||||||
|
return methodName + methodDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ClassMapping(
|
||||||
|
String obfName,
|
||||||
|
String mojangName,
|
||||||
|
Map<String, String> methodsByObf,
|
||||||
|
Map<String, String> fieldsByObf,
|
||||||
|
// obf name with mapped desc to mapped name. return value is excluded from desc as reflection doesn't use it
|
||||||
|
Map<String, String> strippedMethods
|
||||||
|
) {}
|
||||||
|
}
|
@@ -0,0 +1,144 @@
|
|||||||
|
package io.papermc.paper.util;
|
||||||
|
|
||||||
|
import io.papermc.paper.configuration.GlobalConfiguration;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.Label;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
|
@DefaultQualifier(NonNull.class)
|
||||||
|
public enum StacktraceDeobfuscator {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
private final Map<Class<?>, Int2ObjectMap<String>> lineMapCache = Collections.synchronizedMap(new LinkedHashMap<>(128, 0.75f, true) {
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(final Map.Entry<Class<?>, Int2ObjectMap<String>> eldest) {
|
||||||
|
return this.size() > 127;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public void deobfuscateThrowable(final Throwable throwable) {
|
||||||
|
if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throwable.setStackTrace(this.deobfuscateStacktrace(throwable.getStackTrace()));
|
||||||
|
final Throwable cause = throwable.getCause();
|
||||||
|
if (cause != null) {
|
||||||
|
this.deobfuscateThrowable(cause);
|
||||||
|
}
|
||||||
|
for (final Throwable suppressed : throwable.getSuppressed()) {
|
||||||
|
this.deobfuscateThrowable(suppressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public StackTraceElement[] deobfuscateStacktrace(final StackTraceElement[] traceElements) {
|
||||||
|
if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true
|
||||||
|
return traceElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
final @Nullable Map<String, ObfHelper.ClassMapping> mappings = ObfHelper.INSTANCE.mappingsByObfName();
|
||||||
|
if (mappings == null || traceElements.length == 0) {
|
||||||
|
return traceElements;
|
||||||
|
}
|
||||||
|
final StackTraceElement[] result = new StackTraceElement[traceElements.length];
|
||||||
|
for (int i = 0; i < traceElements.length; i++) {
|
||||||
|
final StackTraceElement element = traceElements[i];
|
||||||
|
|
||||||
|
final String className = element.getClassName();
|
||||||
|
final String methodName = element.getMethodName();
|
||||||
|
|
||||||
|
final ObfHelper.ClassMapping classMapping = mappings.get(className);
|
||||||
|
if (classMapping == null) {
|
||||||
|
result[i] = element;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Class<?> clazz;
|
||||||
|
try {
|
||||||
|
clazz = Class.forName(className);
|
||||||
|
} catch (final ClassNotFoundException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
final @Nullable String methodKey = this.determineMethodForLine(clazz, element.getLineNumber());
|
||||||
|
final @Nullable String mappedMethodName = methodKey == null ? null : classMapping.methodsByObf().get(methodKey);
|
||||||
|
|
||||||
|
result[i] = new StackTraceElement(
|
||||||
|
element.getClassLoaderName(),
|
||||||
|
element.getModuleName(),
|
||||||
|
element.getModuleVersion(),
|
||||||
|
classMapping.mojangName(),
|
||||||
|
mappedMethodName != null ? mappedMethodName : methodName,
|
||||||
|
sourceFileName(classMapping.mojangName()),
|
||||||
|
element.getLineNumber()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable String determineMethodForLine(final Class<?> clazz, final int lineNumber) {
|
||||||
|
return this.lineMapCache.computeIfAbsent(clazz, StacktraceDeobfuscator::buildLineMap).get(lineNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String sourceFileName(final String fullClassName) {
|
||||||
|
final int dot = fullClassName.lastIndexOf('.');
|
||||||
|
final String className = dot == -1
|
||||||
|
? fullClassName
|
||||||
|
: fullClassName.substring(dot + 1);
|
||||||
|
final String rootClassName = className.split("\\$")[0];
|
||||||
|
return rootClassName + ".java";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Int2ObjectMap<String> buildLineMap(final Class<?> key) {
|
||||||
|
final StringPool pool = new StringPool();
|
||||||
|
final Int2ObjectMap<String> lineMap = new Int2ObjectOpenHashMap<>();
|
||||||
|
final class LineCollectingMethodVisitor extends MethodVisitor {
|
||||||
|
private final String name;
|
||||||
|
private final String descriptor;
|
||||||
|
|
||||||
|
LineCollectingMethodVisitor(final String name, final String descriptor) {
|
||||||
|
super(Opcodes.ASM9);
|
||||||
|
this.name = name;
|
||||||
|
this.descriptor = descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitLineNumber(final int line, final Label start) {
|
||||||
|
lineMap.put(line, pool.string(ObfHelper.methodKey(this.name, this.descriptor)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) {
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, final String[] exceptions) {
|
||||||
|
return new LineCollectingMethodVisitor(name, descriptor);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
final @Nullable InputStream inputStream = StacktraceDeobfuscator.class.getClassLoader()
|
||||||
|
.getResourceAsStream(key.getName().replace('.', '/') + ".class");
|
||||||
|
if (inputStream == null) {
|
||||||
|
throw new IllegalStateException("Could not find class file: " + key.getName());
|
||||||
|
}
|
||||||
|
final byte[] classData;
|
||||||
|
try (inputStream) {
|
||||||
|
classData = inputStream.readAllBytes();
|
||||||
|
}
|
||||||
|
final ClassReader reader = new ClassReader(classData);
|
||||||
|
reader.accept(classVisitor, 0);
|
||||||
|
} catch (final IOException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
return lineMap;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,34 @@
|
|||||||
|
package io.papermc.paper.util;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* De-duplicates {@link String} instances without using {@link String#intern()}.
|
||||||
|
*
|
||||||
|
* <p>Interning may not be desired as we may want to use the heap for our pool,
|
||||||
|
* so it can be garbage collected as normal, etc.</p>
|
||||||
|
*
|
||||||
|
* <p>Additionally, interning can be slow due to the potentially large size of the
|
||||||
|
* pool (as it is shared for the entire JVM), and because most JVMs implement
|
||||||
|
* it using JNI.</p>
|
||||||
|
*/
|
||||||
|
@DefaultQualifier(NonNull.class)
|
||||||
|
public final class StringPool {
|
||||||
|
private final Map<String, String> pool;
|
||||||
|
|
||||||
|
public StringPool() {
|
||||||
|
this(new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringPool(final Map<String, String> map) {
|
||||||
|
this.pool = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String string(final String string) {
|
||||||
|
return this.pool.computeIfAbsent(string, Function.identity());
|
||||||
|
}
|
||||||
|
}
|
@@ -130,7 +130,7 @@ public class WatchdogThread extends Thread
|
|||||||
}
|
}
|
||||||
log.log( Level.SEVERE, "\tStack:" );
|
log.log( Level.SEVERE, "\tStack:" );
|
||||||
//
|
//
|
||||||
for ( StackTraceElement stack : thread.getStackTrace() )
|
for ( StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()) ) // Paper
|
||||||
{
|
{
|
||||||
log.log( Level.SEVERE, "\t\t" + stack );
|
log.log( Level.SEVERE, "\t\t" + stack );
|
||||||
}
|
}
|
||||||
|
@@ -30,10 +30,14 @@
|
|||||||
<DefaultRolloverStrategy max="1000"/>
|
<DefaultRolloverStrategy max="1000"/>
|
||||||
</RollingRandomAccessFile>
|
</RollingRandomAccessFile>
|
||||||
<Async name="Async">
|
<Async name="Async">
|
||||||
|
<AppenderRef ref="rewrite"/>
|
||||||
|
</Async>
|
||||||
|
<Rewrite name="rewrite">
|
||||||
|
<StacktraceDeobfuscatingRewritePolicy />
|
||||||
<AppenderRef ref="File"/>
|
<AppenderRef ref="File"/>
|
||||||
<AppenderRef ref="TerminalConsole" level="info"/>
|
<AppenderRef ref="TerminalConsole" level="info"/>
|
||||||
<AppenderRef ref="ServerGuiConsole" level="info"/>
|
<AppenderRef ref="ServerGuiConsole" level="info"/>
|
||||||
</Async>
|
</Rewrite>
|
||||||
</Appenders>
|
</Appenders>
|
||||||
<Loggers>
|
<Loggers>
|
||||||
<Root level="info">
|
<Root level="info">
|
||||||
|
Reference in New Issue
Block a user