Support intercepting logs from authlib

This commit is contained in:
yushijinhun 2018-11-24 17:11:07 +08:00
parent 1d73a05413
commit d6cf905a38
No known key found for this signature in database
GPG key ID: 5BC167F73EA558E4
4 changed files with 239 additions and 3 deletions

View file

@ -39,6 +39,7 @@ gradle
* `transform.skipped` 分析了却未执行字节码修改的类(用于性能分析)
* `config` 有关配置获取的
* `httpd` 有关本地 HTTP 服务器的(其负责在本地处理掉部分请求,而不是发送到 Yggdrasil 服务端)
* `authlib` 打印从 authlib 处获取的日志(其记录了网络调用的详细信息)
可以指定多个类型,中间用 `,` 分隔。如果要打印以上所有调试信息,可以设置其为 `all`

View file

@ -18,6 +18,8 @@ import java.util.Base64;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import moe.yushi.authlibinjector.transform.AuthlibLogInterceptor;
import moe.yushi.authlibinjector.transform.ClassTransformer;
import moe.yushi.authlibinjector.transform.DumpClassListener;
import moe.yushi.authlibinjector.transform.SkinWhitelistTransformUnit;
@ -271,6 +273,10 @@ public final class AuthlibInjector {
transformer.listeners.add(new DumpClassListener(Paths.get("").toAbsolutePath()));
}
if (Logging.isDebugOnFor(Logging.PREFIX + ".authlib")) {
transformer.units.add(new AuthlibLogInterceptor());
}
if (!"true".equals(System.getProperty(PROP_DISABLE_HTTPD))) {
transformer.units.add(new LocalYggdrasilApiTransformUnit(config));
}

View file

@ -0,0 +1,223 @@
package moe.yushi.authlibinjector.transform;
import static org.objectweb.asm.Opcodes.ASM6;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.stream.Stream;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import moe.yushi.authlibinjector.util.Logging;
public class AuthlibLogInterceptor implements TransformUnit {
private static Set<ClassLoader> interceptedClassloaders = Collections.newSetFromMap(new WeakHashMap<>());
public static void onClassLoading(ClassLoader classLoader) {
Class<?> classLogManager;
try {
classLogManager = classLoader.loadClass("org.apache.logging.log4j.LogManager");
} catch (ClassNotFoundException e) {
return;
}
ClassLoader clLog4j = classLogManager.getClassLoader();
synchronized (interceptedClassloaders) {
if (!interceptedClassloaders.add(clLog4j)) {
return;
}
}
try {
registerLogHandle(clLog4j);
Logging.TRANSFORM.info("Registered log handler on " + clLog4j);
} catch (Throwable e) {
Logging.TRANSFORM.log(Level.WARNING, "Failed to register log handler on " + clLog4j, e);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static void registerLogHandle(ClassLoader cl) throws ReflectiveOperationException {
final String appenderName = "AUTHLIB_INJECTOR_CONSOLE_APPENDER";
final String loggerName = "AUTHLIB_INJECTOR_AUTHLIB_LOGGER";
final String authlibPackageName = "com.mojang.authlib";
Class<?> classLayout = cl.loadClass("org.apache.logging.log4j.core.Layout");
Class<?> classAppender = cl.loadClass("org.apache.logging.log4j.core.Appender");
Class<?> classAppenderRef = cl.loadClass("org.apache.logging.log4j.core.config.AppenderRef");
Class<?> classLevel = cl.loadClass("org.apache.logging.log4j.Level");
Class<?> classFilter = cl.loadClass("org.apache.logging.log4j.core.Filter");
Class<?> classLoggerConfig = cl.loadClass("org.apache.logging.log4j.core.config.LoggerConfig");
Class<?> classConfiguration = cl.loadClass("org.apache.logging.log4j.core.config.Configuration");
Class<?> classPatternLayout = cl.loadClass("org.apache.logging.log4j.core.layout.PatternLayout");
Class<?> classConsoleAppender = cl.loadClass("org.apache.logging.log4j.core.appender.ConsoleAppender");
Object loggerContext = cl.loadClass("org.apache.logging.log4j.LogManager").getDeclaredMethod("getContext", boolean.class).invoke(null, false);
Object configuration = cl.loadClass("org.apache.logging.log4j.core.LoggerContext").getMethod("getConfiguration").invoke(loggerContext);
Object patternLayout;
try {
Object builder = classPatternLayout.getDeclaredMethod("newBuilder").invoke(null);
Class<?> classBuilder = cl.loadClass("org.apache.logging.log4j.core.layout.PatternLayout$Builder");
patternLayout = classBuilder.getMethod("build").invoke(builder);
} catch (NoSuchMethodException ex) {
// prior to https://github.com/apache/logging-log4j2/commit/7aabb11111c69f452d674b00845b66b82c80afa4
Map<String, Object> values = new HashMap<>();
values.put("alwaysWriteExceptions", true);
values.put("noConsoleNoAnsi", true);
patternLayout = invokeCreateMethod(classPatternLayout, "createLayout", configuration, values);
}
Object appender;
try {
Object buider = classConsoleAppender.getDeclaredMethod("newBuilder").invoke(null);
Class<?> classBuilder = cl.loadClass("org.apache.logging.log4j.core.appender.ConsoleAppender$Builder");
classBuilder.getMethod("withLayout", classLayout).invoke(buider, patternLayout);
classBuilder.getMethod("withName", String.class).invoke(buider, appenderName);
appender = classBuilder.getMethod("build").invoke(buider);
} catch (NoSuchMethodException ex) {
Map<String, Object> values = new HashMap<>();
values.put("Layout", patternLayout);
values.put("name", appenderName);
values.put("follow", false);
values.put("direct", false);
values.put("ignoreExceptions", true);
appender = invokeCreateMethod(classConsoleAppender, "createAppender", configuration, values);
}
classAppender.getMethod("start").invoke(appender);
Object appenderRef;
{
Map<String, Object> values = new HashMap<>();
values.put("ref", appenderName);
appenderRef = invokeCreateMethod(classAppenderRef, "createAppenderRef", configuration, values);
}
Object appenderRefs = Array.newInstance(classAppenderRef, 1);
Array.set(appenderRefs, 0, appenderRef);
Object loggerConfig;
{
Map<String, Object> values = new HashMap<>();
values.put("additivity", true);
values.put("level", classLevel.getDeclaredField("ALL").get(null));
values.put("name", loggerName);
values.put("includeLocation", authlibPackageName);
values.put("AppenderRef", appenderRefs);
loggerConfig = invokeCreateMethod(classLoggerConfig, "createLogger", configuration, values);
}
classLoggerConfig.getMethod("addAppender", classAppender, classLevel, classFilter).invoke(loggerConfig, appender, null, null);
try {
classConfiguration.getMethod("addAppender", classAppender).invoke(configuration, appender);
} catch (NoSuchMethodException e) {
((Map) classConfiguration.getMethod("getAppenders").invoke(configuration)).put(appenderName, appender);
}
try {
classConfiguration.getMethod("addLogger", String.class, classLoggerConfig).invoke(configuration, authlibPackageName, loggerConfig);
} catch (NoSuchMethodException e) {
// prior to https://github.com/apache/logging-log4j2/commit/f9744033b93e2fe1f52a27c66f24f53cf44939a2
// bypass the check in https://github.com/apache/logging-log4j2/blob/log4j-2.0-beta9/log4j-core/src/main/java/org/apache/logging/log4j/core/config/BaseConfiguration.java#L554
Class<?> classBaseConfiguration = cl.loadClass("org.apache.logging.log4j.core.config.BaseConfiguration");
Field fieldLoggers = classBaseConfiguration.getDeclaredField("loggers");
fieldLoggers.setAccessible(true);
((Map) fieldLoggers.get(configuration)).put(authlibPackageName, loggerConfig);
Method methodSetParents = classBaseConfiguration.getDeclaredMethod("setParents");
methodSetParents.setAccessible(true);
methodSetParents.invoke(configuration);
}
cl.loadClass("org.apache.logging.log4j.core.LoggerContext").getMethod("updateLoggers", classConfiguration).invoke(loggerContext, configuration);
}
private static Object invokeCreateMethod(Class<?> owner, String methodName, Object config, Map<String, Object> values) throws ReflectiveOperationException {
ClassLoader cl = owner.getClassLoader();
Class<?> classConfiguration = cl.loadClass("org.apache.logging.log4j.core.config.Configuration");
@SuppressWarnings("unchecked")
Class<? extends Annotation> classPluginAttribute = (Class<? extends Annotation>) cl.loadClass("org.apache.logging.log4j.core.config.plugins.PluginAttribute");
Method methodPluginAttributeValue = classPluginAttribute.getMethod("value");
@SuppressWarnings("unchecked")
Class<? extends Annotation> classPluginElement = (Class<? extends Annotation>) cl.loadClass("org.apache.logging.log4j.core.config.plugins.PluginElement");
Method methodPluginElementValue = classPluginElement.getMethod("value");
@SuppressWarnings("unchecked")
Class<? extends Annotation> classPluginFactory = (Class<? extends Annotation>) cl.loadClass("org.apache.logging.log4j.core.config.plugins.PluginFactory");
Method method = Stream.of(owner.getDeclaredMethods())
.filter(it -> it.getName().equals(methodName))
.filter(it -> it.getDeclaredAnnotation(classPluginFactory) != null)
.findFirst().orElseThrow(NoSuchMethodException::new);
Object[] input = new Object[method.getParameterCount()];
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Class<?> type = parameters[i].getType();
String key = null;
Annotation annotation = parameters[i].getDeclaredAnnotation(classPluginAttribute);
if (annotation != null) {
key = (String) methodPluginAttributeValue.invoke(annotation);
} else {
annotation = parameters[i].getDeclaredAnnotation(classPluginElement);
if (annotation != null) {
key = (String) methodPluginElementValue.invoke(annotation);
}
}
if (key == null) {
if (classConfiguration.isAssignableFrom(type)) {
input[i] = config;
}
} else {
Object value = values.get(key);
if (value != null) {
if (type.isPrimitive() || type.isInstance(value)) {
input[i] = value;
} else if (type == String.class) {
input[i] = value.toString();
}
}
}
}
return method.invoke(null, input);
}
@Override
public Optional<ClassVisitor> transform(String className, ClassVisitor writer, Runnable modifiedCallback) {
if (className.startsWith("com.mojang.authlib.")) {
return Optional.of(new ClassVisitor(ASM6, writer) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if ("<clinit>".equals(name)) {
modifiedCallback.run();
mv.visitLdcInsn(Type.getType("L" + className.replace('.', '/') + ";"));
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false);
mv.visitMethodInsn(INVOKESTATIC, AuthlibLogInterceptor.class.getName().replace('.', '/'), "onClassLoading", "(Ljava/lang/ClassLoader;)V", false);
}
return mv;
}
});
}
return Optional.empty();
}
@Override
public String toString() {
return "Authlib Log Interceptor";
}
}

View file

@ -17,7 +17,7 @@ import java.util.logging.StreamHandler;
public final class Logging {
private Logging() {}
private static final String PREFIX = "moe.yushi.authlibinjector";
public static final String PREFIX = "moe.yushi.authlibinjector";
public static final Logger ROOT = Logger.getLogger(PREFIX);
public static final Logger LAUNCH = Logger.getLogger(PREFIX + ".launch");
@ -26,7 +26,11 @@ public final class Logging {
public static final Logger TRANSFORM_SKIPPED = Logger.getLogger(PREFIX + ".transform.skipped");
public static final Logger HTTPD = Logger.getLogger(PREFIX + ".httpd");
private static Predicate<String> debugLoggerNamePredicate;
public static void init() {
debugLoggerNamePredicate = createDebugLoggerNamePredicate();
initRootLogger();
}
@ -90,8 +94,10 @@ public final class Logging {
}
private static Filter createFilter() {
Predicate<String> namePredicate = createDebugLoggerNamePredicate();
return log -> log.getLevel().intValue() >= Level.INFO.intValue() || namePredicate.test(log.getLoggerName());
return log -> log.getLevel().intValue() >= Level.INFO.intValue() || isDebugOnFor(log.getLoggerName());
}
public static boolean isDebugOnFor(String loggerName) {
return debugLoggerNamePredicate.test(loggerName);
}
}