Arthas 类查找和反编译原理

系列

  • Arthas入门篇
  • Arthas功能介绍
  • Arthas 启动过程分析
  • Arthas使用Idea调试
  • Arthas Command处理流程
  • Arthas类查找和反编译原理
  • Arthas内存动态编译原理
  • Arthas动态重新加载类
  • Arthas导出加载类

开篇

  • Arthas支持通过类相关的操作命令,包括sc、sm、jad等。
  • sc(Search-Class)命令搜索出所有已经加载到 JVM 中的 Class 信息。
  • sm(Search-Method)命令搜索出所有已经加载了 Class 信息的方法信息。
  • jad命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码。

这篇文章是分析上述类相关操作的底层实现原理包含Instrumentation和CFR。

SC实现原理

public class SearchClassCommand extends AnnotatedCommand {
    public void process(final CommandProcess process) {
        // 1、核心是获取Instrumentation对象
        Instrumentation inst = process.session().getInstrumentation();

        // 2、SearchUtils负责查找对应的class
        List> matchedClasses = new ArrayList>(SearchUtils.searchClass(inst, classPattern, isRegEx, hashCode));

        // 3、对查询结果进行下排序
        Collections.sort(matchedClasses, new Comparator>() {
            @Override
            public int compare(Class c1, Class c2) {
                return StringUtils.classname(c1).compareTo(StringUtils.classname(c2));
            }
        });

        affect.rCnt(matchedClasses.size());
        process.appendResult(new RowAffectModel(affect));
        process.end();
    }
}

public class SearchUtils {

    public static Set> searchClass(Instrumentation inst, Matcher classNameMatcher, int limit) {
        if (classNameMatcher == null) {
            return Collections.emptySet();
        }
        final Set> matches = new HashSet>();
        // 通过inst.getAllLoadedClasses获取所有加载的类
        for (Class clazz : inst.getAllLoadedClasses()) {
            if (classNameMatcher.matching(clazz.getName())) {
                matches.add(clazz);
            }
            if (matches.size() >= limit) {
                break;
            }
        }
        return matches;
    }
  • java Instrumentation指的是可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序。这种监测和协助包括但不限于获取JVM运行时状态,替换和修改类定义等。
  • Arthas的SC命令就是通过Instrumentation的getAllLoadedClasses来实现类的查找。

SM实现原理

public class SearchMethodCommand extends AnnotatedCommand {

    public void process(CommandProcess process) {
        RowAffect affect = new RowAffect();

        Instrumentation inst = process.session().getInstrumentation();
        Matcher methodNameMatcher = methodNameMatcher();
        // 1、通过Instrumentation查找对应的类
        Set> matchedClasses = SearchUtils.searchClass(inst, classPattern, isRegEx, hashCode);

        for (Class clazz : matchedClasses) {
            try {
                // 2、遍历类的构造函数
                for (Constructor constructor : clazz.getDeclaredConstructors()) {
                    if (!methodNameMatcher.matching("")) {
                        continue;
                    }

                    MethodVO methodInfo = ClassUtils.createMethodInfo(constructor, clazz, isDetail);
                    process.appendResult(new SearchMethodModel(methodInfo, isDetail));
                    affect.rCnt(1);
                }
                // 3、遍历所有的方法
                for (Method method : clazz.getDeclaredMethods()) {
                    if (!methodNameMatcher.matching(method.getName())) {
                        continue;
                    }
                    MethodVO methodInfo = ClassUtils.createMethodInfo(method, clazz, isDetail);
                    process.appendResult(new SearchMethodModel(methodInfo, isDetail));
                    affect.rCnt(1);
                }
            } catch (Error e) {
            }
        }

        process.appendResult(new RowAffectModel(affect));
        process.end();
    }
}
  • Arthas的SM命令首先通过Instrumentation的getAllLoadedClasses来实现类的查找。
  • Arthas的SM命令其次通过反射查找对应的方法。

JAD实现原理

public class JadCommand extends AnnotatedCommand {

    public void process(CommandProcess process) {
        RowAffect affect = new RowAffect();
        Instrumentation inst = process.session().getInstrumentation();
        // 1、通过Instrumentation查找对应的类
        Set> matchedClasses = SearchUtils.searchClassOnly(inst, classPattern, isRegEx, code);

        try {
            ExitStatus status = null;
            if (matchedClasses == null || matchedClasses.isEmpty()) {
                status = processNoMatch(process);
            } else if (matchedClasses.size() > 1) {
                status = processMatches(process, matchedClasses);
            } else { // matchedClasses size is 1
                // find inner classes.
                Set> withInnerClasses = SearchUtils.searchClassOnly(inst,  matchedClasses.iterator().next().getName() + "$*", false, code);
                if(withInnerClasses.isEmpty()) {
                    withInnerClasses = matchedClasses;
                }
                // 2、执行类的反编译核心操作
                status = processExactMatch(process, affect, inst, matchedClasses, withInnerClasses);
            }
            if (!this.sourceOnly) {
                process.appendResult(new RowAffectModel(affect));
            }
            CommandUtils.end(process, status);
        } catch (Throwable e){
        }
    }


    private ExitStatus processExactMatch(CommandProcess process, RowAffect affect, Instrumentation inst, Set> matchedClasses, Set> withInnerClasses) {
        Class c = matchedClasses.iterator().next();
        Set> allClasses = new HashSet>(withInnerClasses);
        allClasses.add(c);

        try {
            // 1、创建ClassDumpTransformer对象
            ClassDumpTransformer transformer = new ClassDumpTransformer(allClasses);
            // 2、执行retransformClasses收集待反编译的类文件
            InstrumentationUtils.retransformClasses(inst, transformer, allClasses);

            Map, File> classFiles = transformer.getDumpResult();
            File classFile = classFiles.get(c);
            // 3、执行反编译的动作
            Pair> decompileResult = Decompiler.decompileWithMappings(classFile.getAbsolutePath(), methodName, hideUnicode, lineNumber);

            // 省略无关代码
            return ExitStatus.success();
        } catch (Throwable t) {
        }
    }
}
  • 通过Instrumentation的getAllLoadedClasses来实现类的查找。
  • 创建ClassDumpTransformer并通过retransformClasses保存原始字节码。
  • 通过decompileWithMappings实现字节码的反编译。


class ClassDumpTransformer implements ClassFileTransformer {

    private Set> classesToEnhance;
    private Map, File> dumpResult;
    private File arthasLogHome;

    private File directory;

    public ClassDumpTransformer(Set> classesToEnhance) {
        this(classesToEnhance, null);
    }

    public ClassDumpTransformer(Set> classesToEnhance, File directory) {
        this.classesToEnhance = classesToEnhance;
        this.dumpResult = new HashMap, File>();
        this.arthasLogHome = new File(LogUtil.loggingDir());
        this.directory = directory;
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer)
            throws IllegalClassFormatException {
        if (classesToEnhance.contains(classBeingRedefined)) {
            dumpClassIfNecessary(classBeingRedefined, classfileBuffer);
        }
        return null;
    }

    public Map, File> getDumpResult() {
        return dumpResult;
    }

    private void dumpClassIfNecessary(Class clazz, byte[] data) {
        String className = clazz.getName();
        ClassLoader classLoader = clazz.getClassLoader();
        String classDumpDir = "classdump";

        // 创建类所在的包路径
        File dumpDir = null;
        if (directory != null) {
            dumpDir = directory;
        } else {
            dumpDir = new File(arthasLogHome, classDumpDir);
        }
        if (!dumpDir.mkdirs() && !dumpDir.exists()) {
            logger.warn("create dump directory:{} failed.", dumpDir.getAbsolutePath());
            return;
        }

        String fileName;
        if (classLoader != null) {
            fileName = classLoader.getClass().getName() + "-" + Integer.toHexString(classLoader.hashCode()) +
                    File.separator + className.replace(".", File.separator) + ".class";
        } else {
            fileName = className.replace(".", File.separator) + ".class";
        }

        File dumpClassFile = new File(dumpDir, fileName);

        // 将类字节码写入文件
        try {
            FileUtils.writeByteArrayToFile(dumpClassFile, data);
            dumpResult.put(clazz, dumpClassFile);
        } catch (IOException e) {
        }
    }
}
  • ClassDumpTransformer是自定义的Transformer对象,retransformClasses会进行调用。
  • ClassDumpTransformer的dumpClassIfNecessary负责保存原始的字节码。


public class InstrumentationUtils {

    public static void retransformClasses(Instrumentation inst, ClassFileTransformer transformer,
            Set> classes) {
        try {
            inst.addTransformer(transformer, true);

            for (Class clazz : classes) {
                try {
                    inst.retransformClasses(clazz);
                } catch (Throwable e) {
                    String errorMsg = "retransformClasses class error, name: " + clazz.getName();
                    logger.error(errorMsg, e);
                }
            }
        } finally {
            inst.removeTransformer(transformer);
        }
    }
}
  • InstrumentationUtils负责执行原始字节码的收集,通过retransformClasses来实现。


public class Decompiler {

    public static Pair> decompileWithMappings(String classFilePath,
            String methodName, boolean hideUnicode, boolean printLineNumber) {
        final StringBuilder sb = new StringBuilder(8192);

        final NavigableMap lineMapping = new TreeMap();

        OutputSinkFactory mySink = new OutputSinkFactory() {
            @Override
            public List getSupportedSinks(SinkType sinkType, Collection collection) {
                return Arrays.asList(SinkClass.STRING, SinkClass.DECOMPILED, SinkClass.DECOMPILED_MULTIVER,
                        SinkClass.EXCEPTION_MESSAGE, SinkClass.LINE_NUMBER_MAPPING);
            }

            @Override
            public  Sink getSink(final SinkType sinkType, final SinkClass sinkClass) {
                return new Sink() {
                    @Override
                    public void write(T sinkable) {
                        // skip message like: Analysing type demo.MathGame
                        if (sinkType == SinkType.PROGRESS) {
                            return;
                        }
                        if (sinkType == SinkType.LINENUMBER) {
                            LineNumberMapping mapping = (LineNumberMapping) sinkable;
                            NavigableMap classFileMappings = mapping.getClassFileMappings();
                            NavigableMap mappings = mapping.getMappings();
                            if (classFileMappings != null && mappings != null) {
                                for (Entry entry : mappings.entrySet()) {
                                    Integer srcLineNumber = classFileMappings.get(entry.getKey());
                                    lineMapping.put(entry.getValue(), srcLineNumber);
                                }
                            }
                            return;
                        }
                        sb.append(sinkable);
                    }
                };
            }
        };

        HashMap options = new HashMap();
        options.put("showversion", "false");
        options.put("hideutf", String.valueOf(hideUnicode));
        options.put("trackbytecodeloc", "true");
        if (!StringUtils.isBlank(methodName)) {
            options.put("methodname", methodName);
        }

        CfrDriver driver = new CfrDriver.Builder().withOptions(options).withOutputSink(mySink).build();
        List toAnalyse = new ArrayList();
        toAnalyse.add(classFilePath);
        driver.analyse(toAnalyse);

        String resultCode = sb.toString();
        if (printLineNumber && !lineMapping.isEmpty()) {
            resultCode = addLineNumber(resultCode, lineMapping);
        }

        return Pair.make(resultCode, lineMapping);
    }
}
  • 通过CFR-api实现字节码的反编译。

总结

  • 类的查询或方法的查找基于Instrumentation来实现的。
  • 类反编译是基于CFR来实现的,提供CFR-api实现。
  • 认识 JavaAgent --获取目标进程已加载的所有类

你可能感兴趣的:(Arthas 类查找和反编译原理)