Skip to content

Commit e17e8ad

Browse files
committed
Support 'aot_training' mode to be used when creating AOT caches
In this mode only the agent jar is added to the boot classpath, no services are started. Also workaround potential AOT bug where TraceInterceptor is mistakenly restored from the system class-loader in production, even though it was visible from the boot class-loader during training, resulting in LinkageErrors. Any call to Tracer.addTraceInterceptor from application code in the system class-loader appears to trigger this bug. The workaround is to replace these calls during training with opcodes that pop the tracer and argument, and push the expected return value. This transformation is not persisted, so in production the original method is invoked.
1 parent 4416578 commit e17e8ad

File tree

3 files changed

+131
-1
lines changed

3 files changed

+131
-1
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package datadog.trace.bootstrap.aot;
2+
3+
import static datadog.instrument.asm.Opcodes.ACC_ABSTRACT;
4+
import static datadog.instrument.asm.Opcodes.ACC_NATIVE;
5+
import static datadog.instrument.asm.Opcodes.ASM9;
6+
import static datadog.instrument.asm.Opcodes.INVOKEINTERFACE;
7+
import static datadog.instrument.asm.Opcodes.POP2;
8+
9+
import datadog.instrument.asm.ClassReader;
10+
import datadog.instrument.asm.ClassVisitor;
11+
import datadog.instrument.asm.ClassWriter;
12+
import datadog.instrument.asm.MethodVisitor;
13+
import java.lang.instrument.ClassFileTransformer;
14+
import java.security.ProtectionDomain;
15+
import java.util.concurrent.atomic.AtomicBoolean;
16+
17+
/**
18+
* Workaround a potential AOT bug where {@link datadog.trace.api.interceptor.TraceInterceptor} is
19+
* mistakenly restored from the system class-loader in production, even though it was visible from
20+
* the boot class-loader during training, resulting in {@link LinkageError}s.
21+
*
22+
* <p>Any call to {@link datadog.trace.api.Tracer#addTraceInterceptor} from application code in the
23+
* system class-loader appears to trigger this bug. The workaround is to replace these calls during
24+
* training with opcodes that pop the tracer and argument, and push the expected return value.
25+
*
26+
* <p>Note this transformation is not persisted, so in production the original method is invoked.
27+
*/
28+
final class TraceApiTransformer implements ClassFileTransformer {
29+
30+
@Override
31+
public byte[] transform(
32+
ClassLoader loader,
33+
String className,
34+
Class<?> classBeingRedefined,
35+
ProtectionDomain pd,
36+
byte[] bytecode) {
37+
38+
// workaround only needed in the system class-loader
39+
if (loader == ClassLoader.getSystemClassLoader()) {
40+
try {
41+
ClassReader cr = new ClassReader(bytecode);
42+
ClassWriter cw = new ClassWriter(cr, 0);
43+
AtomicBoolean modified = new AtomicBoolean();
44+
cr.accept(new CallerPatch(cw, modified), 0);
45+
// only return something when we've modified the bytecode
46+
if (modified.get()) {
47+
return cw.toByteArray();
48+
}
49+
} catch (Throwable ignore) {
50+
// skip this class
51+
}
52+
}
53+
return null; // tells the JVM to keep the original bytecode
54+
}
55+
56+
/** Patches callers of {@link datadog.trace.api.Tracer#addTraceInterceptor}. */
57+
static final class CallerPatch extends ClassVisitor {
58+
private final AtomicBoolean modified;
59+
60+
CallerPatch(ClassVisitor cv, AtomicBoolean modified) {
61+
super(ASM9, cv);
62+
this.modified = modified;
63+
}
64+
65+
@Override
66+
public MethodVisitor visitMethod(
67+
int access, String name, String descriptor, String signature, String[] exceptions) {
68+
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
69+
if ((access & (ACC_ABSTRACT | ACC_NATIVE)) == 0) {
70+
return new InvokePatch(mv, modified);
71+
} else {
72+
return mv; // no need to patch abstract/native methods
73+
}
74+
}
75+
}
76+
77+
/** Removes calls to {@link datadog.trace.api.Tracer#addTraceInterceptor}. */
78+
static final class InvokePatch extends MethodVisitor {
79+
private final AtomicBoolean modified;
80+
81+
InvokePatch(MethodVisitor mv, AtomicBoolean modified) {
82+
super(ASM9, mv);
83+
this.modified = modified;
84+
}
85+
86+
@Override
87+
public void visitMethodInsn(
88+
int opcode, String owner, String name, String descriptor, boolean isInterface) {
89+
if (INVOKEINTERFACE == opcode
90+
&& "datadog/trace/api/Tracer".equals(owner)
91+
&& "addTraceInterceptor".equals(name)
92+
&& "(Ldatadog/trace/api/interceptor/TraceInterceptor;)Z".equals(descriptor)) {
93+
// pop tracer and trace interceptor argument from call stack
94+
mv.visitInsn(POP2);
95+
// push true return value
96+
mv.visitLdcInsn(true);
97+
// flag that we've modified the bytecode
98+
modified.set(true);
99+
} else {
100+
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
101+
}
102+
}
103+
}
104+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package datadog.trace.bootstrap.aot;
2+
3+
import java.lang.instrument.Instrumentation;
4+
import java.net.URL;
5+
6+
/** Prepares the agent for Ahead-of-Time training. */
7+
public final class TrainingAgent {
8+
public static void start(
9+
final Object bootstrapInitTelemetry,
10+
final Instrumentation inst,
11+
final URL agentJarURL,
12+
final String agentArgs) {
13+
14+
// apply TraceInterceptor LinkageError workaround
15+
inst.addTransformer(new TraceApiTransformer());
16+
17+
// don't start services, they won't be cached as they use a custom classloader
18+
}
19+
}

dd-java-agent/src/main/java/datadog/trace/bootstrap/AgentBootstrap.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,17 @@ private static void agentmainImpl(
142142
recordInstrumentationSource("cmd_line");
143143
}
144144

145+
String agentClassName;
146+
if ("aot_training".equalsIgnoreCase(agentArgs)) {
147+
agentClassName = "datadog.trace.bootstrap.aot.TrainingAgent";
148+
} else {
149+
agentClassName = "datadog.trace.bootstrap.Agent";
150+
}
151+
145152
final URL agentJarURL = installAgentJar(inst);
146153
final Class<?> agentClass;
147154
try {
148-
agentClass = Class.forName("datadog.trace.bootstrap.Agent", true, null);
155+
agentClass = Class.forName(agentClassName, true, null);
149156
} catch (ClassNotFoundException | LinkageError e) {
150157
throw new IllegalStateException("Unable to load DD Java Agent.", e);
151158
}

0 commit comments

Comments
 (0)