在几次生产环境排查问题中使用到了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)
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