<返回更多

Arthas底层字节增强工具bytekit

2022-07-05  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

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>