debugger agent: pass settings file

This commit is contained in:
Egor.Ushakov
2017-10-03 21:10:04 +03:00
parent ae1a0ad7f2
commit 277da41761
4 changed files with 310 additions and 90 deletions
@@ -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<String, List<CapturePoint>> myCapturePoints = new HashMap<String, List<CapturePoint>>();
private static Map<String, List<InsertPoint>> myInsertPoints = new HashMap<String, List<InsertPoint>>();
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<CapturePoint> 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);
}
@@ -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;
@@ -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<AgentCapturePoint> CAPTURE_POINTS = new ArrayList<>();
private static final List<AgentInsertPoint> INSERT_POINTS = new ArrayList<>();
private static final List<CapturePoint> 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<AgentPoint> getCapturePoints() {
return Collections.unmodifiableList(CAPTURE_POINTS);
}
public static List<AgentPoint> getInsertPoints() {
return Collections.unmodifiableList(INSERT_POINTS);
}
public static List<CapturePoint> 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;
}
}
}
@@ -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<CapturePoint> 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));
}
}