HiSEN

字节码增强 - 初识链路追踪 - ByteBuddy

1. 简单介绍

1.1 背景

  • 不能通过 -javaagent 方式启动
  • 需要增强非业务代码( Spring AOP 不够用)
  • 业务方尽量少改动代码

1.2 效果

1
2
3
4
5
6
7
[Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer@87f383f on sun.instrument.InstrumentationImpl@4eb7f003
[Byte Buddy] INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer@87f383f on sun.instrument.InstrumentationImpl@4eb7f003
[Byte Buddy] TRANSFORM com.hisen.agent.util.Hisen [sun.misc.Launcher$AppClassLoader@18b4aac2, null, Thread[main,5,main], loaded=false]
name before:hisen
hello: hisen1677417020466
public static void com.hisen.agent.util.Hisen.hello(java.lang.String): took 0 millisecond
HisenAdvice exit. time use:0

2. 代码逻辑

2.1 启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author hisenyuan
* @date 2023/2/6 21:31
*/
public class Start {
public static void main(String[] args) {
startTraceAgent();
Hisen.hello("hisen");
}

private static void startTraceAgent() {
Instrumentation install = ByteBuddyAgent.install();
HisenInterceptor.init(install);
new HisenInstrumentation().init(install);
}
}

2.2 拦截方式

会改变程序执行的堆栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @author hisenyuan
* @date 2023/2/8 21:17
*/
public class HisenInterceptor {
public static void init(Instrumentation instrumentation) {
new AgentBuilder.Default()
.type(ElementMatchers.nameEndsWith("Hisen"))
.transform((builder, type, classLoader, module, protectionDomain) -> builder.method(ElementMatchers.any()).intercept(MethodDelegation.to(TimeInterceptor.class)))
.installOn(instrumentation);
}

public static class TimeInterceptor {
@RuntimeType
public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable) throws Exception {
long start = System.currentTimeMillis();
try {
return callable.call();
} catch (Exception e) {
// 进行异常信息上报
System.out.println("方法执行发生异常" + e.getMessage());
throw e;
} finally {
System.out.println(method + ": took " + (System.currentTimeMillis() - start) + " millisecond");
}
}
}
}

2.3 代码注入

不会改变代码堆栈流
这种方式也可以增强系统类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* @author hisenyuan
* @date 2023/2/8 20:16
*/
public class HisenInstrumentation {
public void init(Instrumentation instrumentation) {
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(RETRANSFORMATION)
// Make sure we see helpful logs
.with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
.with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
.with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
.ignore(none())
// Ignore Byte Buddy and JDK classes we are not interested in
.ignore(
nameStartsWith("net.bytebuddy.")
.or(nameStartsWith("jdk.internal.reflect."))
.or(nameStartsWith("java.lang.invoke."))
.or(nameStartsWith("com.sun.proxy."))
)
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.type(nameEndsWith("Hisen"))
.transform(
(builder, type, classLoader, transformer, module) ->
builder.visit(Advice.to(HisenAdvice.class).on(isMethod().and(named("hello")))))
.installOn(instrumentation);
}

public static class HisenAdvice {
@Advice.OnMethodEnter
static long enter(@Advice.Argument(value = 0, typing = DYNAMIC, readOnly = false) String name) {
System.out.println("name before:" + name);
// change name
name += System.currentTimeMillis();
return System.currentTimeMillis();
}

@Advice.OnMethodExit(onThrowable = RuntimeException.class)
static void exit(
@Advice.Enter
long startTime) {
System.out.println("HisenAdvice exit. time use:" + (System.currentTimeMillis() - startTime));
}
}
}