All Downloads are FREE. Search and download functionalities are using the official Maven repository.

datadog.trace.bootstrap.AgentBootstrap Maven / Gradle / Ivy

There is a newer version: 1.39.0
Show newest version
package datadog.trace.bootstrap;

import de.thetaphi.forbiddenapis.SuppressForbidden;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.CodeSource;
import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Entry point for initializing the agent.
 *
 * 

The bootstrap process of the agent is somewhat complicated and care has to be taken to make * sure things do not get broken by accident. * *

JVM loads this class onto app's classloader, afterwards agent needs to inject its classes onto * bootstrap classpath. This leads to this class being visible on bootstrap. This in turn means that * this class may be loaded again on bootstrap by accident if we ever reference it after bootstrap * has been setup. * *

In order to avoid this we need to make sure we do a few things: * *

    *
  • Do as little as possible here *
  • Never reference this class after we have setup bootstrap and jumped over to 'real' agent * code *
  • Do not store any static data in this class *
  • Do dot touch any logging facilities here so we can configure them later *
*/ public final class AgentBootstrap { private static final Class thisClass = AgentBootstrap.class; private static final int MAX_EXCEPTION_CHAIN_LENGTH = 99; private static boolean initialized = false; public static void premain(final String agentArgs, final Instrumentation inst) { agentmain(agentArgs, inst); } public static void agentmain(final String agentArgs, final Instrumentation inst) { if (alreadyInitialized() || lessThanJava8() || isJdkTool()) { return; } try { final URL agentJarURL = installAgentJar(inst); final Class agentClass = Class.forName("datadog.trace.bootstrap.Agent", true, null); if (agentClass.getClassLoader() != null) { throw new IllegalStateException("DD Java Agent NOT added to bootstrap classpath."); } final Method startMethod = agentClass.getMethod("start", Instrumentation.class, URL.class, String.class); startMethod.invoke(null, inst, agentJarURL, agentArgs); } catch (final Throwable ex) { if (exceptionCauseChainContains( ex, "datadog.trace.util.throwable.FatalAgentMisconfigurationError")) { throw new Error(ex); } // Don't rethrow. We don't have a log manager here, so just print. System.err.println("ERROR " + thisClass.getName()); ex.printStackTrace(); } } static boolean exceptionCauseChainContains(Throwable ex, String exClassName) { Set stack = Collections.newSetFromMap(new IdentityHashMap()); Throwable t = ex; while (t != null && stack.add(t) && stack.size() <= MAX_EXCEPTION_CHAIN_LENGTH) { // cannot do an instanceof check since most of the agent's code is loaded by an isolated CL if (t.getClass().getName().equals(exClassName)) { return true; } t = t.getCause(); } return false; } private static boolean lessThanJava8() { return lessThanJava8(System.getProperty("java.version"), System.out); } // Reachable for testing static boolean lessThanJava8(String version, PrintStream output) { if (parseJavaMajorVersion(version) < 8) { String agentVersion = "This version"; // If we can't find the agent version try { agentVersion = AgentJar.getAgentVersion(); agentVersion = "Version " + agentVersion; } catch (IOException ignored) { } output.println( "Warning: " + agentVersion + " of dd-java-agent is not compatible with Java " + version + " and will not be installed."); output.println( "Please upgrade your Java version to 8+ or use the 0.x version of dd-java-agent in your build tool or download it from https://dtdg.co/java-tracer-v0"); return true; } return false; } private static boolean alreadyInitialized() { if (initialized) { System.out.println( "Warning: dd-java-agent is being initialized more than once. Please check that you are defining -javaagent:dd-java-agent.jar only once."); return true; } initialized = true; return false; } private static boolean isJdkTool() { String moduleMain = System.getProperty("jdk.module.main"); if (null != moduleMain && !moduleMain.isEmpty() && moduleMain.charAt(0) == 'j') { switch (moduleMain) { case "java.base": // keytool case "java.corba": case "java.desktop": case "java.rmi": case "java.scripting": case "java.security.jgss": case "jdk.aot": case "jdk.compiler": case "jdk.dev": case "jdk.hotspot.agent": case "jdk.httpserver": case "jdk.jartool": case "jdk.javadoc": case "jdk.jcmd": case "jdk.jconsole": case "jdk.jdeps": case "jdk.jdi": case "jdk.jfr": case "jdk.jlink": case "jdk.jpackage": case "jdk.jshell": case "jdk.jstatd": case "jdk.jvmstat.rmi": case "jdk.pack": case "jdk.pack200": case "jdk.policytool": case "jdk.rmic": case "jdk.scripting.nashorn.shell": case "jdk.xml.bind": case "jdk.xml.ws": return true; } } return false; } // Reachable for testing static int parseJavaMajorVersion(String version) { int major = 0; if (null == version || version.isEmpty()) { return major; } int start = 0; if (version.charAt(0) == '1' && version.length() >= 3 && version.charAt(1) == '.' && Character.isDigit(version.charAt(2))) { start = 2; } // Parse the major digit and be a bit lenient, allowing digits followed by any non digit for (int i = start; i < version.length(); i++) { char c = version.charAt(i); if (Character.isDigit(c)) { major *= 10; major += Character.digit(c, 10); } else { break; } } return major; } public static void main(final String[] args) { if (lessThanJava8()) { return; } AgentJar.main(args); } private static synchronized URL installAgentJar(final Instrumentation inst) throws IOException, URISyntaxException { // First try Code Source final CodeSource codeSource = thisClass.getProtectionDomain().getCodeSource(); if (codeSource != null) { URL ddJavaAgentJarURL = codeSource.getLocation(); if (ddJavaAgentJarURL != null) { final File ddJavaAgentJarPath = new File(ddJavaAgentJarURL.toURI()); if (!ddJavaAgentJarPath.isDirectory()) { return appendAgentToBootstrapClassLoaderSearch( inst, ddJavaAgentJarURL, ddJavaAgentJarPath); } } } System.out.println("Could not get bootstrap jar from code source, using -javaagent arg"); File javaagentFile = getAgentFileFromJavaagentArg(inst); if (javaagentFile != null) { URL ddJavaAgentJarURL = javaagentFile.toURI().toURL(); return appendAgentToBootstrapClassLoaderSearch(inst, ddJavaAgentJarURL, javaagentFile); } System.out.println( "Could not get agent jar from -javaagent arg, using ClassLoader#getResource"); javaagentFile = getAgentFileUsingClassLoaderLookup(); if (!javaagentFile.isDirectory()) { URL ddJavaAgentJarURL = javaagentFile.toURI().toURL(); return appendAgentToBootstrapClassLoaderSearch(inst, ddJavaAgentJarURL, javaagentFile); } throw new IllegalStateException( "Could not determine agent jar location, not installing tracing agent"); } private static URL appendAgentToBootstrapClassLoaderSearch( Instrumentation inst, URL ddJavaAgentJarURL, File javaagentFile) throws IOException { checkJarManifestMainClassIsThis(ddJavaAgentJarURL); inst.appendToBootstrapClassLoaderSearch(new JarFile(javaagentFile)); return ddJavaAgentJarURL; } private static File getAgentFileFromJavaagentArg(Instrumentation inst) throws IOException { URL ddJavaAgentJarURL; // ManagementFactory indirectly references java.util.logging.LogManager // - On Oracle-based JDKs after 1.8 // - On IBM-based JDKs since at least 1.7 // This prevents custom log managers from working correctly // Use reflection to bypass the loading of the class final List arguments = getVMArgumentsThroughReflection(); String agentArgument = null; for (final String arg : arguments) { if (arg.startsWith("-javaagent")) { if (agentArgument == null) { agentArgument = arg; } else { System.out.println( "Could not get bootstrap jar from -javaagent arg: multiple javaagents specified"); return null; } } } if (agentArgument == null) { System.out.println("Could not get bootstrap jar from -javaagent arg: no argument specified"); return null; } // argument is of the form -javaagent:/path/to/dd-java-agent.jar=optionalargumentstring final Matcher matcher = Pattern.compile("-javaagent:([^=]+).*").matcher(agentArgument); if (!matcher.matches()) { System.out.println( "Could not get bootstrap jar from -javaagent arg: unable to parse javaagent parameter: " + agentArgument); return null; } final File javaagentFile = new File(matcher.group(1)); if (!(javaagentFile.exists() || javaagentFile.isFile())) { System.out.println( "Could not get bootstrap jar from -javaagent arg: unable to find javaagent file: " + javaagentFile); return null; } return javaagentFile; } @SuppressForbidden private static File getAgentFileUsingClassLoaderLookup() throws URISyntaxException { File javaagentFile; URL thisClassUrl; String thisClassResourceName = thisClass.getName().replace('.', '/') + ".class"; ClassLoader classLoader = thisClass.getClassLoader(); if (classLoader == null) { thisClassUrl = ClassLoader.getSystemResource(thisClassResourceName); } else { thisClassUrl = classLoader.getResource(thisClassResourceName); } if (thisClassUrl == null) { throw new IllegalStateException( "Could not locate agent bootstrap class resource, not installing tracing agent"); } javaagentFile = new File(new URI(thisClassUrl.getFile().split("!")[0])); return javaagentFile; } @SuppressForbidden private static List getVMArgumentsThroughReflection() { try { // Try Oracle-based // IBM Semeru Runtime 1.8.0_345-b01 will throw UnsatisfiedLinkError here. final Class managementFactoryHelperClass = Class.forName("sun.management.ManagementFactoryHelper"); final Class vmManagementClass = Class.forName("sun.management.VMManagement"); Object vmManagement; try { vmManagement = managementFactoryHelperClass.getDeclaredMethod("getVMManagement").invoke(null); } catch (final NoSuchMethodException e) { // Older vm before getVMManagement() existed final Field field = managementFactoryHelperClass.getDeclaredField("jvm"); field.setAccessible(true); vmManagement = field.get(null); field.setAccessible(false); } return (List) vmManagementClass.getMethod("getVmArguments").invoke(vmManagement); } catch (final ReflectiveOperationException | UnsatisfiedLinkError e) { try { // Try IBM-based. final Class VMClass = Class.forName("com.ibm.oti.vm.VM"); final String[] argArray = (String[]) VMClass.getMethod("getVMArgs").invoke(null); return Arrays.asList(argArray); } catch (final ReflectiveOperationException e1) { // Fallback to default System.out.println( "WARNING: Unable to get VM args through reflection. A custom java.util.logging.LogManager may not work correctly"); return ManagementFactory.getRuntimeMXBean().getInputArguments(); } } } private static boolean checkJarManifestMainClassIsThis(final URL jarUrl) throws IOException { final URL manifestUrl = new URL("jar:" + jarUrl + "!/META-INF/MANIFEST.MF"); final String mainClassLine = "Main-Class: " + thisClass.getCanonicalName(); try (final BufferedReader reader = new BufferedReader( new InputStreamReader(manifestUrl.openStream(), StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { if (line.equals(mainClassLine)) { return true; } } } throw new IllegalStateException( "dd-java-agent is not installed, because class '" + thisClass.getCanonicalName() + "' is located in '" + jarUrl + "'. Make sure you don't have this .class-file anywhere, besides dd-java-agent.jar"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy