ArthasArthas 类查找和反编译原理

Posted 九师兄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ArthasArthas 类查找和反编译原理相关的知识,希望对你有一定的参考价值。

1.概述

转载:Arthas 类查找和反编译原理

2.开篇

Arthas支持通过类相关的操作命令,包括sc、sm、jad等。

sc(Search-Class)命令搜索出所有已经加载到 JVM 中的 Class 信息。

sm(Search-Method)命令搜索出所有已经加载了 Class 信息的方法信息。

jad命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码。

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

3.SC实现原理

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

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

        // 3、对查询结果进行下排序
        Collections.sort(matchedClasses, new Comparator<Class<?>>() 
            @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<Class<?>> searchClass(Instrumentation inst, Matcher<String> classNameMatcher, int limit) 
        if (classNameMatcher == null) 
            return Collections.emptySet();
        
        final Set<Class<?>> matches = new HashSet<Class<?>>();
        // 通过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来实现类的查找。

4. SM实现原理

public class SearchMethodCommand extends AnnotatedCommand 

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

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

        for (Class<?> clazz : matchedClasses) 
            try 
                // 2、遍历类的构造函数
                for (Constructor<?> constructor : clazz.getDeclaredConstructors()) 
                    if (!methodNameMatcher.matching("<init>")) 
                        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命令其次通过反射查找对应的方法。

5.JAD实现原理

public class JadCommand extends AnnotatedCommand 

    public void process(CommandProcess process) 
        RowAffect affect = new RowAffect();
        Instrumentation inst = process.session().getInstrumentation();
        // 1、通过Instrumentation查找对应的类
        Set<Class<?>> 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<Class<?>> 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<Class<?>> matchedClasses, Set<Class<?>> withInnerClasses) 
        Class<?> c = matchedClasses.iterator().next();
        Set<Class<?>> allClasses = new HashSet<Class<?>>(withInnerClasses);
        allClasses.add(c);

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

            Map<Class<?>, File> classFiles = transformer.getDumpResult();
            File classFile = classFiles.get(c);
            // 3、执行反编译的动作
            Pair<String,NavigableMap<Integer,Integer>> 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<Class<?>> classesToEnhance;
    private Map<Class<?>, File> dumpResult;
    private File arthasLogHome;

    private File directory;

    public ClassDumpTransformer(Set<Class<?>> classesToEnhance) 
        this(classesToEnhance, null);
    

    public ClassDumpTransformer(Set<Class<?>> classesToEnhance, File directory) 
        this.classesToEnhance = classesToEnhance;
        this.dumpResult = new HashMap<Class<?>, 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<Class<?>, 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<Class<?>> 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<String, NavigableMap<Integer, Integer>> decompileWithMappings(String classFilePath,
            String methodName, boolean hideUnicode, boolean printLineNumber) 
        final StringBuilder sb = new StringBuilder(8192);

        final NavigableMap<Integer, Integer> lineMapping = new TreeMap<Integer, Integer>();

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

            @Override
            public <T> Sink<T> getSink(final SinkType sinkType, final SinkClass sinkClass) 
                return new Sink<T>() 
                    @Override
                    public void write(T sinkable) 
                        // skip message like: Analysing type demo.MathGame
                        if (sinkType == SinkType.PROGRESS) 
               

以上是关于ArthasArthas 类查找和反编译原理的主要内容,如果未能解决你的问题,请参考以下文章

ArthasArthas retransform动态重新加载类

ArthasArthas dump导出加载类

ArthasArthas classloader类加载器

ArthasArthas 导出堆栈信息

java javassist创建类和反编译类

arthasArthas 查看 Mbean 的信息