aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0015-Deobfuscate-stacktraces-in-log-messages-crash-report.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server/0015-Deobfuscate-stacktraces-in-log-messages-crash-report.patch')
-rw-r--r--patches/server/0015-Deobfuscate-stacktraces-in-log-messages-crash-report.patch581
1 files changed, 581 insertions, 0 deletions
diff --git a/patches/server/0015-Deobfuscate-stacktraces-in-log-messages-crash-report.patch b/patches/server/0015-Deobfuscate-stacktraces-in-log-messages-crash-report.patch
new file mode 100644
index 0000000000..7e2c824c7e
--- /dev/null
+++ b/patches/server/0015-Deobfuscate-stacktraces-in-log-messages-crash-report.patch
@@ -0,0 +1,581 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Sun, 20 Jun 2021 18:19:09 -0700
+Subject: [PATCH] Deobfuscate stacktraces in log messages, crash reports, and
+ etc.
+
+
+diff --git a/build.gradle.kts b/build.gradle.kts
+index 3bd5c2a2add9b462523beb9dfaf2eb5a00d470b9..a2bb659ae3a502d4c181d1ccbd15eefc38e4823b 100644
+--- a/build.gradle.kts
++++ b/build.gradle.kts
+@@ -45,6 +45,7 @@ dependencies {
+ testImplementation("org.mockito:mockito-core:5.11.0")
+ testImplementation("org.ow2.asm:asm-tree:9.7")
+ testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest
++ implementation("net.neoforged:srgutils:1.0.9") // Paper - mappings handling
+ }
+
+ paperweight {
+diff --git a/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java b/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..66b6011ee3684695b2ab9292961c80bf2a420ee9
+--- /dev/null
++++ b/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java
+@@ -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();
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/util/ObfHelper.java b/src/main/java/io/papermc/paper/util/ObfHelper.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..9e6d48335b37fa5204bfebf396d748089884555b
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/util/ObfHelper.java
+@@ -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
++ ) {}
++}
+diff --git a/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..242811578a786e3807a1a7019d472d5a68f87116
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java
+@@ -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;
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/util/StringPool.java b/src/main/java/io/papermc/paper/util/StringPool.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..c0a486cb46ff30353c3ff09567891cd36238eeb4
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/util/StringPool.java
+@@ -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());
++ }
++}
+diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java
+index 1938ae691dafec1fc1e5a68792d1191bd52b4e5c..268310642181a715815d3b2d1c0f090e6252971a 100644
+--- a/src/main/java/net/minecraft/CrashReport.java
++++ b/src/main/java/net/minecraft/CrashReport.java
+@@ -34,6 +34,7 @@ public class CrashReport {
+ private final SystemReport systemReport = new SystemReport();
+
+ public CrashReport(String message, Throwable cause) {
++ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper
+ this.title = message;
+ this.exception = cause;
+ this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit
+diff --git a/src/main/java/net/minecraft/CrashReportCategory.java b/src/main/java/net/minecraft/CrashReportCategory.java
+index f367ba058018074bfe6e4fe88bcc875ea9794d9e..2176171954609fd88f97f93408e14e018c1d6eaa 100644
+--- a/src/main/java/net/minecraft/CrashReportCategory.java
++++ b/src/main/java/net/minecraft/CrashReportCategory.java
+@@ -110,6 +110,7 @@ public class CrashReportCategory {
+ } 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;
+ }
+ }
+diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
+index 77985072928a1b892fb4f7dec1d0899324780082..f5e6610d271ef2c997fb3d1a5f65e0bf0740805a 100644
+--- a/src/main/java/net/minecraft/network/Connection.java
++++ b/src/main/java/net/minecraft/network/Connection.java
+@@ -82,13 +82,13 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+ 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;
+@@ -197,7 +197,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+
+ }
+ }
+- 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) {
+diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+index b41eb920b5665b7a1b7cd9f38955c31eeb350847..bb59986c211f7d6ea50b1ad4bd5565227bec8a6c 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -210,6 +210,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings"));
+ org.spigotmc.SpigotConfig.registerCommands();
+ // 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
+ this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess());
+ this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
+diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
+index 987360a266a5a870f06929b00c9f451901188fd6..2cf3e79ec5e8706b71d27ebad4668773f0b91195 100644
+--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
+@@ -52,10 +52,10 @@ public class ServerConnectionListener {
+
+ 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;
+diff --git a/src/main/java/net/minecraft/server/players/OldUsersConverter.java b/src/main/java/net/minecraft/server/players/OldUsersConverter.java
+index 8d06e8d286da2573e40794adab695ff77e5afd86..68551947f5b7d3471f15bd74ccd86519ab34c1c1 100644
+--- a/src/main/java/net/minecraft/server/players/OldUsersConverter.java
++++ b/src/main/java/net/minecraft/server/players/OldUsersConverter.java
+@@ -356,7 +356,7 @@ public class OldUsersConverter {
+ try {
+ root = NbtIo.readCompressed(new java.io.FileInputStream(file5), NbtAccounter.unlimitedHeap());
+ } catch (Exception exception) {
+- exception.printStackTrace();
++ io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Paper
+ }
+
+ if (root != null) {
+@@ -369,7 +369,7 @@ public class OldUsersConverter {
+ try {
+ NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2));
+ } catch (Exception exception) {
+- exception.printStackTrace();
++ io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Paper
+ }
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
+index c4bf7053d83d207caca0e13e19f5c1afa7062de3..f697d45e0ac4e9cdc8a46121510a04c0f294d91f 100644
+--- a/src/main/java/org/spigotmc/WatchdogThread.java
++++ b/src/main/java/org/spigotmc/WatchdogThread.java
+@@ -130,7 +130,7 @@ public class WatchdogThread extends Thread
+ }
+ 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 );
+ }
+diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
+index 18e961a37b2830da6e5dab7aa35116b2f5215898..128fa1376f22d3429a23d79a2772abf2e7fec2bc 100644
+--- a/src/main/resources/log4j2.xml
++++ b/src/main/resources/log4j2.xml
+@@ -30,10 +30,14 @@
+ <DefaultRolloverStrategy max="1000"/>
+ </RollingRandomAccessFile>
+ <Async name="Async">
++ <AppenderRef ref="rewrite"/>
++ </Async>
++ <Rewrite name="rewrite">
++ <StacktraceDeobfuscatingRewritePolicy />
+ <AppenderRef ref="File"/>
+ <AppenderRef ref="TerminalConsole" level="info"/>
+ <AppenderRef ref="ServerGuiConsole" level="info"/>
+- </Async>
++ </Rewrite>
+ </Appenders>
+ <Loggers>
+ <Root level="info">