Skip to content

Commit 5eddd46

Browse files
authored
Merge pull request #191 from DataDog/jb/debugger_minimal_agent
Initial stab on a minimal debugger agent.
2 parents a9d9d32 + b2e0d15 commit 5eddd46

File tree

7 files changed

+448
-3
lines changed

7 files changed

+448
-3
lines changed

dd-java-agent/agent-debugging/agent-debugging.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ shadowJar {
6363
exclude(project(':internal-api'))
6464
exclude(dependency('org.slf4j::'))
6565
}
66+
67+
relocate 'datadog.trace.api.Config', 'datadog.trace.api.debug.Config'
6668
}
6769

6870
jar {
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
2+
3+
plugins {
4+
id "com.github.johnrengelman.shadow"
5+
}
6+
7+
apply from: "$rootDir/gradle/java.gradle"
8+
9+
configurations {
10+
sharedShadowInclude
11+
}
12+
13+
dependencies {
14+
compile project(':dd-trace-api')
15+
compile project(':internal-api')
16+
compile deps.slf4j
17+
18+
// Includes for the shared internal shadow jar
19+
sharedShadowInclude deps.shared
20+
sharedShadowInclude deps.jackson
21+
sharedShadowInclude deps.jacksonJsr310
22+
}
23+
24+
def includeShadowJar(shadowJarTask, jarname, renameClasses = true, xxx = { true }) {
25+
project.processResources {
26+
from(zipTree(shadowJarTask.archiveFile)) {
27+
into jarname
28+
if (renameClasses) {
29+
rename '(^.*)\\.class$', '$1.classdata'
30+
}
31+
// Rename LICENSE file since it clashes with license dir on non-case sensitive FSs (i.e. Mac)
32+
rename '^LICENSE$', 'LICENSE.renamed'
33+
include xxx
34+
}
35+
}
36+
37+
project.processResources.dependsOn shadowJarTask
38+
}
39+
40+
project(':dd-java-agent:agent-debugging:minimal-agent').afterEvaluate {
41+
includeShadowJar(project(':dd-java-agent:agent-debugging:debugging-bootstrap').tasks.jar, '', false)
42+
includeShadowJar(project(':dd-java-agent:agent-debugging').tasks.shadowJar, 'debugging', true, {
43+
return it.path.startsWith('com/datadog/debugging/')
44+
})
45+
}
46+
47+
task sharedShadowJar(type: ShadowJar) {
48+
configurations = [project.configurations.sharedShadowInclude]
49+
}
50+
includeShadowJar(sharedShadowJar, 'shared')
51+
52+
shadowJar {
53+
manifest {
54+
attributes(
55+
"Main-Class": "datadog.trace.bootstrap.DebuggerAgentBootstrap",
56+
"Agent-Class": "datadog.trace.bootstrap.DebuggerAgentBootstrap",
57+
"Premain-Class": "datadog.trace.bootstrap.DebuggerAgentBootstrap",
58+
"Can-Redefine-Classes": true,
59+
"Can-Retransform-Classes": true,
60+
)
61+
}
62+
archiveBaseName.set('dd-java-agent')
63+
archiveClassifier.set('debugger-minimal')
64+
65+
exclude {
66+
if (it.path.contains('/maven')) {
67+
return true
68+
}
69+
def exc = false
70+
if (it.path.startsWith("datadog/trace/")) {
71+
if (it.directory) {
72+
exc = false
73+
} else {
74+
exc = !it.path.toLowerCase().contains('debug') && !it.path.endsWith('/Config.class') && !it.path.endsWith('/ConfigDefaults.class')
75+
}
76+
}
77+
return exc
78+
}
79+
80+
// Prevents conflict with other SLF4J instances. Important for premain.
81+
relocate 'org.slf4j', 'datadog.slf4j'
82+
relocate 'datadog.trace.api.Config', 'datadog.trace.api.debug.Config'
83+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package datadog.trace.bootstrap;
2+
3+
import static datadog.trace.api.Platform.isJavaVersionAtLeast;
4+
5+
import java.lang.instrument.Instrumentation;
6+
import java.lang.reflect.Constructor;
7+
import java.lang.reflect.InvocationTargetException;
8+
import java.lang.reflect.Method;
9+
import java.net.URL;
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
12+
13+
/**
14+
* Agent start up logic.
15+
*
16+
* <p>This class is loaded and called by {@code datadog.trace.bootstrap.DebuggerAgentBootstrap}
17+
*
18+
* <p>The intention is for this class to be loaded by bootstrap classloader to make sure we have
19+
* unimpeded access to the rest of Datadog's agent parts.
20+
*/
21+
public final class DebuggerAgent {
22+
private static final Logger log = LoggerFactory.getLogger(DebuggerAgent.class);
23+
24+
// fields must be managed under class lock
25+
private static ClassLoader PARENT_CLASSLOADER = null;
26+
private static ClassLoader BOOTSTRAP_PROXY = null;
27+
private static ClassLoader DEBUGGING_CLASSLOADER = null;
28+
29+
public static void start(final Instrumentation inst, final URL bootstrapURL) {
30+
createParentClassloader(bootstrapURL);
31+
32+
startDebuggingAgent(inst, bootstrapURL);
33+
}
34+
35+
private static synchronized void createParentClassloader(final URL bootstrapURL) {
36+
if (PARENT_CLASSLOADER == null) {
37+
try {
38+
final Class<?> bootstrapProxyClass =
39+
ClassLoader.getSystemClassLoader()
40+
.loadClass("datadog.trace.bootstrap.DatadogClassLoader$BootstrapClassLoaderProxy");
41+
final Constructor constructor = bootstrapProxyClass.getDeclaredConstructor(URL.class);
42+
BOOTSTRAP_PROXY = (ClassLoader) constructor.newInstance(bootstrapURL);
43+
44+
final ClassLoader grandParent;
45+
if (!isJavaVersionAtLeast(9)) {
46+
grandParent = null; // bootstrap
47+
} else {
48+
// platform classloader is parent of system in java 9+
49+
grandParent = getPlatformClassLoader();
50+
}
51+
52+
PARENT_CLASSLOADER = createDatadogClassLoader("shared", bootstrapURL, grandParent);
53+
} catch (final Throwable ex) {
54+
log.error("Throwable thrown creating parent classloader", ex);
55+
}
56+
}
57+
}
58+
59+
private static synchronized void startDebuggingAgent(
60+
final Instrumentation inst, final URL bootstrapURL) {
61+
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
62+
try {
63+
final ClassLoader classLoader = getDebuggingClassloader(bootstrapURL);
64+
Thread.currentThread().setContextClassLoader(classLoader);
65+
final Class<?> debuggingAgentClass =
66+
classLoader.loadClass("com.datadog.debugging.agent.DebuggingAgent");
67+
final Method debuggingInstallerMethod =
68+
debuggingAgentClass.getMethod("run", Instrumentation.class);
69+
debuggingInstallerMethod.invoke(null, inst);
70+
} catch (final ClassFormatError e) {
71+
/*
72+
Debugging is compiled for Java8. Loading it on Java7 results in ClassFormatError
73+
(more specifically UnsupportedClassVersionError). Just ignore and continue when this happens.
74+
*/
75+
log.debug("Debugging requires OpenJDK 8 or above - skipping");
76+
} catch (final Throwable ex) {
77+
log.error("Throwable thrown while starting debugging agent", ex);
78+
} finally {
79+
Thread.currentThread().setContextClassLoader(contextLoader);
80+
}
81+
}
82+
83+
private static ClassLoader getDebuggingClassloader(final URL bootstrapURL) throws Exception {
84+
if (DEBUGGING_CLASSLOADER == null) {
85+
DEBUGGING_CLASSLOADER =
86+
createDelegateClassLoader("debugging", bootstrapURL, PARENT_CLASSLOADER);
87+
}
88+
return DEBUGGING_CLASSLOADER;
89+
}
90+
91+
/**
92+
* Create the datadog classloader. This must be called after the bootstrap jar has been appened to
93+
* the bootstrap classpath.
94+
*
95+
* @param innerJarFilename Filename of internal jar to use for the classpath of the datadog
96+
* classloader
97+
* @param bootstrapURL
98+
* @return Datadog Classloader
99+
*/
100+
private static ClassLoader createDatadogClassLoader(
101+
final String innerJarFilename, final URL bootstrapURL, final ClassLoader parent)
102+
throws Exception {
103+
104+
final Class<?> loaderClass =
105+
ClassLoader.getSystemClassLoader().loadClass("datadog.trace.bootstrap.DatadogClassLoader");
106+
final Constructor constructor =
107+
loaderClass.getDeclaredConstructor(
108+
URL.class, String.class, ClassLoader.class, ClassLoader.class);
109+
return (ClassLoader)
110+
constructor.newInstance(bootstrapURL, innerJarFilename, BOOTSTRAP_PROXY, parent);
111+
}
112+
113+
private static ClassLoader createDelegateClassLoader(
114+
final String innerJarFilename, final URL bootstrapURL, final ClassLoader parent)
115+
throws Exception {
116+
final Class<?> loaderClass =
117+
ClassLoader.getSystemClassLoader()
118+
.loadClass("datadog.trace.bootstrap.DatadogClassLoader$DelegateClassLoader");
119+
final Constructor constructor =
120+
loaderClass.getDeclaredConstructor(
121+
URL.class, String.class, ClassLoader.class, ClassLoader.class, ClassLoader.class);
122+
final ClassLoader classLoader =
123+
(ClassLoader)
124+
constructor.newInstance(
125+
bootstrapURL, innerJarFilename, BOOTSTRAP_PROXY, parent, PARENT_CLASSLOADER);
126+
return classLoader;
127+
}
128+
129+
private static ClassLoader getPlatformClassLoader()
130+
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
131+
/*
132+
Must invoke ClassLoader.getPlatformClassLoader by reflection to remain
133+
compatible with java 7 + 8.
134+
*/
135+
final Method method = ClassLoader.class.getDeclaredMethod("getPlatformClassLoader");
136+
return (ClassLoader) method.invoke(null);
137+
}
138+
}

0 commit comments

Comments
 (0)