From 277da417619c8d8aa832ee0dd1176f2028f3695d Mon Sep 17 00:00:00 2001 From: "Egor.Ushakov" Date: Tue, 3 Oct 2017 20:48:53 +0300 Subject: [PATCH] debugger agent: pass settings file --- .../rt/debugger/agent/CaptureAgent.java | 137 +++++++------- .../debugger/impl/DebuggerManagerImpl.java | 82 ++++++--- .../settings/CaptureSettingsProvider.java | 170 ++++++++++++++++++ .../StackCapturingLineBreakpoint.java | 11 +- 4 files changed, 310 insertions(+), 90 deletions(-) create mode 100644 java/debugger/impl/src/com/intellij/debugger/settings/CaptureSettingsProvider.java diff --git a/java/debugger/debugger-agent/src/com/intellij/rt/debugger/agent/CaptureAgent.java b/java/debugger/debugger-agent/src/com/intellij/rt/debugger/agent/CaptureAgent.java index 22b4ecaa0d24..b72a7dfc70f9 100644 --- a/java/debugger/debugger-agent/src/com/intellij/rt/debugger/agent/CaptureAgent.java +++ b/java/debugger/debugger-agent/src/com/intellij/rt/debugger/agent/CaptureAgent.java @@ -3,7 +3,9 @@ package com.intellij.rt.debugger.agent; import org.jetbrains.org.objectweb.asm.*; +import java.io.File; import java.io.FileOutputStream; +import java.io.FileReader; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; @@ -25,78 +27,68 @@ public class CaptureAgent { private static Map> myCapturePoints = new HashMap>(); private static Map> myInsertPoints = new HashMap>(); - static { - addCapturePoint("javax/swing/SwingUtilities", "invokeLater", new ParamKeyProvider(0)); - addInsertPoint("java/awt/event/InvocationEvent", "dispatch", - new FieldKeyProvider("java/awt/event/InvocationEvent", "runnable", "Ljava/lang/Runnable;")); - - addCapturePoint("java/lang/Thread", "start", THIS_KEY_PROVIDER); - addInsertPoint("java/lang/Thread", "run", THIS_KEY_PROVIDER); - - addCapturePoint("java/util/concurrent/ExecutorService", "submit", new ParamKeyProvider(1)); - addInsertPoint("java/util/concurrent/Executors$RunnableAdapter", "call", - new FieldKeyProvider("java/util/concurrent/Executors$RunnableAdapter", "task", "Ljava/lang/Runnable;")); - - addCapturePoint("java/util/concurrent/ThreadPoolExecutor", "execute", new ParamKeyProvider(1)); - addInsertPoint("java/util/concurrent/FutureTask", "run", THIS_KEY_PROVIDER); - - addCapturePoint("java/util/concurrent/CompletableFuture", "supplyAsync", new ParamKeyProvider(0)); - addInsertPoint("java/util/concurrent/CompletableFuture$AsyncSupply", "run", - new FieldKeyProvider("java/util/concurrent/CompletableFuture$AsyncSupply", "fn", "Ljava/util/function/Supplier;")); - - addCapturePoint("java/util/concurrent/CompletableFuture", "runAsync", new ParamKeyProvider(0)); - addInsertPoint("java/util/concurrent/CompletableFuture$AsyncRun", "run", - new FieldKeyProvider("java/util/concurrent/CompletableFuture$AsyncRun", "fn", "Ljava/lang/Runnable;")); - - addCapturePoint("java/util/concurrent/CompletableFuture", "thenAcceptAsync", new ParamKeyProvider(1)); - addInsertPoint("java/util/concurrent/CompletableFuture", "uniAccept", new ParamKeyProvider(2)); - //addInsertPoint("java/util/concurrent/CompletableFuture$UniAccept", "tryFire", - // new FieldKeyProvider("java/util/concurrent/CompletableFuture$UniAccept", "fn", "Ljava/util/function/Consumer;")); - - addCapturePoint("java/util/concurrent/CompletableFuture", "thenRunAsync", new ParamKeyProvider(1)); - addInsertPoint("java/util/concurrent/CompletableFuture", "uniRun", new ParamKeyProvider(2)); - } - public static void premain(String args, Instrumentation instrumentation) throws IOException { ourInstrumentation = instrumentation; - String asmPath = null; - if (args != null) { - String[] split = args.split(";"); - for (String s : split) { - if ("debug".equals(s)) { - DEBUG = true; - CaptureStorage.setDebug(true); + FileReader reader = null; + try { + reader = new FileReader(args); + Properties properties = new Properties(); + properties.load(reader); + + DEBUG = Boolean.parseBoolean(properties.getProperty("debug", "false")); + if (DEBUG) { + CaptureStorage.setDebug(true); + } + + if (Boolean.parseBoolean(properties.getProperty("disabled", "false"))) { + CaptureStorage.setEnabled(false); + } + + String asmPath = properties.getProperty("asm-lib"); + if (asmPath == null) { + System.out.println("Capture agent: asm path is not specified, exiting"); + return; + } + + Enumeration propNames = properties.propertyNames(); + while (propNames.hasMoreElements()) { + String propName = (String)propNames.nextElement(); + if (propName.startsWith("capture")) { + addPoint(true, properties.getProperty(propName)); } - else if ("disabled".equals(s)) { - CaptureStorage.setEnabled(false); - } - else { - asmPath = s; + else if (propName.startsWith("insert")) { + addPoint(false, properties.getProperty(propName)); } } - } - if (asmPath == null) { - System.out.println("Capture agent: asm path is not specified, exiting"); - return; - } - instrumentation.appendToSystemClassLoaderSearch(new JarFile(asmPath)); + instrumentation.appendToSystemClassLoaderSearch(new JarFile(asmPath)); - instrumentation.addTransformer(new CaptureTransformer()); - for (Class aClass : instrumentation.getAllLoadedClasses()) { - String name = aClass.getName().replaceAll("\\.", "/"); - if (myCapturePoints.containsKey(name) || myInsertPoints.containsKey(name)) { - try { - instrumentation.retransformClasses(aClass); - } - catch (UnmodifiableClassException e) { - e.printStackTrace(); + instrumentation.addTransformer(new CaptureTransformer()); + for (Class aClass : instrumentation.getAllLoadedClasses()) { + String name = aClass.getName().replaceAll("\\.", "/"); + if (myCapturePoints.containsKey(name) || myInsertPoints.containsKey(name)) { + try { + instrumentation.retransformClasses(aClass); + } + catch (UnmodifiableClassException e) { + e.printStackTrace(); + } } } + if (DEBUG) { + System.out.println("Capture agent: ready"); + } } - if (DEBUG) { - System.out.println("Capture agent: ready"); + catch (IOException e) { + System.out.println("Capture agent: unable to read settings"); + e.printStackTrace(); + } + finally { + if (reader != null) { + reader.close(); + } + new File(args).delete(); } } @@ -307,6 +299,17 @@ public class CaptureAgent { ourInstrumentation.retransformClasses(classes.toArray(new Class[0])); } + private static void addPoint(boolean capture, String line) { + String[] split = line.split(" "); + KeyProvider keyProvider = createKeyProvider(Arrays.copyOfRange(split, 2, split.length)); + if (capture) { + addCapturePoint(split[0], split[1], keyProvider); + } + else { + addInsertPoint(split[0], split[1], keyProvider); + } + } + private static void addCapturePoint(String className, String methodName, KeyProvider keyProvider) { List points = myCapturePoints.get(className); if (points == null) { @@ -325,6 +328,18 @@ public class CaptureAgent { points.add(new InsertPoint(className, methodName, keyProvider)); } + private static KeyProvider createKeyProvider(String[] line) { + if ("this".equals(line[0])) { + return THIS_KEY_PROVIDER; + } + try { + return new ParamKeyProvider(Integer.parseInt(line[0])); + } + catch (NumberFormatException ignored) { + } + return new FieldKeyProvider(line[0], line[1], line[2]); + } + private interface KeyProvider { void loadKey(MethodVisitor mv); } diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerManagerImpl.java b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerManagerImpl.java index f0c629f749bd..8ffa5546eba1 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerManagerImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerManagerImpl.java @@ -4,6 +4,7 @@ package com.intellij.debugger.impl; import com.intellij.debugger.*; import com.intellij.debugger.apiAdapters.TransportServiceWrapper; import com.intellij.debugger.engine.*; +import com.intellij.debugger.settings.CaptureSettingsProvider; import com.intellij.debugger.settings.DebuggerSettings; import com.intellij.debugger.ui.GetJPDADialog; import com.intellij.debugger.ui.breakpoints.BreakpointManager; @@ -36,6 +37,7 @@ import com.intellij.openapi.projectRoots.ex.JavaSdkUtil; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.WriteExternalException; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; @@ -53,6 +55,8 @@ import org.jetbrains.org.objectweb.asm.MethodVisitor; import javax.swing.*; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.*; import java.util.jar.Attributes; import java.util.stream.Stream; @@ -496,41 +500,69 @@ public class DebuggerManagerImpl extends DebuggerManagerEx implements Persistent private static void addDebuggerAgent(JavaParameters parameters) { if (Registry.is("debugger.capture.points.agent")) { - Sdk jdk = parameters.getJdk(); - String version = jdk != null ? JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION) : null; - if (version != null) { - JavaSdkVersion sdkVersion = JavaSdkVersion.fromVersionString(version); - if (sdkVersion != null && sdkVersion.isAtLeast(JavaSdkVersion.JDK_1_6)) { - File classesRoot = new File(PathUtil.getJarPathForClass(DebuggerManagerImpl.class)); - String agentName = "debugger-agent.jar"; - File agentFile; - if (classesRoot.isFile()) { - agentFile = new File(classesRoot.getParentFile(), "rt/" + agentName); - } - else { - agentFile = new File(classesRoot.getParentFile().getParentFile(), "/artifacts/debugger_agent/" + agentName); - } - if (agentFile.exists()) { - String agent = "-javaagent:" + agentFile + "=" + PathUtil.getJarPathForClass(MethodVisitor.class); - if (Registry.is("debugger.capture.points.agent.debug")) { - agent += ";debug"; + String prefix = "-javaagent:"; + String agentName = "debugger-agent.jar"; + ParametersList parametersList = parameters.getVMParametersList(); + if (parametersList.getParameters().stream().noneMatch(p -> p.startsWith(prefix) && p.contains(agentName))) { + Sdk jdk = parameters.getJdk(); + String version = jdk != null ? JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION) : null; + if (version != null) { + JavaSdkVersion sdkVersion = JavaSdkVersion.fromVersionString(version); + if (sdkVersion != null && sdkVersion.isAtLeast(JavaSdkVersion.JDK_1_6)) { + File classesRoot = new File(PathUtil.getJarPathForClass(DebuggerManagerImpl.class)); + File agentFile; + if (classesRoot.isFile()) { + agentFile = new File(classesRoot.getParentFile(), "rt/" + agentName); } - ParametersList parametersList = parameters.getVMParametersList(); - if (!parametersList.hasParameter(agent)) { - parametersList.add(agent); + else { + agentFile = new File(classesRoot.getParentFile().getParentFile(), "/artifacts/debugger_agent/" + agentName); + } + if (agentFile.exists()) { + parametersList.add(prefix + agentFile + "=" + generateAgentSettings()); + } + else { + LOG.warn("Capture agent not found: " + agentFile); } } else { - LOG.warn("Capture agent not found: " + agentFile); + LOG.warn("Capture agent is not supported for jre " + version); } } - else { - LOG.warn("Capture agent is not supported for jre " + version); - } } } } + private static String generateAgentSettings() { + Properties properties = new Properties(); + properties.setProperty("asm-lib", PathUtil.getJarPathForClass(MethodVisitor.class)); + if (Registry.is("debugger.capture.points.agent.debug")) { + properties.setProperty("debug", "true"); + } + int idx = 0; + for (CaptureSettingsProvider.AgentPoint point : CaptureSettingsProvider.getCapturePoints()) { + properties.setProperty("capture" + idx++, point.myClassName + CaptureSettingsProvider.AgentPoint.SEPARATOR + + point.myMethodName + CaptureSettingsProvider.AgentPoint.SEPARATOR + + point.myKey.asString()); + } + idx = 0; + for (CaptureSettingsProvider.AgentPoint point : CaptureSettingsProvider.getInsertPoints()) { + properties.setProperty("insert" + idx++, point.myClassName + CaptureSettingsProvider.AgentPoint.SEPARATOR + + point.myMethodName + CaptureSettingsProvider.AgentPoint.SEPARATOR + + point.myKey.asString()); + } + try { + File file = FileUtil.createTempFile("capture", ".props"); + try (FileOutputStream out = new FileOutputStream(file)) { + properties.store(out, null); + return file.getAbsolutePath(); + } + } + catch (IOException e) { + LOG.error(e); + } + return null; + } + private static boolean shouldForceNoJIT(Sdk jdk) { if (DebuggerSettings.getInstance().DISABLE_JIT) { return true; diff --git a/java/debugger/impl/src/com/intellij/debugger/settings/CaptureSettingsProvider.java b/java/debugger/impl/src/com/intellij/debugger/settings/CaptureSettingsProvider.java new file mode 100644 index 000000000000..15fa786e3fa3 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/settings/CaptureSettingsProvider.java @@ -0,0 +1,170 @@ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.settings; + +import com.intellij.debugger.jdi.DecompiledLocalVariable; +import one.util.streamex.StreamEx; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author egor + */ +public class CaptureSettingsProvider { + private static final List CAPTURE_POINTS = new ArrayList<>(); + private static final List INSERT_POINTS = new ArrayList<>(); + private static final List IDE_INSERT_POINTS; + + private static final KeyProvider THIS_KEY = new StringKeyProvider("this"); + + static { + CAPTURE_POINTS.add(new AgentCapturePoint("javax/swing/SwingUtilities", "invokeLater", new StringKeyProvider("0"))); + INSERT_POINTS.add(new AgentInsertPoint("java/awt/event/InvocationEvent", "dispatch", + new FieldKeyProvider("java/awt/event/InvocationEvent", "runnable", "Ljava/lang/Runnable;"))); + + CAPTURE_POINTS.add(new AgentCapturePoint("java/lang/Thread", "start", THIS_KEY)); + INSERT_POINTS.add(new AgentInsertPoint("java/lang/Thread", "run", THIS_KEY)); + + CAPTURE_POINTS.add(new AgentCapturePoint("java/util/concurrent/ExecutorService", "submit", new StringKeyProvider("1"))); + INSERT_POINTS.add(new AgentInsertPoint("java/util/concurrent/Executors$RunnableAdapter", "call", + new FieldKeyProvider("java/util/concurrent/Executors$RunnableAdapter", + "task", + "Ljava/lang/Runnable;"))); + + CAPTURE_POINTS.add(new AgentCapturePoint("java/util/concurrent/ThreadPoolExecutor", "execute", new StringKeyProvider("1"))); + INSERT_POINTS.add(new AgentInsertPoint("java/util/concurrent/FutureTask", "run", THIS_KEY)); + + CAPTURE_POINTS.add(new AgentCapturePoint("java/util/concurrent/CompletableFuture", "supplyAsync", new StringKeyProvider("0"))); + + CapturePoint ideInsertPoint = new CapturePoint(); + ideInsertPoint.myInsertClassName = "java.util.concurrent.CompletableFuture$AsyncSupply"; + ideInsertPoint.myInsertMethodName = "run$$$capture"; + ideInsertPoint.myInsertKeyExpression = "f"; + INSERT_POINTS.add(new AgentInsertPoint("java/util/concurrent/CompletableFuture$AsyncSupply", "run", + new FieldKeyProvider("java/util/concurrent/CompletableFuture$AsyncSupply", + "fn", + "Ljava/util/function/Supplier;"), + ideInsertPoint)); + + CAPTURE_POINTS.add(new AgentCapturePoint("java/util/concurrent/CompletableFuture", "runAsync", new StringKeyProvider("0"))); + ideInsertPoint = new CapturePoint(); + ideInsertPoint.myInsertClassName = "java.util.concurrent.CompletableFuture$AsyncRun"; + ideInsertPoint.myInsertMethodName = "run$$$capture"; + ideInsertPoint.myInsertKeyExpression = "f"; + INSERT_POINTS.add(new AgentInsertPoint("java/util/concurrent/CompletableFuture$AsyncRun", + "run", + new FieldKeyProvider("java/util/concurrent/CompletableFuture$AsyncRun", + "fn", + "Ljava/lang/Runnable;"), + ideInsertPoint)); + + CAPTURE_POINTS.add(new AgentCapturePoint("java/util/concurrent/CompletableFuture", "thenAcceptAsync", new StringKeyProvider("1"))); + INSERT_POINTS.add(new AgentInsertPoint("java/util/concurrent/CompletableFuture$UniAccept", "tryFire", + new FieldKeyProvider("java/util/concurrent/CompletableFuture$UniAccept", + "fn", + "Ljava/util/function/Consumer;"))); + + CAPTURE_POINTS.add(new AgentCapturePoint("java/util/concurrent/CompletableFuture", "thenRunAsync", new StringKeyProvider("1"))); + INSERT_POINTS.add(new AgentInsertPoint("java/util/concurrent/CompletableFuture$UniRun", "tryFire", + new FieldKeyProvider("java/util/concurrent/CompletableFuture$UniRun", + "fn", + "Ljava/lang/Runnable;"))); + + IDE_INSERT_POINTS = StreamEx.of(INSERT_POINTS).map(p -> p.myInsertPoint).nonNull().toList(); + } + + public static List getCapturePoints() { + return Collections.unmodifiableList(CAPTURE_POINTS); + } + + public static List getInsertPoints() { + return Collections.unmodifiableList(INSERT_POINTS); + } + + public static List getIdeInsertPoints() { + return Collections.unmodifiableList(IDE_INSERT_POINTS); + } + + public static class AgentPoint { + public final String myClassName; + public final String myMethodName; + public final KeyProvider myKey; + + public static final String SEPARATOR = " "; + + public AgentPoint(String className, String methodName, KeyProvider key) { + myClassName = className; + myMethodName = methodName; + myKey = key; + } + } + + public static class AgentCapturePoint extends AgentPoint { + public AgentCapturePoint(String className, String methodName, KeyProvider key) { + super(className, methodName, key); + } + } + + public static class AgentInsertPoint extends AgentPoint { + public final CapturePoint myInsertPoint; // for IDE + + public AgentInsertPoint(String className, String methodName, KeyProvider key) { + super(className, methodName, key); + this.myInsertPoint = new CapturePoint(); + myInsertPoint.myInsertClassName = className.replaceAll("/", "."); + myInsertPoint.myInsertMethodName = methodName; + if (myKey instanceof FieldKeyProvider) { + myInsertPoint.myInsertKeyExpression = ((FieldKeyProvider)myKey).myFieldName; + } + else { + String keyStr = key.asString(); + try { + myInsertPoint.myInsertKeyExpression = DecompiledLocalVariable.PARAM_PREFIX + Integer.parseInt(keyStr); + } + catch (NumberFormatException ignored) { + myInsertPoint.myInsertKeyExpression = keyStr; + } + } + } + + public AgentInsertPoint(String className, String methodName, KeyProvider key, CapturePoint point) { + super(className, methodName, key); + this.myInsertPoint = point; + } + } + + public interface KeyProvider { + String asString(); + } + + private static class StringKeyProvider implements KeyProvider { + private final String myValue; + + public StringKeyProvider(String value) { + myValue = value; + } + + @Override + public String asString() { + return myValue; + } + } + + private static class FieldKeyProvider implements KeyProvider { + private final String myClassName; + private final String myFieldName; + private final String myFieldDesc; + + public FieldKeyProvider(String className, String fieldName, String fieldDesc) { + myClassName = className; + myFieldName = fieldName; + myFieldDesc = fieldDesc; + } + + @Override + public String asString() { + return myClassName + AgentPoint.SEPARATOR + myFieldName + AgentPoint.SEPARATOR + myFieldDesc; + } + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StackCapturingLineBreakpoint.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StackCapturingLineBreakpoint.java index f00ba0d9f008..a2d7202bb531 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StackCapturingLineBreakpoint.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StackCapturingLineBreakpoint.java @@ -1,6 +1,4 @@ -// Copyright 2000-2017 JetBrains s.r.o. -// Use of this source code is governed by the Apache 2.0 license that can be -// found in the LICENSE file. +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.debugger.ui.breakpoints; import com.intellij.debugger.DebuggerBundle; @@ -18,6 +16,7 @@ import com.intellij.debugger.jdi.StackFrameProxyImpl; import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; import com.intellij.debugger.memory.utils.StackFrameItem; import com.intellij.debugger.settings.CapturePoint; +import com.intellij.debugger.settings.CaptureSettingsProvider; import com.intellij.debugger.settings.DebuggerSettings; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; @@ -150,7 +149,11 @@ public class StackCapturingLineBreakpoint extends WildcardMethodBreakpoint { public static void createAll(DebugProcessImpl debugProcess) { DebuggerManagerThreadImpl.assertIsManagerThread(); if (Registry.is("debugger.capture.points")) { - DebuggerSettings.getInstance().getCapturePoints().stream().filter(c -> c.myEnabled).forEach(c -> track(debugProcess, c)); + StreamEx points = StreamEx.of(DebuggerSettings.getInstance().getCapturePoints()).filter(c -> c.myEnabled); + if (Registry.is("debugger.capture.points.agent")) { + points = points.append(CaptureSettingsProvider.getIdeInsertPoints()); + } + points.forEach(c -> track(debugProcess, c)); } }