From c96074562d8e7d0fa27ff7c64891202be9934dfc Mon Sep 17 00:00:00 2001 From: Alexander Xia Date: Tue, 20 Aug 2019 21:16:46 +0800 Subject: [PATCH] Add analyze some crash automaticly --- README.md | 21 +++++- build.gradle | 4 +- .../me/xfl03/morecrashinfo/MoreCrashInfo.java | 18 +++++ .../morecrashinfo/handler/CrashHandler.java | 34 +++++++++ .../handler/ExceptionHandler.java | 15 ++++ .../handler/exception/VerifyErrorHandler.java | 70 +++++++++++++++++++ .../xfl03/morecrashinfo/util/CrashMaker.java | 11 +++ .../xfl03/morecrashinfo/util/ModHelper.java | 49 +++++++++++-- src/main/resources/META-INF/coremods.json | 3 + src/main/resources/META-INF/mods.toml | 6 +- src/main/resources/crashtransformers.js | 52 ++++++++++++++ 11 files changed, 270 insertions(+), 13 deletions(-) create mode 100644 src/main/java/me/xfl03/morecrashinfo/handler/CrashHandler.java create mode 100644 src/main/java/me/xfl03/morecrashinfo/handler/ExceptionHandler.java create mode 100644 src/main/java/me/xfl03/morecrashinfo/handler/exception/VerifyErrorHandler.java create mode 100644 src/main/java/me/xfl03/morecrashinfo/util/CrashMaker.java create mode 100644 src/main/resources/META-INF/coremods.json create mode 100644 src/main/resources/crashtransformers.js diff --git a/README.md b/README.md index 9b54f57..a6e437f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Display more info in crash report. ## Why make it? Forge 1.13.2 - 1.14.3 removed mod list and coremod list in crash report, while users need them to analyze what caused crash. Forge 1.14.4 added mod list back, but it is not formatted. It's hard to read when plenty of mods loaded. Meanwhile, coremods list is still not shown. -Mod list added in Forge 1.14.4 crash report can be shown below: +Mod list added in Forge 1.14.4 crash report: ``` Mod List: CustomSkinLoader_Forge-14.11-SNAPSHOT-89.jar CustomSkinLoader {customskinloader@14.11-SNAPSHOT-89 DONE} @@ -13,7 +13,7 @@ Mod List: ## What added? This mod is designed to take mod list and coremod list back with pretty printing. Feel free to open an issue if other info needed. -What added in crash report is shown below: +What added in crash report: ``` Forge Mods: | ID | Name | Version | Source | Status | @@ -28,3 +28,20 @@ Forge CoreMods: | customskinloader | transformers | transformers.js | Loaded | | forge | fieldtomethodtransformers | fieldtomethodtransformers.js | Loaded | ``` +## What's more? +We are trying to analyze some crash automaticly. +Now, we can help you to solve `java.lang.VerifyError`. +You can open an issue to submit crash report, I will try my best to find the way to solve. +What added for some crash: +``` +Possible Reason: + Bytecode in class 'me.xfl03.morecrashinfo.util.CrashMaker' failed to verify. + CoreMod 'MoreCrashInfo' modified that class, which may cause crash. +Possible Solution: + Please remove or update 'MoreCrashInfo(MoreCrashInfo-1.0.3.jar)' and try again. + +Error Info: + Class: me.xfl03.morecrashinfo.util.CrashMaker + Owner: MoreCrashInfo + Audit: xf:fml:morecrashinfo:CrashMakerTransformer +``` \ No newline at end of file diff --git a/build.gradle b/build.gradle index 99829e7..bb76ce0 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { } apply plugin: 'net.minecraftforge.gradle' -version = '1.0.2' +version = '1.0.3' group = 'me.xfl03' archivesBaseName = 'MoreCrashInfo' @@ -47,7 +47,7 @@ minecraft { workingDirectory project.file('run') property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' property 'forge.logging.console.level', 'debug' - args '--mod', 'examplemod', '--all', '--output', file('src/generated/resources/') + args '--mod', 'morecrashinfo', '--all', '--output', file('src/generated/resources/') mods { examplemod { diff --git a/src/main/java/me/xfl03/morecrashinfo/MoreCrashInfo.java b/src/main/java/me/xfl03/morecrashinfo/MoreCrashInfo.java index 228df0f..2bf98f8 100644 --- a/src/main/java/me/xfl03/morecrashinfo/MoreCrashInfo.java +++ b/src/main/java/me/xfl03/morecrashinfo/MoreCrashInfo.java @@ -2,9 +2,15 @@ import me.xfl03.morecrashinfo.crash.CoreModList; import me.xfl03.morecrashinfo.crash.ModList; +import me.xfl03.morecrashinfo.handler.CrashHandler; +import me.xfl03.morecrashinfo.handler.exception.VerifyErrorHandler; +import me.xfl03.morecrashinfo.util.CrashMaker; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.CrashReportExtender; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -16,6 +22,7 @@ public class MoreCrashInfo public MoreCrashInfo() { FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup); + MinecraftForge.EVENT_BUS.register(this); } private void setup(final FMLCommonSetupEvent event) @@ -25,6 +32,17 @@ private void setup(final FMLCommonSetupEvent event) CrashReportExtender.registerCrashCallable(new ModList()); CrashReportExtender.registerCrashCallable(new CoreModList()); + CrashHandler.registerHandler(VerifyError.class, VerifyErrorHandler::new); + LOGGER.debug("MoreCrashInfo: PreInit End."); } + + @SubscribeEvent + public void onServerStarting(FMLServerStartingEvent event) { + // Uncomment to cause java.lang.RuntimeException + //CrashMaker.makeCrash(); + + // Uncomment in crashtransformers.js can cause special crash + CrashMaker.doNothing(); + } } diff --git a/src/main/java/me/xfl03/morecrashinfo/handler/CrashHandler.java b/src/main/java/me/xfl03/morecrashinfo/handler/CrashHandler.java new file mode 100644 index 0000000..fd18a87 --- /dev/null +++ b/src/main/java/me/xfl03/morecrashinfo/handler/CrashHandler.java @@ -0,0 +1,34 @@ +package me.xfl03.morecrashinfo.handler; + +import cpw.mods.modlauncher.log.TransformingThrowablePatternConverter; +import me.xfl03.morecrashinfo.handler.exception.*; +import net.minecraft.crash.CrashReport; + +import java.util.*; +import java.util.function.Function; + +public class CrashHandler { + + private static Map> handlers = new HashMap<>(); + private static ExceptionHandler handler; + + public static void registerHandler(Class exception, Function handler) { + handlers.put(exception, handler); + } + + // net.minecraftforge.fml.CrashReportExtender.addCrashReportHeader + public static void addCrashReportHeader(StringBuilder stringbuilder, CrashReport crashReport) { + Throwable cause = crashReport.getCrashCause(); + handler = Optional.ofNullable(handlers.get(cause.getClass())) + .orElse(ExceptionHandler::new).apply(cause); + handler.handleHeader(stringbuilder); + } + + // net.minecraftforge.fml.CrashReportExtender.generateEnhancedStackTrace + public static String generateEnhancedStackTrace(final Throwable throwable) { + StringBuilder stringbuilder = new StringBuilder(); + handler.handleException(stringbuilder); + stringbuilder.append(TransformingThrowablePatternConverter.generateEnhancedStackTrace(throwable)); + return stringbuilder.toString(); + } +} diff --git a/src/main/java/me/xfl03/morecrashinfo/handler/ExceptionHandler.java b/src/main/java/me/xfl03/morecrashinfo/handler/ExceptionHandler.java new file mode 100644 index 0000000..51f16f2 --- /dev/null +++ b/src/main/java/me/xfl03/morecrashinfo/handler/ExceptionHandler.java @@ -0,0 +1,15 @@ +package me.xfl03.morecrashinfo.handler; + +public class ExceptionHandler { + protected Throwable cause; + + public ExceptionHandler(Throwable cause) { + this.cause = cause; + } + + public void handleHeader(StringBuilder sb) { + } + + public void handleException(StringBuilder sb) { + } +} diff --git a/src/main/java/me/xfl03/morecrashinfo/handler/exception/VerifyErrorHandler.java b/src/main/java/me/xfl03/morecrashinfo/handler/exception/VerifyErrorHandler.java new file mode 100644 index 0000000..75a4c4e --- /dev/null +++ b/src/main/java/me/xfl03/morecrashinfo/handler/exception/VerifyErrorHandler.java @@ -0,0 +1,70 @@ +package me.xfl03.morecrashinfo.handler.exception; + +import me.xfl03.morecrashinfo.handler.ExceptionHandler; +import me.xfl03.morecrashinfo.util.ModHelper; +import net.minecraftforge.fml.ModContainer; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.moddiscovery.ModInfo; +import net.minecraftforge.forgespi.language.IModInfo; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class VerifyErrorHandler extends ExceptionHandler { + private String className; + private List transformers; + private Optional owner; + + public VerifyErrorHandler(Throwable cause) { + super(cause); + className = getClass(cause.getMessage()); + transformers = ModHelper.getTransformers(className); + owner = ModHelper.getModByClass(className); + } + + @Override + public void handleHeader(StringBuilder sb) { + sb.append("Possible Reason:\n\tBytecode in class '").append(className).append("' failed to verify. ") + .append(getReason()).append("\n\n"); + } + + private String getReason() { + if (!transformers.isEmpty()) { + + return String.format( + "\n\tCoreMod '%s' modified that class, which may cause crash.\n" + + "Possible Solution:\n\tPlease remove or update %s'%s' and try again.", + transformers.stream().map(it -> it.getModInfo().getDisplayName()) + .collect(Collectors.joining(",")), + transformers.size() > 1 ? "one of " : "", + transformers.stream().map(it -> it.getModInfo().getDisplayName() + + "(" + ModHelper.getSource(it.getModInfo()) + ")") + .collect(Collectors.joining(",")) + ); + } else if (owner.isPresent()) { + IModInfo info = owner.get().getModInfo(); + return String.format( + "\n\tMod '%s' might has been broken.\n" + + "Possible Solution:\n\tPlease remove or update '%s' and try again.", + info.getDisplayName(), + info.getDisplayName() + "(" + ModHelper.getSource(info) + ")" + ); + } + return ""; + } + + @Override + public void handleException(StringBuilder sb) { + sb.append("Error Info:\n\tClass: ").append(className) + .append("\n\tOwner: ").append(owner.map(it -> it.getModInfo().getDisplayName()).orElse("Unknown")) + .append("\n\tAudit: ").append(ModHelper.getAuditLine(className)).append("\n\n"); + } + + private String getClass(String message) { + String[] t = message.split("Location:"); + if (t.length < 2) return null; + t = t[1].split("\\."); + return t[0].trim().replace('/', '.'); + } +} diff --git a/src/main/java/me/xfl03/morecrashinfo/util/CrashMaker.java b/src/main/java/me/xfl03/morecrashinfo/util/CrashMaker.java new file mode 100644 index 0000000..8c68076 --- /dev/null +++ b/src/main/java/me/xfl03/morecrashinfo/util/CrashMaker.java @@ -0,0 +1,11 @@ +package me.xfl03.morecrashinfo.util; + +public class CrashMaker { + public static void makeCrash() { + throw new RuntimeException(new RuntimeException(new RuntimeException("Crashed for test."))); + } + + public static void doNothing() { + + } +} diff --git a/src/main/java/me/xfl03/morecrashinfo/util/ModHelper.java b/src/main/java/me/xfl03/morecrashinfo/util/ModHelper.java index 7de0af6..157693a 100644 --- a/src/main/java/me/xfl03/morecrashinfo/util/ModHelper.java +++ b/src/main/java/me/xfl03/morecrashinfo/util/ModHelper.java @@ -1,19 +1,23 @@ package me.xfl03.morecrashinfo.util; +import cpw.mods.modlauncher.Launcher; +import cpw.mods.modlauncher.api.IEnvironment; import net.minecraftforge.coremod.CoreMod; import net.minecraftforge.coremod.CoreModEngine; import net.minecraftforge.coremod.CoreModProvider; import net.minecraftforge.fml.ModContainer; import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.fml.loading.moddiscovery.CoreModFile; import net.minecraftforge.fml.loading.moddiscovery.ModInfo; import net.minecraftforge.forgespi.coremod.ICoreModFile; import net.minecraftforge.forgespi.coremod.ICoreModProvider; +import net.minecraftforge.forgespi.language.IModInfo; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.net.URL; +import java.util.*; +import java.util.stream.Collectors; public class ModHelper { public static List getCoreModList() throws Exception { @@ -27,6 +31,11 @@ public static List getCoreModList() throws Exception { return list; } + public static String getSource(IModInfo it) { + if (it instanceof ModInfo) return getSource((ModInfo) it); + return "Not Found"; + } + public static String getSource(ModInfo it) { if (it.getOwningFile() == null || it.getOwningFile().getFile() == null) return "Not Found"; return it.getOwningFile().getFile().getFileName(); @@ -47,9 +56,37 @@ public static String getName(ICoreModFile file) { return name; } + public static Optional getModContainer(String modId) { + return ModList.get().getModContainerById(modId); + } + public static String getStatus(String modId) { - return ModList.get() - .getModContainerById(modId).map(ModContainer::getCurrentState).map(Objects::toString) - .orElse("NONE"); + return getModContainer(modId) + .map(ModContainer::getCurrentState).map(Objects::toString).orElse("NONE"); + } + + public static String getAuditLine(String className) { + String name = className.replace('/', '.'); + return Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.AUDITTRAIL.get()) + .map(it -> it.getAuditString(name)).orElse(""); + } + + public static List getTransformers(String className) { + return Arrays.stream( + getAuditLine(className).split(",")) + .filter(it -> it.startsWith("xf:")) + .map(it -> it.split(":")) + .filter(it -> it.length > 2) + .map(it -> it[1].equals("fml") ? it[2] : it[1]) + .map(ModHelper::getModContainer) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } + + public static Optional getModByClass(String className) { + String path = className.replace('.', '/') + ".class"; + URL url = ModHelper.class.getClassLoader().getResource(path); + return url == null ? Optional.empty() : getModContainer(url.getHost()); } } diff --git a/src/main/resources/META-INF/coremods.json b/src/main/resources/META-INF/coremods.json new file mode 100644 index 0000000..697078e --- /dev/null +++ b/src/main/resources/META-INF/coremods.json @@ -0,0 +1,3 @@ +{ + "crashtransformers": "crashtransformers.js" +} \ No newline at end of file diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 40575c3..8bd94ec 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -3,9 +3,9 @@ loaderVersion="[25,)" issueTrackerURL="http://github.com/xfl03/MoreCrashInfo/issues" [[mods]] modId="morecrashinfo" -version="1.0.2" +version="1.0.3" displayName="MoreCrashInfo" -displayURL="http://github.com/xfl03/MoreCrashInfo" #optional +displayURL="http://github.com/xfl03/MoreCrashInfo" credits="" authors="xfl03" description=''' @@ -14,7 +14,7 @@ Display more info in crash report. [[dependencies.morecrashinfo]] modId="forge" mandatory=true - versionRange="[25,)" + versionRange="[25.0.216,)" ordering="NONE" side="BOTH" [[dependencies.morecrashinfo]] diff --git a/src/main/resources/crashtransformers.js b/src/main/resources/crashtransformers.js new file mode 100644 index 0000000..5d3baf5 --- /dev/null +++ b/src/main/resources/crashtransformers.js @@ -0,0 +1,52 @@ +var InsnNode = Java.type('org.objectweb.asm.tree.InsnNode'); +var MethodInsnNode = Java.type('org.objectweb.asm.tree.MethodInsnNode'); +var VarInsnNode = Java.type('org.objectweb.asm.tree.VarInsnNode'); + +var Opcodes = Java.type('org.objectweb.asm.Opcodes'); + +function initializeCoreMod() { + return { + 'CrashReportExtenderTransformer': { + 'target': { + 'type': 'CLASS', + 'name': 'net/minecraftforge/fml/CrashReportExtender' + }, + 'transformer': function (cn) { + cn.methods.forEach(function (mn) { + if (mn.name === 'addCrashReportHeader') { + mn.instructions.clear(); + mn.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + mn.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + mn.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, + "me/xfl03/morecrashinfo/handler/CrashHandler", + "addCrashReportHeader", + "(Ljava/lang/StringBuilder;Lnet/minecraft/crash/CrashReport;)V", false)); + mn.instructions.add(new InsnNode(Opcodes.RETURN)); + } else if (mn.name === 'generateEnhancedStackTrace') { + mn.instructions.clear(); + mn.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + mn.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, + "me/xfl03/morecrashinfo/handler/CrashHandler", + "generateEnhancedStackTrace", + "(Ljava/lang/Throwable;)Ljava/lang/String;", false)); + mn.instructions.add(new InsnNode(Opcodes.ARETURN)); + } + }); + return cn; + } + }, + 'CrashMakerTransformer': { + 'target': { + 'type': 'CLASS', + 'name': 'me/xfl03/morecrashinfo/util/CrashMaker' + }, + 'transformer': function (cn) { + // Uncomment to cause java.lang.ClassFormatError + //cn.methods.add(cn.methods.get(0)); + + //Uncomment to cause java.lang.VerifyError + //cn.methods.forEach(function (mn) { mn.instructions.clear(); mn.instructions.add(new InsnNode(Opcodes.ARETURN)); }); + } + } + } +} \ No newline at end of file