mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
debugger agent: implemented recursive async handling
This commit is contained in:
+106
-10
@@ -15,9 +15,10 @@
|
||||
*/
|
||||
package com.intellij.rt.debugger.agent;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import sun.misc.JavaLangAccess;
|
||||
import sun.misc.SharedSecrets;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author egor
|
||||
@@ -25,22 +26,117 @@ import java.util.Map;
|
||||
public class CaptureStorage {
|
||||
private static final int MAX_STORED_STACKS = 1000;
|
||||
|
||||
public static final Map<Object, Exception> STORAGE = Collections.synchronizedMap(new LinkedHashMap<Object, Exception>() {
|
||||
private static final Map<Object, CapturedStack> STORAGE = Collections.synchronizedMap(new LinkedHashMap<Object, CapturedStack>() {
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry eldest) {
|
||||
return size() > MAX_STORED_STACKS;
|
||||
}
|
||||
});
|
||||
private static final ThreadLocal<Deque<InsertMatch>> CURRENT_STACKS = ThreadLocal.withInitial(LinkedList::new);
|
||||
private static final JavaLangAccess ourJavaLangAccess = SharedSecrets.getJavaLangAccess();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void capture(Object key) {
|
||||
System.out.println("capture: " + key);
|
||||
Deque<InsertMatch> currentStacks = CURRENT_STACKS.get();
|
||||
CapturedStack stack;
|
||||
if (currentStacks.isEmpty()) {
|
||||
stack = new CapturedStack(new Throwable());
|
||||
}
|
||||
else {
|
||||
stack = new CapturedStack(new Throwable(), currentStacks.getLast());
|
||||
System.out.println("capture: insertion added");
|
||||
}
|
||||
STORAGE.put(key, stack);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void insertEnter(Object key) {
|
||||
System.out.println("insert ->: " + key);
|
||||
CapturedStack stack = STORAGE.get(key);
|
||||
Deque<InsertMatch> currentStacks = CURRENT_STACKS.get();
|
||||
if (stack != null) {
|
||||
currentStacks.add(new InsertMatch(stack, ourJavaLangAccess.getStackTraceDepth(new Throwable())));
|
||||
System.out.println("insert ->: stack saved (" + currentStacks.size() + ")");
|
||||
}
|
||||
else {
|
||||
currentStacks.add(InsertMatch.EMPTY);
|
||||
System.out.println("insert ->: no stack found (" + currentStacks.size() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void insertExit(Object key) {
|
||||
System.out.println("insert <-: " + key);
|
||||
Deque<InsertMatch> currentStacks = CURRENT_STACKS.get();
|
||||
if (currentStacks.isEmpty()) {
|
||||
System.out.println("insert error, empty stack: " + key);
|
||||
return;
|
||||
}
|
||||
currentStacks.removeLast();
|
||||
System.out.println("insert <-: stack removed (" + currentStacks.size() + ")");
|
||||
}
|
||||
|
||||
// TODO: may save space using a separate CapturedStack w/o replace stack when can
|
||||
private static class CapturedStack {
|
||||
private final Throwable myException;
|
||||
private final InsertMatch myInsertMatch;
|
||||
|
||||
public CapturedStack(Throwable exception) {
|
||||
this(exception, InsertMatch.EMPTY);
|
||||
}
|
||||
|
||||
public CapturedStack(Throwable exception, InsertMatch insertMatch) {
|
||||
myException = exception;
|
||||
myInsertMatch = insertMatch;
|
||||
}
|
||||
|
||||
List<StackTraceElement> getStackTrace() {
|
||||
StackTraceElement[] stackTrace = myException.getStackTrace();
|
||||
if (myInsertMatch == null || myInsertMatch == InsertMatch.EMPTY) {
|
||||
return Arrays.asList(stackTrace).subList(1, stackTrace.length);
|
||||
}
|
||||
else {
|
||||
List<StackTraceElement> insertStack = myInsertMatch.myStack.getStackTrace();
|
||||
int insertPos = stackTrace.length - myInsertMatch.myDepth + 2;
|
||||
ArrayList<StackTraceElement> res = new ArrayList<>(insertPos + insertStack.size() + 1);
|
||||
res.addAll(Arrays.asList(stackTrace).subList(1, insertPos));
|
||||
res.add(null);
|
||||
res.addAll(insertStack);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class InsertMatch {
|
||||
private final CapturedStack myStack;
|
||||
private final int myDepth;
|
||||
|
||||
static final InsertMatch EMPTY = new InsertMatch(null, 0);
|
||||
|
||||
private InsertMatch(CapturedStack stack, int depth) {
|
||||
myStack = stack;
|
||||
myDepth = depth;
|
||||
}
|
||||
}
|
||||
|
||||
// to be run from the debugger
|
||||
@SuppressWarnings("unused")
|
||||
public static Object[][] getRelatedStack(Object key) {
|
||||
Exception exception = STORAGE.get(key);
|
||||
StackTraceElement[] stackTrace = exception.getStackTrace();
|
||||
Object[][] res = new Object[stackTrace.length][];
|
||||
for (int i = 0; i < stackTrace.length; i++) {
|
||||
StackTraceElement elem = stackTrace[i];
|
||||
res[i] = new Object[]{elem.getClassName(), elem.getFileName(), elem.getMethodName(), String.valueOf(elem.getLineNumber())};
|
||||
CapturedStack stack = STORAGE.get(key);
|
||||
if (stack == null) {
|
||||
return null;
|
||||
}
|
||||
List<StackTraceElement> stackTrace = stack.getStackTrace();
|
||||
Object[][] res = new Object[stackTrace.size()][];
|
||||
for (int i = 0; i < stackTrace.size(); i++) {
|
||||
StackTraceElement elem = stackTrace.get(i);
|
||||
if (elem == null) {
|
||||
res[i] = null;
|
||||
}
|
||||
else {
|
||||
res[i] = new Object[]{elem.getClassName(), elem.getFileName(), elem.getMethodName(), String.valueOf(elem.getLineNumber())};
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
+137
-17
@@ -23,9 +23,11 @@ import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.instrument.UnmodifiableClassException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
@@ -33,12 +35,20 @@ import java.util.jar.JarFile;
|
||||
*/
|
||||
public class CaptureAgent {
|
||||
private static Instrumentation ourInstrumentation;
|
||||
|
||||
private static volatile Map<String, List<CapturePoint>> myCapturePoints = new HashMap<>();
|
||||
static {
|
||||
CapturePoint invokeLater = new CapturePoint("javax/swing/SwingUtilities", "invokeLater", 0);
|
||||
myCapturePoints.put(invokeLater.myClassName, Collections.singletonList(invokeLater));
|
||||
}
|
||||
|
||||
private static volatile Map<String, List<InsertPoint>> myInsertPoints = new HashMap<>();
|
||||
|
||||
static {
|
||||
InsertPoint invokeLater = new InsertPoint("java/awt/event/InvocationEvent", "dispatch", "runnable", "Ljava/lang/Runnable;");
|
||||
myInsertPoints.put(invokeLater.myClassName, Collections.singletonList(invokeLater));
|
||||
}
|
||||
|
||||
public static void premain(String args, Instrumentation instrumentation) throws IOException {
|
||||
ourInstrumentation = instrumentation;
|
||||
instrumentation.appendToBootstrapClassLoaderSearch(createTempJar("debugger-agent-storage.jar"));
|
||||
@@ -47,6 +57,10 @@ public class CaptureAgent {
|
||||
System.out.println("Capture agent: ready");
|
||||
}
|
||||
|
||||
private static <T> List<T> getNotNull(List<T> list) {
|
||||
return list != null ? list : Collections.emptyList();
|
||||
}
|
||||
|
||||
private static class CaptureTransformer implements ClassFileTransformer {
|
||||
@Override
|
||||
public byte[] transform(ClassLoader loader,
|
||||
@@ -54,14 +68,36 @@ public class CaptureAgent {
|
||||
Class<?> classBeingRedefined,
|
||||
ProtectionDomain protectionDomain,
|
||||
byte[] classfileBuffer) {
|
||||
List<CapturePoint> capturePoints = myCapturePoints.get(className);
|
||||
if (!capturePoints.isEmpty()) {
|
||||
List<CapturePoint> capturePoints = getNotNull(myCapturePoints.get(className));
|
||||
List<InsertPoint> insertPoints = getNotNull(myInsertPoints.get(className));
|
||||
if (!capturePoints.isEmpty() || !insertPoints.isEmpty()) {
|
||||
ClassReader reader = new ClassReader(classfileBuffer);
|
||||
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
|
||||
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
|
||||
for (CapturePoint point : capturePoints) {
|
||||
reader.accept(new CaptureInstrumentor(Opcodes.ASM6, writer, point), 0);
|
||||
try {
|
||||
reader.accept(new CaptureInstrumentor(Opcodes.ASM6, writer, point), 0);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return writer.toByteArray();
|
||||
for (InsertPoint point : insertPoints) {
|
||||
try {
|
||||
reader.accept(new InsertInstrumentor(Opcodes.ASM6, writer, point), 0);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
byte[] bytes = writer.toByteArray();
|
||||
|
||||
try {
|
||||
Path path = new File("instrumented_" + className.replaceAll("/", "_") + ".class").toPath();
|
||||
Files.write(path, bytes);
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -79,18 +115,13 @@ public class CaptureAgent {
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
if (capturePoint.myMethodName.equals(name)) {
|
||||
System.out.println("Capture agent: instrumented " + capturePoint.myClassName + "." + name);
|
||||
System.out.println("Capture agent: instrumented capture point at " + capturePoint.myClassName + "." + name);
|
||||
return new MethodVisitor(api, mv) {
|
||||
@Override
|
||||
public void visitCode() {
|
||||
visitFieldInsn(Opcodes.GETSTATIC, CaptureStorage.class.getName().replaceAll("\\.", "/"), "STORAGE", "Ljava/util/Map;");
|
||||
visitVarInsn(Opcodes.ALOAD, capturePoint.myParamSlotId);
|
||||
visitTypeInsn(Opcodes.NEW, "java/lang/Exception");
|
||||
visitInsn(Opcodes.DUP);
|
||||
visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Exception", "<init>", "()V", false);
|
||||
visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map", "put",
|
||||
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
|
||||
visitInsn(Opcodes.POP);
|
||||
visitMethodInsn(Opcodes.INVOKESTATIC, CaptureStorage.class.getName().replaceAll("\\.", "/"), "capture",
|
||||
"(Ljava/lang/Object;)V", false);
|
||||
super.visitCode();
|
||||
}
|
||||
};
|
||||
@@ -99,15 +130,104 @@ public class CaptureAgent {
|
||||
}
|
||||
}
|
||||
|
||||
private static class InsertInstrumentor extends ClassVisitor {
|
||||
private InsertPoint myInsertPoint;
|
||||
Supplier<MethodVisitor> myVisitMethod = null;
|
||||
String myDesc;
|
||||
|
||||
public InsertInstrumentor(int api, ClassVisitor cv, InsertPoint insertPoint) {
|
||||
super(api, cv);
|
||||
this.myInsertPoint = insertPoint;
|
||||
}
|
||||
|
||||
private static String getNewName(String name) {
|
||||
return name + "$$$capture";
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
if (myInsertPoint.myMethodName.equals(name)) {
|
||||
MethodVisitor mv = super.visitMethod(access, getNewName(name), desc, signature, exceptions);
|
||||
myDesc = desc;
|
||||
myVisitMethod = () -> super.visitMethod(access, name, desc, signature, exceptions);
|
||||
System.out.println("Capture agent: instrumented insert point at " + myInsertPoint.myClassName + "." + name);
|
||||
return mv;
|
||||
}
|
||||
return super.visitMethod(access, name, desc, signature, exceptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
if (myVisitMethod != null) {
|
||||
MethodVisitor mv = myVisitMethod.get();
|
||||
|
||||
Label start = new Label();
|
||||
mv.visitLabel(start);
|
||||
|
||||
insertEnter(mv);
|
||||
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
// TODO: mv.loadArgs();
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, myInsertPoint.myClassName, getNewName(myInsertPoint.myMethodName), myDesc, false);
|
||||
|
||||
Label end = new Label();
|
||||
mv.visitLabel(end);
|
||||
|
||||
// regular exit
|
||||
insertExit(mv);
|
||||
mv.visitInsn(Opcodes.RETURN);
|
||||
|
||||
Label catchLabel = new Label();
|
||||
mv.visitLabel(catchLabel);
|
||||
mv.visitTryCatchBlock(start, end, catchLabel, null);
|
||||
|
||||
// exception exit
|
||||
insertExit(mv);
|
||||
mv.visitInsn(Opcodes.ATHROW);
|
||||
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
}
|
||||
|
||||
private void insertEnter(MethodVisitor mv) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, myInsertPoint.myClassName, myInsertPoint.myField, myInsertPoint.myFieldDesc);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESTATIC, CaptureStorage.class.getName().replaceAll("\\.", "/"), "insertEnter",
|
||||
"(Ljava/lang/Object;)V", false);
|
||||
}
|
||||
|
||||
private void insertExit(MethodVisitor mv) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, myInsertPoint.myClassName, myInsertPoint.myField, myInsertPoint.myFieldDesc);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESTATIC, CaptureStorage.class.getName().replaceAll("\\.", "/"), "insertExit",
|
||||
"(Ljava/lang/Object;)V", false);
|
||||
}
|
||||
}
|
||||
|
||||
static class CapturePoint {
|
||||
final String myClassName;
|
||||
final String myMethodName;
|
||||
final int myParamSlotId;
|
||||
|
||||
public CapturePoint(String myClassName, String myMethodName, int myParamSlotId) {
|
||||
this.myClassName = myClassName;
|
||||
this.myMethodName = myMethodName;
|
||||
this.myParamSlotId = myParamSlotId;
|
||||
public CapturePoint(String className, String methodName, int paramSlotId) {
|
||||
this.myClassName = className;
|
||||
this.myMethodName = methodName;
|
||||
this.myParamSlotId = paramSlotId;
|
||||
}
|
||||
}
|
||||
|
||||
static class InsertPoint {
|
||||
final String myClassName;
|
||||
final String myMethodName;
|
||||
final String myField;
|
||||
final String myFieldDesc;
|
||||
|
||||
public InsertPoint(String className, String methodName, String field, String fieldDesc) {
|
||||
this.myClassName = className;
|
||||
this.myMethodName = methodName;
|
||||
this.myField = field;
|
||||
myFieldDesc = fieldDesc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-4
@@ -303,10 +303,15 @@ public class StackCapturingLineBreakpoint extends WildcardMethodBreakpoint {
|
||||
List<Value> values = ((ArrayReference)resArray).getValues();
|
||||
List<StackFrameItem> res = new ArrayList<>(values.size());
|
||||
for (Value value : values) {
|
||||
List<Value> values1 = ((ArrayReference)value).getValues();
|
||||
res.add(new ProcessStackFrameItem(getStringRefValue((StringReference)values1.get(0)),
|
||||
getStringRefValue((StringReference)values1.get(2)),
|
||||
Integer.parseInt(((StringReference)values1.get(3)).value())));
|
||||
if (value == null) {
|
||||
res.add(null);
|
||||
}
|
||||
else {
|
||||
List<Value> values1 = ((ArrayReference)value).getValues();
|
||||
res.add(new ProcessStackFrameItem(getStringRefValue((StringReference)values1.get(0)),
|
||||
getStringRefValue((StringReference)values1.get(2)),
|
||||
Integer.parseInt(((StringReference)values1.get(3)).value())));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -346,6 +351,11 @@ public class StackCapturingLineBreakpoint extends WildcardMethodBreakpoint {
|
||||
public String method() {
|
||||
return myMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myClass + "." + myMethod + ":" + myLine;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
Reference in New Issue
Block a user