您当前的位置:首页 > 电脑百科 > 软件技术 > 应用软件

Arthas底层字节增强工具bytekit

时间:2022-07-05 11:34:01  来源:CSDN  作者:一只程序猿粑粑

简要说明

在几次生产环境排查问题中使用到了Arthas,查找了部分资料找到一个关于Arthas底层用于字节增强的工具。

主要实现了JAVA字节码文件层次的动态增强,可在不重新编译原java文件重新生成class的情况下,在应用服务器运行时对class文件进行修改增强;

找了一圈都没有找到除了在Arthas之外的用的地方,大概率的也就是用于系统运行时诊断之类的,或系统运行时开关调整调试,获取更多的调试信息;

体验过程

1、引入maven依赖,主要依赖了以下信息

<dependencies>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>bytekit-core</artifactId>
    <version>0.0.7</version>
  </dependency>
  <dependency>
    <groupId>.NET.bytebuddy</groupId>
    <artifactId>byte-buddy-agent</artifactId>
    <version>1.11.6</version>
  </dependency>
  <dependency>
    <groupId>org.benf</groupId>
    <artifactId>cfr</artifactId>
    <version>0.151</version>
  </dependency>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.30</version>
  </dependency>
</dependencies>

2、以下几个过程的文件通过网上搜来的,基本大差不差,按照使用顺序分别是

3、未进行字节增强的源文件如下

public class Sample {
    private int exceptionCount = 0;
    public String hello(String str, boolean exception) {
        if (exception) {
            exceptionCount++;
            throw new RuntimeException("test exception, str: " + str);
        }
        return "hello " + str;
    }
}

4、字节增强类:对字节文件进行处理,到了处理了什么,该类定义了进入方法@AtEnter,结束方法@AtExit,以及方法抛出异常@AtExceptionExit时的处理;

// Sample 类的拦截器
class SampleInterceptor {

    // 拦截方法Entry点进行处理
    @AtEnter(inline = false, suppress = RuntimeException.class, suppressHandler = PrintExceptionSuppressHandler.class)
    public static void atEnter(@Binding.This Object object,
                               @Binding.Class Object clazz,
                               @Binding.Args Object[] args,
                               @Binding.MethodName String methodName,
                               @Binding.MethodDesc String methodDesc) {
        System.out.println("atEnter, args[0]: " + args[0]);
    }
 
    // 拦截方法正常返回的语句,在返回前进行处理
    @AtExit(inline = true)
    public static void atExit(@Binding.Return Object returnObject,@Binding.MethodName String methodName) {
        System.out.println("atExit, returnObject: " + returnObject);
    }
 
    // 拦截方法内部抛出异常点
    @AtExceptionExit(inline = true, onException = RuntimeException.class)
    public static void atExceptionExit(@Binding.Throwable RuntimeException ex,
                                       @Binding.Field(name = "exceptionCount") int exceptionCount,@Binding.MethodName String methodName) {
        System.out.println("atExceptionExit, ex: " + ex.getMessage() + ", field exceptionCount: " + exceptionCount);
    }
}

5、异常处理器:即上述程序提到的异常处理方法

// 异常处理器
class PrintExceptionSuppressHandler {
    @ExceptionHandler(inline = true)
    public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) {
        System.out.println("exception handler: " + clazz);
        e.printStackTrace();
    }
}

6、对ByteKit的封装处理,主要是获取到目标类,和增强类,使用增强类对目标类的字节码文件进行处理,获取到新的字节码文件

class EnhanceUtil {
 
    public static byte[] enhanceClass(Class targetClass, String[] targetMethodNames, Class interceptorClass) throws Exception {
        // 初始化Instrumentation
        AgentUtils.install();
 
        // 解析定义的 Interceptor类 和相关的注解
        DefaultInterceptorClassParser interceptorClassParser = new DefaultInterceptorClassParser();
        List<InterceptorProcessor> processors = interceptorClassParser.parse(interceptorClass);
 
        // 加载字节码
        ClassNode classNode = AsmUtils.loadClass(targetClass);
 
        List<String> methodNameList = Arrays.asList(targetMethodNames);
 
        // 对加载到的字节码做增强处理
        for (MethodNode methodNode : classNode.methods) {
            if (methodNameList.contAIns(methodNode.name)) {
                MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode);
                for (InterceptorProcessor interceptor : processors) {
                    interceptor.process(methodProcessor);
                }
            }
        }
 
        // 获取增强后的字节码
        return AsmUtils.toBytes(classNode);
    }
 
}

7、最后是实际的执行过程:

//测试入口类
class SampleDemo {

    public static void main(String[] args) throws Exception {

        // 启动Sample
        System.out.println("before retransform ...");
        try {
            Sample sample = new Sample();
            sample.hello("1", false);
            sample.hello("2", true);
        } catch (Exception e) {
            e.printStackTrace(System.out);
        }
        System.out.println();


        // 对Sample类的hello方法进行拦截处理,返回增强后的字节码
        byte[] bytes = EnhanceUtil.enhanceClass(Sample.class, new String[]{"hello"}, SampleInterceptor.class);

        // 查看反编译结果
        //System.out.println(Decompiler.decompile(bytes));

        // 通过 reTransform 增强类
        AgentUtils.reTransform(Sample.class, bytes);

        // 启动Sample
        System.out.println("after retransform ...");
        try {
            Sample sample = new Sample();
            sample.hello("3", false);
            sample.hello("4", true);
        } catch (Exception e) {
            e.printStackTrace(System.out);
        }
    }

}

8、运行结果

before retransform ...
java.lang.RuntimeException: test exception, str: 2
	at com.sukeinfo.bk.demo.Sample.hello(Sample.java:9)
	at com.sukeinfo.bk.demo.SampleDemo.main(SampleDemo.java:15)

after retransform ...
atEnter, args[0]: 3
atExit, returnObject: hello 3
atEnter, args[0]: 4
atExceptionExit, ex: test exception, str: 4, field exceptionCount: 1
java.lang.RuntimeException: test exception, str: 4
	at com.sukeinfo.bk.demo.Sample.hello(Sample.java:9)
	at com.sukeinfo.bk.demo.SampleDemo.main(SampleDemo.java:36)

InstrumentApi.invokeOrigin

0、再以上mvn的配置基础上增加了

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.13.2</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.assertj</groupId>
  <artifactId>assertj-core</artifactId>
  <version>3.23.1</version>
  <scope>test</scope>
</dependency>

1、原始的java文件如下

public class InvokeOriginDemo {

    public void returnVoid() {
    }

    public Void returnVoidObject() {
        int i = 0;
        try {
            int parseInt = Integer.parseInt("1000");
            i += parseInt;
        } catch (Exception e) {
            System.err.println(i + " " + e);
        }

        return null;
    }

    public int returnInt(int i) {
        return 9998;
    }

    public int returnIntToObject(int i) {

        return 9998;
    }

    public int returnIntToInteger(int i) {

        return 9998;
    }

    public static int returnIntStatic(int i) {
        return 9998;
    }

    public long returnLong() {
        return 9998L;
    }

    public long returnLongToObject() {
        return 9998L;
    }

    public String[] returnStrArray() {
        String[] result = new String[] {"abc", "xyz" , "ufo"};
        return result;
    }

    public String[] returnStrArrayWithArgs(int i, String s, long l) {
        String[] result = new String[] {"abc" + i, "xyz" + s , "ufo" + l};
        return result;
    }

    public String returnStr() {
        return new Date().toString();
    }

    public Object returnObject() {
        return InvokeOriginDemo.class;
    }


    public int recursive(int i) {
        if (i == 1) {
            return 1;
        }
        return i + recursive(i - 1);
    }

    // 测试修改args
    public String changeArgs(int i, long l, String str) {
        return str + i + l;
    }
}

2、通过字节增强的文件如下:

@Instrument(Class = "com.sukeinfo.bk.demo2.InvokeOriginDemo")
public abstract class InvokeOriginDemo_APM {

    public void returnVoid() {
        Object o = InstrumentApi.invokeOrigin();
        System.out.println(o);
    }

    public Void returnVoidObject() {
        Void v = InstrumentApi.invokeOrigin();
        System.out.println(v);
        return v;
    }

    public int returnInt(int i) {
        System.out.println("before");
        int value = InstrumentApi.invokeOrigin();
        System.out.println("after");
        return value + 123;
    }

    public int returnIntToObject(int i) {
        Object value = InstrumentApi.invokeOrigin();
        return 9998 + (Integer) value;
    }

    public int returnIntToInteger(int i) {

        Integer ixx = InstrumentApi.invokeOrigin();

        return ixx + 9998;
    }

    public static int returnIntStatic(int i) {
        int result = InstrumentApi.invokeOrigin();
        return 9998 + result;
    }

    public long returnLong() {
        long result = InstrumentApi.invokeOrigin();
        return 9998L + result;
    }

    public long returnLongToObject() {
        Long lll = InstrumentApi.invokeOrigin();
        return 9998L + lll;
    }

    public String[] returnStrArray() {
        String[] result = InstrumentApi.invokeOrigin();
        System.err.println(result);
        return result;
    }

    public String[] returnStrArrayWithArgs(int i, String s, long l) {
        System.out.println(i);
        String[] result = InstrumentApi.invokeOrigin();
        result[0] = "fff";
        return result;
    }

    public String returnStr() {
        System.err.println("ssss");
        Object result = InstrumentApi.invokeOrigin();
        return "hello" + result;
    }

    public Object returnObject() {
        InstrumentApi.invokeOrigin();
        return InvokeOriginDemo.class;
    }

    public int recursive(int i) {
        int result = InstrumentApi.invokeOrigin();

        System.err.println(result);
        return result;
    }

    public String changeArgs(int i, long l, String str) {
        i = 1;
        l = 9999;
        str = "xxx";
        String result = InstrumentApi.invokeOrigin();
        System.err.println(result);
        return result;
    }
}

3、以增强后的returnInt方法为例进行说明,在增强后的该类中进行如下处理,得到的class文件反编译的效果如下,即将原代码中的执行结果拿到
InstrumentApi.invokeOrigin()所在位置再进行处理:

//原代码
public int returnInt(int i) {
    return 9998;
}
//增强代码如下
public int returnInt(int i) {
    System.out.println("before");
    int value = InstrumentApi.invokeOrigin();
    System.out.println("after");
    return value + 123;
}
//增强后运行时进行替换class文件效果
public int returnInt(int i) {
    System.out.println("before");
    int n = i;
    InvokeOriginDemo invokeOriginDemo = this;
    int value = 9998;
    System.out.println("after");
    return value + 123;
}

4、增强测试类

public class InvokeOriginTest {

    ClassNode apmClassNode;
    ClassNode originClassNode;

    ClassNode targetClassNode;

    @Rule
    public TestName testName = new TestName();

    @BeforeClass
    public static void beforeClass() throws IOException {

    }

    @Before
    public void before() throws IOException {
        apmClassNode = AsmUtils.loadClass(InvokeOriginDemo_APM.class);
        originClassNode = AsmUtils.loadClass(InvokeOriginDemo.class);

        byte[] renameClass = AsmUtils.renameClass(AsmUtils.toBytes(apmClassNode),
                        Type.getObjectType(originClassNode.name).getClassName());

        apmClassNode = AsmUtils.toClassNode(renameClass);

        targetClassNode = AsmUtils.copy(originClassNode);
    }

    private Object replace(String methodName) throws Exception {
        System.err.println(methodName);
        for (MethodNode methodNode : apmClassNode.methods) {
            if (methodNode.name.equals(methodName)) {
                methodNode = AsmUtils.removeLineNumbers(methodNode);
                // 从原来的类里查找对应的函数
                MethodNode findMethod = AsmUtils.findMethod(originClassNode.methods, methodNode);
                if (findMethod != null) {
                    MethodNode methodNode2 = InstrumentImpl.replaceInvokeOrigin(originClassNode.name, findMethod,
                                    methodNode);

                    //System.err.println(Decompiler.toString(methodNode2));

                    AsmUtils.replaceMethod(targetClassNode, methodNode2);

                } else {

                }
            }
        }

        byte[] resutlBytes = AsmUtils.toBytes(targetClassNode);

        System.err.println("=================");

        System.err.println(Decompiler.decompile(resutlBytes));

        // System.err.println(AsmUtils.toASMCode(resutlBytes));

        VerifyUtils.asmVerify(resutlBytes);
        return VerifyUtils.instanceVerity(resutlBytes);
    }

    @Test
    public void test_returnVoid() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);

        Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(null);
    }

    @Test
    public void test_returnVoidObject() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);
        Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(null);
    }

    @Test
    public void test_returnInt() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);
        Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 123);
    }

    @Test
    public void test_returnIntToObject() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);
        Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 9998);
    }

    @Test
    public void test_returnIntToInteger() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);
        Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 9998);
    }

    @Test
    public void test_returnIntStatic() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);
        Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 9998);
    }

    @Test
    public void test_returnLong() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);
        Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(9998L + 9998);
    }

    @Test
    public void test_returnLongToObject() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);
        Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(9998L + 9998);
    }

    @Test
    public void test_returnStrArray() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);
        Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(new String[] { "abc", "xyz", "ufo" });
    }

    @Test
    public void test_returnStrArrayWithArgs() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);
        Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123, "sss", 777L))
                        .isEqualTo(new Object[] { "fff", "xyz" + "sss", "ufo" + 777 });
    }

    @Test
    public void test_returnStr() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);
        Assertions.assertThat(VerifyUtils.invoke(object, methodName)).asString().startsWith("hello");
    }

    @Test
    public void test_returnObject() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);
        Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(object.getClass());
    }

    @Test
    public void test_recursive() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);
        Assertions.assertThat(VerifyUtils.invoke(object, methodName, 100)).isEqualTo((100 + 1) * 100 / 2);
    }

    @Test
    public void test_changeArgs() throws Exception {
        String methodName = testName.getMethodName().substring("test_".length());
        Object object = replace(methodName);
        Assertions.assertThat(VerifyUtils.invoke(object, methodName, 100, 333, "fff")).isEqualTo("xxx19999");
    }
}

参考文件

https://blog.csdn.net/lianggzone/article/details/120245570



Tags:bytekit   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Arthas底层字节增强工具bytekit
简要说明在几次生产环境排查问题中使用到了Arthas,查找了部分资料找到一个关于Arthas底层用于字节增强的工具。主要实现了java字节码文件层次的动态增强,可在不重新编译原java...【详细内容】
2022-07-05  Search: bytekit  点击:(360)  评论:(0)  加入收藏
▌简易百科推荐
系统优化工具,Ultimate Windows Tweaker软件体验
电脑上的Windows优化工具年年都有,每年还会翻着花样地出现新东西,都不带重复的。每个人都可以上来折腾一番Windows...从这个角度来说,Windows系统还挺“稳定”的,经得起各种用户...【详细内容】
2024-04-10  果核剥壳    Tags:系统优化   点击:(4)  评论:(0)  加入收藏
Telegram怎么不显示在线?
在Telegram中,您可以通过进入“设置” -> “隐私与安全” -> “最后在线时间”,然后选择“没有人”或者自定义特定的人群,以隐藏自己的在线状态。这样设置后,其他用户将无法看到...【详细内容】
2024-04-04  HouseRelax    Tags:Telegram   点击:(8)  评论:(0)  加入收藏
谷歌 Gmail 新规生效:为遏制钓鱼 / 欺诈情况,日群发超 5000 封邮件账号需验证
IT之家 4 月 2 日消息,谷歌为了增强对垃圾邮件和网络钓鱼攻击的管控,今天宣布正式启用新措施:对于向 Gmail 邮箱账号日群发数量超过 5000 封的用户,需要其在域名中设置 SPF / DK...【详细内容】
2024-04-02    IT之家  Tags:Gmail   点击:(16)  评论:(0)  加入收藏
钉钉AI升级多模态:能根据图片识人、翻译、创作、多轮问答
新浪科技讯 3月28日午间消息,钉钉AI助理迎来升级,上线图片理解、文档速读、工作流等产品能力,探索多模态、长文本与RPA技术在AI应用的落地。基于阿里通义千问大模型,升级后的钉...【详细内容】
2024-03-28    新浪科技  Tags:钉钉   点击:(17)  评论:(0)  加入收藏
都2024年了,谁还在用QQ聊天啊?
你还在用 QQ 吗?之所以突然这么问,是因为前些天腾讯发了份热气腾腾的财报。随手翻了翻,发现 QQ 这个老企鹅,居然还有5.54 亿多人每个月都在坚持登录。虽说和辉煌时候没法比了,但...【详细内容】
2024-03-26    差评  Tags:QQ   点击:(11)  评论:(0)  加入收藏
腾讯QQ浏览器工具权益卡上线PC端,每月最低6元
IT之家 1 月 29 日消息,腾讯 QQ 浏览器此前在手机端上线工具权益卡,现将部分权益适用范围拓展至 PC 端,每月 10 元,连续包月为 6 元。开通后用户可以在 QQ 浏览器软件内享有由腾...【详细内容】
2024-01-29    IT之家  Tags:QQ浏览器   点击:(87)  评论:(0)  加入收藏
开源工具Ventoy更新:新增对FreeBSD 14.0的支持
近日,开源装机工具Ventoy发布了1.0.97版本的更新。本次更新的主要亮点是新增了对FreeBSD 14.0版本的支持,并修复了启动问题以及解决了几个Linux独有的bug等。同时,官方还修复了...【详细内容】
2024-01-25    中关村在线  Tags:Ventoy   点击:(42)  评论:(0)  加入收藏
微软Copilot Pro来了:个人用户也能在Word里用GPT-4,20美元/月
面向个人用户的微软Copilot会员版来了。一个月多交20刀(约合人民币142元),Microsoft 365个人版/家庭版用户就能在Word、Excel、PPT等Office全家桶中用上GPT-4。就像这样,不用在C...【详细内容】
2024-01-16    量子位  Tags:Copilot Pro   点击:(97)  评论:(0)  加入收藏
微软 Edge 浏览器支持双引擎同时搜索功能,便利与槽点并存
IT之家 1 月 15 日消息,微软广告和网络服务部门首席执行官 Mikhail Parakhin 近日透露了一个微软 Edge 浏览器的隐藏功能:双引擎同时搜索。顾名思义,该功能允许用户同时使用两...【详细内容】
2024-01-16    IT之家  Tags:Edge   点击:(65)  评论:(0)  加入收藏
11个面向设计师的必备AI工具
译者 | 布加迪审校 | 重楼在当今快速发展的设计领域,人工智能(AI)工具已成为不可或缺的创新催化剂。这些工具专门用于提高效率和创造力,从而重新定义传统的设计方法。AI正在彻底...【详细内容】
2024-01-09    51CTO  Tags:AI工具   点击:(109)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条