Skip to content

Commit 6bfef97

Browse files
authored
Support instrumentation of repackaged libraries (#8153)
1 parent 886169b commit 6bfef97

File tree

8 files changed

+196
-14
lines changed

8 files changed

+196
-14
lines changed

dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/CombiningTransformerBuilder.java

+21-6
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public final class CombiningTransformerBuilder
7171
private ElementMatcher<ClassLoader> classLoaderMatcher;
7272
private Map<String, String> contextStore;
7373
private AgentBuilder.Transformer contextRequestRewriter;
74+
private AdviceShader adviceShader;
7475
private HelperTransformer helperTransformer;
7576
private Advice.PostProcessor.Factory postProcessor;
7677
private MuzzleCheck muzzle;
@@ -118,14 +119,19 @@ private void prepareInstrumentation(InstrumenterModule module, int instrumentati
118119
new FieldBackedContextRequestRewriter(contextStore, module.name()))
119120
: null;
120121

122+
adviceShader = AdviceShader.with(module.adviceShading());
123+
121124
String[] helperClassNames = module.helperClassNames();
122125
if (module.injectHelperDependencies()) {
123126
helperClassNames = HelperScanner.withClassDependencies(helperClassNames);
124127
}
125128
helperTransformer =
126129
helperClassNames.length > 0
127130
? new HelperTransformer(
128-
module.useAgentCodeSource(), module.getClass().getSimpleName(), helperClassNames)
131+
module.useAgentCodeSource(),
132+
adviceShader,
133+
module.getClass().getSimpleName(),
134+
helperClassNames)
129135
: null;
130136

131137
postProcessor = module.postProcessor();
@@ -238,11 +244,17 @@ public void applyAdvice(ElementMatcher<? super MethodDescription> matcher, Strin
238244
if (postProcessor != null) {
239245
customMapping = customMapping.with(postProcessor);
240246
}
241-
advice.add(
247+
AgentBuilder.Transformer.ForAdvice forAdvice =
242248
new AgentBuilder.Transformer.ForAdvice(customMapping)
243-
.include(Utils.getBootstrapProxy(), Utils.getExtendedClassLoader())
244249
.withExceptionHandler(ExceptionHandlers.defaultExceptionHandler())
245-
.advice(not(ignoredMethods).and(matcher), adviceClass));
250+
.include(Utils.getBootstrapProxy());
251+
ClassLoader adviceLoader = Utils.getExtendedClassLoader();
252+
if (adviceShader != null) {
253+
forAdvice = forAdvice.include(new ShadedAdviceLocator(adviceLoader, adviceShader));
254+
} else {
255+
forAdvice = forAdvice.include(adviceLoader);
256+
}
257+
advice.add(forAdvice.advice(not(ignoredMethods).and(matcher), adviceClass));
246258
}
247259

248260
public ClassFileTransformer installOn(Instrumentation instrumentation) {
@@ -342,8 +354,11 @@ public DynamicType.Builder<?> transform(
342354

343355
static final class HelperTransformer extends HelperInjector implements AgentBuilder.Transformer {
344356
HelperTransformer(
345-
boolean useAgentCodeSource, String requestingName, String... helperClassNames) {
346-
super(useAgentCodeSource, requestingName, helperClassNames);
357+
boolean useAgentCodeSource,
358+
AdviceShader adviceShader,
359+
String requestingName,
360+
String... helperClassNames) {
361+
super(useAgentCodeSource, adviceShader, requestingName, helperClassNames);
347362
}
348363
}
349364

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package datadog.trace.agent.tooling;
2+
3+
import java.io.IOException;
4+
import net.bytebuddy.dynamic.ClassFileLocator;
5+
6+
/** Locates and shades class-file resources from the advice class-loader. */
7+
public final class ShadedAdviceLocator implements ClassFileLocator {
8+
private final ClassFileLocator adviceLocator;
9+
private final AdviceShader adviceShader;
10+
11+
public ShadedAdviceLocator(ClassLoader adviceLoader, AdviceShader adviceShader) {
12+
this.adviceLocator = ClassFileLocator.ForClassLoader.of(adviceLoader);
13+
this.adviceShader = adviceShader;
14+
}
15+
16+
@Override
17+
public Resolution locate(String className) throws IOException {
18+
final Resolution resolution = adviceLocator.locate(className);
19+
if (resolution.isResolved()) {
20+
return new Resolution.Explicit(adviceShader.shade(resolution.resolve()));
21+
} else {
22+
return resolution;
23+
}
24+
}
25+
26+
@Override
27+
public void close() throws IOException {
28+
adviceLocator.close();
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package datadog.trace.agent.tooling;
2+
3+
import datadog.trace.api.cache.DDCache;
4+
import datadog.trace.api.cache.DDCaches;
5+
import java.util.Map;
6+
import net.bytebuddy.jar.asm.ClassReader;
7+
import net.bytebuddy.jar.asm.ClassVisitor;
8+
import net.bytebuddy.jar.asm.ClassWriter;
9+
import net.bytebuddy.jar.asm.commons.ClassRemapper;
10+
import net.bytebuddy.jar.asm.commons.Remapper;
11+
12+
/** Shades advice bytecode by applying relocations to all references. */
13+
public final class AdviceShader extends Remapper {
14+
private final DDCache<String, String> cache = DDCaches.newFixedSizeCache(64);
15+
16+
/** Flattened sequence of old-prefix, new-prefix relocations. */
17+
private final String[] prefixes;
18+
19+
public static AdviceShader with(Map<String, String> relocations) {
20+
return relocations != null ? new AdviceShader(relocations) : null;
21+
}
22+
23+
AdviceShader(Map<String, String> relocations) {
24+
// convert relocations to a flattened sequence: old-prefix, new-prefix, etc.
25+
this.prefixes = new String[relocations.size() * 2];
26+
int i = 0;
27+
for (Map.Entry<String, String> e : relocations.entrySet()) {
28+
String oldPrefix = e.getKey();
29+
String newPrefix = e.getValue();
30+
if (oldPrefix.indexOf('.') > 0) {
31+
// accept dotted prefixes, but store them in their internal form
32+
this.prefixes[i++] = oldPrefix.replace('.', '/');
33+
this.prefixes[i++] = newPrefix.replace('.', '/');
34+
} else {
35+
this.prefixes[i++] = oldPrefix;
36+
this.prefixes[i++] = newPrefix;
37+
}
38+
}
39+
}
40+
41+
/** Applies shading before calling the given {@link ClassVisitor}. */
42+
public ClassVisitor shade(ClassVisitor cv) {
43+
return new ClassRemapper(cv, this);
44+
}
45+
46+
/** Returns the result of shading the given bytecode. */
47+
public byte[] shade(byte[] bytecode) {
48+
ClassReader cr = new ClassReader(bytecode);
49+
ClassWriter cw = new ClassWriter(null, 0);
50+
cr.accept(shade(cw), 0);
51+
return cw.toByteArray();
52+
}
53+
54+
@Override
55+
public String map(String internalName) {
56+
if (internalName.startsWith("java/")
57+
|| internalName.startsWith("datadog/")
58+
|| internalName.startsWith("net/bytebuddy/")) {
59+
return internalName; // never shade these references
60+
}
61+
return cache.computeIfAbsent(internalName, this::shade);
62+
}
63+
64+
@Override
65+
public Object mapValue(Object value) {
66+
if (value instanceof String) {
67+
String text = (String) value;
68+
if (text.isEmpty()) {
69+
return text;
70+
} else if (text.indexOf('.') > 0) {
71+
return shadeDottedName(text);
72+
} else {
73+
return shade(text);
74+
}
75+
} else {
76+
return super.mapValue(value);
77+
}
78+
}
79+
80+
private String shade(String internalName) {
81+
for (int i = 0; i < prefixes.length; i += 2) {
82+
if (internalName.startsWith(prefixes[i])) {
83+
return prefixes[i + 1] + internalName.substring(prefixes[i].length());
84+
}
85+
}
86+
return internalName;
87+
}
88+
89+
private String shadeDottedName(String name) {
90+
String internalName = name.replace('.', '/');
91+
for (int i = 0; i < prefixes.length; i += 2) {
92+
if (internalName.startsWith(prefixes[i])) {
93+
return prefixes[i + 1].replace('/', '.') + name.substring(prefixes[i].length());
94+
}
95+
}
96+
return name;
97+
}
98+
}

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/HelperInjector.java

+15-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class HelperInjector implements Instrumenter.TransformingAdvice {
3333
ClassFileLocator.ForClassLoader.of(Utils.getExtendedClassLoader());
3434

3535
private final boolean useAgentCodeSource;
36+
private final AdviceShader adviceShader;
3637
private final String requestingName;
3738

3839
private final Set<String> helperClassNames;
@@ -58,8 +59,17 @@ public HelperInjector(
5859
final boolean useAgentCodeSource,
5960
final String requestingName,
6061
final String... helperClassNames) {
62+
this(useAgentCodeSource, null, requestingName, helperClassNames);
63+
}
64+
65+
public HelperInjector(
66+
final boolean useAgentCodeSource,
67+
final AdviceShader adviceShader,
68+
final String requestingName,
69+
final String... helperClassNames) {
6170
this.useAgentCodeSource = useAgentCodeSource;
6271
this.requestingName = requestingName;
72+
this.adviceShader = adviceShader;
6373

6474
this.helperClassNames = new LinkedHashSet<>(Arrays.asList(helperClassNames));
6575
}
@@ -70,6 +80,7 @@ public HelperInjector(
7080
final Map<String, byte[]> helperMap) {
7181
this.useAgentCodeSource = useAgentCodeSource;
7282
this.requestingName = requestingName;
83+
this.adviceShader = null;
7384

7485
helperClassNames = helperMap.keySet();
7586
dynamicTypeMap.putAll(helperMap);
@@ -78,9 +89,11 @@ public HelperInjector(
7889
private Map<String, byte[]> getHelperMap() throws IOException {
7990
if (dynamicTypeMap.isEmpty()) {
8091
final Map<String, byte[]> classnameToBytes = new LinkedHashMap<>();
81-
8292
for (final String helperClassName : helperClassNames) {
83-
final byte[] classBytes = classFileLocator.locate(helperClassName).resolve();
93+
byte[] classBytes = classFileLocator.locate(helperClassName).resolve();
94+
if (adviceShader != null) {
95+
classBytes = adviceShader.shade(classBytes);
96+
}
8497
classnameToBytes.put(helperClassName, classBytes);
8598
}
8699

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterModule.java

+5
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ public ElementMatcher<? super MethodDescription> methodIgnoreMatcher() {
156156
return isSynthetic();
157157
}
158158

159+
/** Override this to apply shading to method advice and injected helpers. */
160+
public Map<String, String> adviceShading() {
161+
return null;
162+
}
163+
159164
/** Override this to post-process the operand stack of any transformed methods. */
160165
public Advice.PostProcessor.Factory postProcessor() {
161166
return null;

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleGenerator.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package datadog.trace.agent.tooling.muzzle;
22

3+
import datadog.trace.agent.tooling.AdviceShader;
34
import datadog.trace.agent.tooling.Instrumenter;
45
import datadog.trace.agent.tooling.InstrumenterModule;
56
import java.io.File;
@@ -78,7 +79,8 @@ public ClassVisitor wrap(
7879
return classVisitor;
7980
}
8081

81-
private static Reference[] generateReferences(Instrumenter.HasMethodAdvice instrumenter) {
82+
private static Reference[] generateReferences(
83+
Instrumenter.HasMethodAdvice instrumenter, AdviceShader adviceShader) {
8284
// track sources we've generated references from to avoid recursion
8385
final Set<String> referenceSources = new HashSet<>();
8486
final Map<String, Reference> references = new LinkedHashMap<>();
@@ -88,7 +90,8 @@ private static Reference[] generateReferences(Instrumenter.HasMethodAdvice instr
8890
for (String adviceClass : adviceClasses) {
8991
if (referenceSources.add(adviceClass)) {
9092
for (Map.Entry<String, Reference> entry :
91-
ReferenceCreator.createReferencesFrom(adviceClass, contextClassLoader).entrySet()) {
93+
ReferenceCreator.createReferencesFrom(adviceClass, adviceShader, contextClassLoader)
94+
.entrySet()) {
9295
Reference toMerge = references.get(entry.getKey());
9396
if (null == toMerge) {
9497
references.put(entry.getKey(), entry.getValue());
@@ -105,12 +108,13 @@ private static Reference[] generateReferences(Instrumenter.HasMethodAdvice instr
105108
private static byte[] generateMuzzleClass(InstrumenterModule module) {
106109

107110
Set<String> ignoredClassNames = new HashSet<>(Arrays.asList(module.muzzleIgnoredClassNames()));
111+
AdviceShader adviceShader = AdviceShader.with(module.adviceShading());
108112

109113
List<Reference> references = new ArrayList<>();
110114
for (Instrumenter instrumenter : module.typeInstrumentations()) {
111115
if (instrumenter instanceof Instrumenter.HasMethodAdvice) {
112116
for (Reference reference :
113-
generateReferences((Instrumenter.HasMethodAdvice) instrumenter)) {
117+
generateReferences((Instrumenter.HasMethodAdvice) instrumenter, adviceShader)) {
114118
// ignore helper classes, they will be injected by the instrumentation's HelperInjector.
115119
if (!ignoredClassNames.contains(reference.className)) {
116120
references.add(reference);

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleVersionScanPlugin.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package datadog.trace.agent.tooling.muzzle;
22

3+
import datadog.trace.agent.tooling.AdviceShader;
34
import datadog.trace.agent.tooling.HelperInjector;
45
import datadog.trace.agent.tooling.InstrumenterModule;
56
import datadog.trace.agent.tooling.bytebuddy.SharedTypePools;
@@ -109,6 +110,7 @@ private static Map<String, byte[]> createHelperMap(final InstrumenterModule modu
109110
String[] helperClasses = module.helperClassNames();
110111
final Map<String, byte[]> helperMap = new LinkedHashMap<>(helperClasses.length);
111112
Set<String> helperClassNames = new HashSet<>(Arrays.asList(helperClasses));
113+
AdviceShader adviceShader = AdviceShader.with(module.adviceShading());
112114
for (final String helperName : helperClasses) {
113115
int nestedClassIndex = helperName.lastIndexOf('$');
114116
if (nestedClassIndex > 0) {
@@ -128,7 +130,10 @@ private static Map<String, byte[]> createHelperMap(final InstrumenterModule modu
128130
}
129131
final ClassFileLocator locator =
130132
ClassFileLocator.ForClassLoader.of(module.getClass().getClassLoader());
131-
final byte[] classBytes = locator.locate(helperName).resolve();
133+
byte[] classBytes = locator.locate(helperName).resolve();
134+
if (null != adviceShader) {
135+
classBytes = adviceShader.shade(classBytes);
136+
}
132137
helperMap.put(helperName, classBytes);
133138
}
134139
return helperMap;

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/ReferenceCreator.java

+14-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static datadog.trace.util.Strings.getClassName;
44
import static datadog.trace.util.Strings.getResourceName;
55

6+
import datadog.trace.agent.tooling.AdviceShader;
67
import datadog.trace.bootstrap.Constants;
78
import java.io.InputStream;
89
import java.util.ArrayDeque;
@@ -41,12 +42,14 @@ public class ReferenceCreator extends ClassVisitor {
4142
* Generate all references reachable from a given class.
4243
*
4344
* @param entryPointClassName Starting point for generating references.
45+
* @param adviceShader Optional shading to apply to the advice.
4446
* @param loader Classloader used to read class bytes.
4547
* @return Map of [referenceClassName -> Reference]
4648
* @throws IllegalStateException if class is not found or unable to be loaded.
4749
*/
4850
public static Map<String, Reference> createReferencesFrom(
49-
final String entryPointClassName, final ClassLoader loader) throws IllegalStateException {
51+
final String entryPointClassName, final AdviceShader adviceShader, final ClassLoader loader)
52+
throws IllegalStateException {
5053
final Set<String> visitedSources = new HashSet<>();
5154
final Map<String, Reference> references = new LinkedHashMap<>();
5255

@@ -64,7 +67,11 @@ public static Map<String, Reference> createReferencesFrom(
6467
}
6568
final ReferenceCreator cv = new ReferenceCreator(null);
6669
final ClassReader reader = new ClassReader(in);
67-
reader.accept(cv, ClassReader.SKIP_FRAMES);
70+
if (null == adviceShader) {
71+
reader.accept(cv, ClassReader.SKIP_FRAMES);
72+
} else {
73+
reader.accept(adviceShader.shade(cv), ClassReader.SKIP_FRAMES);
74+
}
6875

6976
final Map<String, Reference> instrumentationReferences = cv.getReferences();
7077
for (final Map.Entry<String, Reference> entry : instrumentationReferences.entrySet()) {
@@ -88,6 +95,11 @@ public static Map<String, Reference> createReferencesFrom(
8895
return references;
8996
}
9097

98+
public static Map<String, Reference> createReferencesFrom(
99+
final String entryPointClassName, final ClassLoader loader) {
100+
return createReferencesFrom(entryPointClassName, null, loader);
101+
}
102+
91103
private static boolean samePackage(String from, String to) {
92104
int fromLength = from.lastIndexOf('/');
93105
int toLength = to.lastIndexOf('/');

0 commit comments

Comments
 (0)