【Arthas】Arthas 类查找和反编译原理

2023-11-20

在这里插入图片描述

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) {
                            return;
                        }
                        if (sinkType == SinkType.LINENUMBER) {
                            LineNumberMapping mapping = (LineNumberMapping) sinkable;
                            NavigableMap<Integer, Integer> classFileMappings = mapping.getClassFileMappings();
                            NavigableMap<Integer, Integer> mappings = mapping.getMappings();
                            if (classFileMappings != null && mappings != null) {
                                for (Entry<Integer, Integer> entry : mappings.entrySet()) {
                                    Integer srcLineNumber = classFileMappings.get(entry.getKey());
                                    lineMapping.put(entry.getValue(), srcLineNumber);
                                }
                            }
                            return;
                        }
                        sb.append(sinkable);
                    }
                };
            }
        };

        HashMap<String, String> options = new HashMap<String, String>();
        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<String> toAnalyse = new ArrayList<String>();
        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实现字节码的反编译。

6.总结

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Arthas】Arthas 类查找和反编译原理 的相关文章

  • 我是否需要安装 SQLite 才能使 SQLiteJDBC 正常工作?

    我想我只是没有 明白 如果我的计算机上尚未安装 SQLite 并且我想编写一个使用嵌入式数据库的 Java 应用程序 并且我将 SQLiteJDBC JAR 下载 导入到我的项目中 那么这就是我所需要的吗 或者 我是否需要先安装 SQLit
  • 如何在由子控件组成的 SWT 复合材料上跟踪鼠标?

    我创建了自己的控件 我想跟踪鼠标并添加一个MouseTrackListener 很遗憾MouseEnter and MouseLeave当鼠标移动到我的合成部分 即标签和按钮 上时 也会生成事件 Mouse enter mouse ente
  • Java Logger 未记录到 Netbeans 中的输出

    我正在 Netbeans 中使用 Maven 启动一个 Java 项目 我编写了一些代码来使用 Logger 类进行日志记录 但是 日志记录似乎不起作用 在程序开始时 我运行 Logger getLogger ProjectMainClas
  • Thymeleaf 3 Spring 5 映射加载字符串而不是 HTML

    我正在尝试将 Spring 5 和 Thymeleaf 3 一起配置 我正在 Eclipse 上工作 我使用 全新安装 构建并使用 springboot run 运行应用程序 我已经设置了一个控制器和几个模板 但 Thymeleaf 似乎找
  • 如何调试“com.android.okhttp”

    在android kitkat中 URLConnection的实现已经被OkHttp取代 如何调试呢 OkHttp 位于此目录中 external okhttp android main java com squareup okhttp 当
  • Jframe 内有 2 个 Jdialogs 的 setModal 问题

    当我设置第一个选项时 我遇到了问题JDialog模态 第二个非模态 这是我正在尝试实现的功能 单击 测试对话框 按钮 一个JDialog有名字自定义对话框 主要的将会打开 如果单击 是 选项自定义对话框主 其他JDialog named 自
  • Android studio - 如何保存先前活动中选择的数据

    这是我的代码片段 这Textview充当按钮并具有Onclicklistner在他们 当cpu1000时Textview单击它会导致cpu g1000其代码如下所示的类 public class Game 1000 extends AppC
  • 如何在 Spring 中使 @PropertyResource 优先于任何其他 application.properties ?

    我正在尝试在类路径之外添加外部配置属性资源 它应该覆盖任何现有的属性 但以下方法不起作用 SpringBootApplication PropertySource d app properties public class MyClass
  • 如何仅从 Firestore 获取最新更新的数据?

    在 Firestore 上发现任何更改时始终获取整个文档 如何只获取最近更新的数据 这是我的数据 我需要在第一次加载时在聊天中按对象顺序 例如 2018 09 17 30 40 msg和sendby 并且如果数据更新则仅获取新的msg和se
  • 提高 PostgreSQL 1 亿数据左连接查询性能

    我在用Postgresql 9 2 version Windows 7 64 bit RAM 6GB 这是一个Java企业项目 我必须在我的页面中显示订单相关信息 有三个表通过左连接连接在一起 Tables TV HD 389772 行 T
  • Jetty、websocket、java.lang.RuntimeException:无法加载平台配置器

    我尝试在 Endpoint 中获取 http 会话 我遵循了这个建议https stackoverflow com a 17994303 https stackoverflow com a 17994303 这就是我这样做的原因 publi
  • 如何将 HTML 链接放入电子邮件正文中?

    我有一个可以发送邮件的应用程序 用 Java 实现 我想在邮件中放置一个 HTML 链接 但该链接显示为普通字母 而不是 HTML 链接 我怎样才能将 HTML 链接放入字符串中 我需要特殊字符吗 太感谢了 Update 大家好你们好 感谢
  • JDBC 时间戳和日期 GMT 问题

    我有一个 JDBC 日期列 如果我使用 getDate 则会得到 date 仅部分2009 年 10 月 2 日但如果我使用 getTimestamp 我会得到完整的 date 2009 年 10 月 2 日 13 56 78 890 这正
  • Java Swing - 如何禁用 JPanel?

    我有一些JComponents on a JPanel我想在按下 开始 按钮时禁用所有这些组件 目前 我通过以下方式显式禁用所有组件 component1 setEnabled false 但是有什么办法可以一次性禁用所有组件吗 我尝试禁用
  • 使用 HtmlUnit 定位弹出窗口

    我正在构建一个登录网站并抓取一些数据的程序 登录表单是一个弹出窗口 所以我需要访问这个www betexplorer com网站 在页面的右上角有一个登录链接 写着 登录 我单击该链接 然后出现登录弹出表单 我能够找到顶部的登录链接 但找不
  • 在 Spring 上下文中查找方法级自定义注释

    我想知道的是 所有的类 方法Spring http en wikipedia org wiki Spring Framework注释为 Versioned的bean 我创建了自定义注释 Target ElementType METHOD E
  • Android S8+ 警告消息“不支持当前的显示尺寸设置,可能会出现意外行为”

    我在 Samsung S8 Android 7 中收到此警告消息 APP NAME 不支持当前的显示尺寸设置 可能会 行为出乎意料 它意味着什么以及如何删除它 谢谢 通过添加解决supports screens 机器人 xlargeScre
  • Log4j2 ThreadContext 映射不适用于parallelStream()

    我有以下示例代码 public class Test static System setProperty isThreadContextMapInheritable true private static final Logger LOGG
  • Spring RESTful控制器方法改进建议

    我是 Spring REST 和 Hibernate 的新手 也就是说 我尝试组合一个企业级控制器方法 我计划将其用作未来开发的模式 您认为可以通过哪些方法来改进 我确信有很多 RequestMapping value user metho
  • Java 和/C++ 在多线程方面的差异

    我读过一些提示 多线程实现很大程度上取决于您正在使用的目标操作系统 操作系统最终提供了多线程能力 比如Linux有POSIX标准实现 而windows32有另一种方式 但我想知道编程语言水平的主要不同 C似乎为同步提供了更多选择 例如互斥锁

随机推荐

  • 删除C++ std::string字符串中的空格

    介绍一个使用标准库算法删除std string字符串中空格的方法 代码如下 std string str1 Hello world str1 erase std remove if str1 begin str1 end unsigned
  • unity: C#的Action Event Delegate的异同

    目录 一 Action 二 Event 三 Action和Event区别 四 Delegate 总结 Action Event Delegate的异同 前言 Action Event和Delegate都是C 语言中的重要概念 分别用于管理函
  • Human3.6M数据集下载

    Download H36M annotations mkdir data cd data wget http visiondata cis upenn edu volumetric h36m h36m annot tar tar xf h3
  • 利用Apache Tika分页解析pdf文件内容

    Apache Tika 实现pdf文档分页提取内容 Apache Tika是一个多功能的文档内容提取工具 可以提取多种类型的文档内容 常用的如pdf office等格式 网上的例子基本上都是提取整篇文档内容 实际上用Tika提取pdf等文档
  • QT 实现点击窗口以外任何位置即关闭窗口

    bool QTipLabel eventFilter QObject o QEvent e switch e gt type ifdef Q DEAD CODE FROM QT4 MAC case QEvent KeyPress case
  • LeetCode-----第八题-----字符串转换整数 (atoi)

    字符串转换整数 atoi 难度 中等 请你来实现一个 atoi 函数 使其能将字符串转换成整数 首先 该函数会根据需要丢弃无用的开头空格字符 直到寻找到第一个非空格的字符为止 接下来的转化规则如下 如果第一个非空字符为正或者负号时 则将该符
  • Java-spring相关八股

    1 Java中有哪几种方式来创建线程执行任务 继承Thread类 public class zhouyuThread extends Thread public static void main string args zhouyuThre
  • XL4015-ADJ 5A 大电流DC-DC原理图分享

    如有问题 请加扣扣群 460189483 最近咨询LM2596S ADJ 3A芯片竟然已经10元rmb了 于是找到5A负载能力的XL4015 ADJ 正品4元 以后就用这个代替啦 通过比较发现 XL4015的温度低一点 效率高一点 电流大一
  • Android 实时获取SurfaceView渲染的内容截图

    近期的需求 偶尔需要获取当前SurfaceView上渲染的内容视图 因为是通过网页端控制的 类似预览功能吧 百度了好久 没找到能用的 无意间发现了这个类PixelCopy java 网上没什么介绍 安卓系统封装的一个类PixelCopy j
  • 解决plt.title()中文显示问题

    我们直接用plt title 默认是显示英文 如图 应该在代码前加这几行代码就行了 解决中文显示问题 plt rcParams font sans serif SimHei plt rcParams axes unicode minus F
  • VMware虚拟机安装Ubuntu系统步骤详解

    VMware虚拟机安装Ubuntu系统步骤详解 Ubuntu系统介绍 VMware安装Ubuntu步骤 一 Ubuntu系统的下载 二 VMware workstation的下载安装 三 配置Ubuntu虚拟机系统 四 VMware安装Ub
  • vue插件(vue-print-nb)实现打印功能

    vue插件vue print nb实现打印功能 1 安装vue print nb 2 引入Vue项目 3 在组件中使用 4 vue print nb插件优化 1 安装vue print nb Vue2 0版本安装方法 npm install
  • 重命名文件或目录(renameTo)

    File or directory with old name File file new File oldname File or directory with new name File file2 new File newname R
  • list scala 当前位置 遍历_【Scala笔记——道】Scala List 遍历 foldLeft / foldRight详解...

    HOF foldLeft foldRight foldLeft 和 foldRight 都是对于 List 遍历的 高阶函数 是对列表遍历过程中进行函数操作的高阶函数抽象 List 遍历 假设有两个方法如下 求和 def sum ints
  • lyapunov直接法

    文章目录 定义6 6 Lyapunov第一定理 Lyapunov第二定理 用于刻画渐进稳定 内积分析 定义6 6 Lyapunov第一定理 假设 A C A subset C A C是闭的 如果存在A的邻域D和满足下面两条件的连续函数
  • 简单镜面反射

    前言 本篇博客只是一个简单的实现镜面反射功能的例子 主要是当做笔记使用 通过反射光向量实现流程 如下所示 核心要点 如下所示 1 顶点镜面反射颜色值等于反射光颜色乘以反射强度来获取 而入射光向量 法向量 视角向量 反射光向量的关系图如下所示
  • 【计算机毕业设计】基于微信小程序的电影院票务系统

    基于微信小程序的电影院票务系统 开发语言 Java 框架 ssm JDK版本 JDK1 8 服务器 tomcat7 数据库 mysql 5 7 一定要5 7版本 数据库工具 Navicat11 开发软件 eclipse myeclipse
  • 利用背景渐变实现边框样式

    css实现信封边框和虚线样式 利用线性渐变背景以及背景重复完成 1 利用渐变背景实现信封边框样式 div class letter border div letter border margin 100px width 750px heig
  • shopify 前端教程(还在学可能有很多错误理解以及知识点)

    第一步 本地开发环境的搭建 第一步是安装ruby gem 以下是官网的链接 Downloads rubyinstaller org 友情提示 官网下载速度非常慢 我不会翻墙 我是在csdn搜索别人下载好的安装 Windows下安装 ruby
  • 【Arthas】Arthas 类查找和反编译原理

    1 概述 转载 Arthas 类查找和反编译原理 2 开篇 Arthas支持通过类相关的操作命令 包括sc sm jad等 sc Search Class 命令搜索出所有已经加载到 JVM 中的 Class 信息 sm Search Met