From 82ccbb1e6a046792f16689d7fa1b723cb8bd88c0 Mon Sep 17 00:00:00 2001 From: Shane Killoran Date: Tue, 7 Dec 2021 10:43:44 -0800 Subject: [PATCH] JIT debug agent using ASM -This agent creates the hook needed to inject the code for running the debug agent tool Signed-off-by: Shane Killoran --- jit-debug-agent/DebugAgent.mf | 6 ++ jit-debug-agent/README.md | 29 ++++++++ jit-debug-agent/pom.xml | 65 +++++++++++++++++ jit-debug-agent/src/main/java/DebugAgent.java | 69 +++++++++++++++++++ .../src/main/java/DebugClassWriter.java | 47 +++++++++++++ .../src/main/java/EvaluateDebugAdapter.java | 48 +++++++++++++ .../src/main/java/ExpectDebugAdapter.java | 53 ++++++++++++++ .../src/main/java/InvokeDebugAdapter.java | 42 +++++++++++ .../src/main/java/MethodDebugAdapter.java | 53 ++++++++++++++ 9 files changed, 412 insertions(+) create mode 100644 jit-debug-agent/DebugAgent.mf create mode 100644 jit-debug-agent/README.md create mode 100644 jit-debug-agent/pom.xml create mode 100644 jit-debug-agent/src/main/java/DebugAgent.java create mode 100644 jit-debug-agent/src/main/java/DebugClassWriter.java create mode 100644 jit-debug-agent/src/main/java/EvaluateDebugAdapter.java create mode 100644 jit-debug-agent/src/main/java/ExpectDebugAdapter.java create mode 100644 jit-debug-agent/src/main/java/InvokeDebugAdapter.java create mode 100644 jit-debug-agent/src/main/java/MethodDebugAdapter.java diff --git a/jit-debug-agent/DebugAgent.mf b/jit-debug-agent/DebugAgent.mf new file mode 100644 index 0000000..a097a6f --- /dev/null +++ b/jit-debug-agent/DebugAgent.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +Premain-Class: DebugAgent +Agent-Class: DebugAgent +Class-path: asm-9.2.jar junit-4.12.jar +Can-Redefine-Classes: true +Can-Retransform-Classes: true diff --git a/jit-debug-agent/README.md b/jit-debug-agent/README.md new file mode 100644 index 0000000..64e5749 --- /dev/null +++ b/jit-debug-agent/README.md @@ -0,0 +1,29 @@ +# OpenJ9 JIT Debug Agent + +This tool automatically obtains a limited JIT trace log for a miscompiled method. It works by using preexistence +and a hooked internal API to repeatedly run a test by sequentially reverting JIT methods to be executed in the interpreter. +By repeatedly executing a test in a controlled environment we are able to devertmine which JIT method needs to be interpreted +for the test to start passing. This is how the tool determines which JIT method _may_ be responsible for the test case failure. + +Once the JIT method is identified the tool performs a `lastOptIndex` search by recompiling the JIT method at different +optimization levels. Once again, when the test starts passing we have determined the minimal `lastOptIndex` which causes the +failure. At that point the tool gather a _"good"_ and _"bad"_ JIT trace log with the optimization included and excluded for +JIT developers to investigate. + +# When to use this tool + +This tool should be used for highly intermittent JIT defects from automated testing environments where we are either not able +to determine the JIT method responsible for the failure, or we are unable to trace the suspect JIT method due to a Heisenbug. + +# How to use this tool + +1. Using maven and running the command `$ mvn install` in this directory will package the agent into a jar file, you will find it in the `target` directory. +2. add the java agent option to your `java` execution which would look like this: `-javaagent: jit-debug-agent-1.0.jar` +3. Run the test by forcing preexistence. + + Currently the tool is not robust enough to enable this automatically, so we have to force every JIT method compilation to use + preexistence. This is to ensure that we have a _revert to interpreter_ stub inserted in the preprologue of every method. + Inserting this stub happens at binary encoding and should not affect the semantics of the method itself. + + ``` + java -Xdump:none -Xnoaot -Xcheck:jni -Xjit:forceUsePreexistence -javaagent: jit-debug-agent-1.0.jar \ No newline at end of file diff --git a/jit-debug-agent/pom.xml b/jit-debug-agent/pom.xml new file mode 100644 index 0000000..23cb5f1 --- /dev/null +++ b/jit-debug-agent/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.openj9 + jit-debug-agent + 1.0 + + + + org.ow2.asm + asm + 9.2 + + + junit + junit + 4.12 + compile + + + + + 11 + 11 + + + + + + maven-assembly-plugin + + + + + DebugAgent + + + true + + + true + + + + + jar-with-dependencies + + false + + + + make-assembly + package + + single + + + + + + + \ No newline at end of file diff --git a/jit-debug-agent/src/main/java/DebugAgent.java b/jit-debug-agent/src/main/java/DebugAgent.java new file mode 100644 index 0000000..7273d63 --- /dev/null +++ b/jit-debug-agent/src/main/java/DebugAgent.java @@ -0,0 +1,69 @@ +{{- /******************************************************************************* + * Copyright (c) 2021, 2021 IBM Corp. and others + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which accompanies this + * distribution and is available at https://www.eclipse.org/legal/epl-2.0/ + * or the Apache License, Version 2.0 which accompanies this distribution and + * is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following + * Secondary Licenses when the conditions for such availability set + * forth in the Eclipse Public License, v. 2.0 are satisfied: GNU + * General Public License, version 2 with the GNU Classpath + * Exception [1] and GNU General Public License, version 2 with the + * OpenJDK Assembly Exception [2]. + * + * [1] https://www.gnu.org/software/classpath/license.html + * [2] http://openjdk.java.net/legal/assembly-exception.html + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception + *******************************************************************************/ -}} +import java.lang.instrument.*; +import java.security.ProtectionDomain; +import java.io.IOException; + +public class DebugAgent { + + private static String REFLECT_METHOD_NAME = "java/lang/reflect/Method"; + private static String EXPECT_EXCEPTION_NAME = "org/junit/internal/runners/statements/ExpectException"; + private static Class REFLECT_METHOD_CLASS = java.lang.reflect.Method.class; + private static Class EXPECT_EXCEPTION_CLASS = org.junit.internal.runners.statements.ExpectException.class; + private static String INVOKE = "invoke"; + private static String EVALUATE = "evaluate"; + + public static void premain(String args, Instrumentation inst) throws Exception { + inst.addTransformer(new ClassFileTransformer(){ + @Override + public byte[] transform( + ClassLoader l, + String name, + Class c, + ProtectionDomain d, + byte[] b + ) throws IllegalClassFormatException { + if(name.equals(REFLECT_METHOD_NAME)) { + try { + DebugClassWriter writer = new DebugClassWriter(c.getName()); + return writer.applyDebugger(INVOKE); + } catch (IOException e) { + System.out.println(e); + } + } + + if(name.equals(EXPECT_EXCEPTION_NAME)) { + try{ + DebugClassWriter writer = new DebugClassWriter(c.getName()); + return writer.applyDebugger(EVALUATE); + } catch (IOException e) { + System.out.println(e); + } + } + return b; + } + }, true); + + Class[] classesToTransform = {EXPECT_EXCEPTION_CLASS, REFLECT_METHOD_CLASS}; + inst.retransformClasses(classesToTransform); + } +} \ No newline at end of file diff --git a/jit-debug-agent/src/main/java/DebugClassWriter.java b/jit-debug-agent/src/main/java/DebugClassWriter.java new file mode 100644 index 0000000..a1e42c4 --- /dev/null +++ b/jit-debug-agent/src/main/java/DebugClassWriter.java @@ -0,0 +1,47 @@ +{{- /******************************************************************************* + * Copyright (c) 2021, 2021 IBM Corp. and others + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which accompanies this + * distribution and is available at https://www.eclipse.org/legal/epl-2.0/ + * or the Apache License, Version 2.0 which accompanies this distribution and + * is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following + * Secondary Licenses when the conditions for such availability set + * forth in the Eclipse Public License, v. 2.0 are satisfied: GNU + * General Public License, version 2 with the GNU Classpath + * Exception [1] and GNU General Public License, version 2 with the + * OpenJDK Assembly Exception [2]. + * + * [1] https://www.gnu.org/software/classpath/license.html + * [2] http://openjdk.java.net/legal/assembly-exception.html + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception + *******************************************************************************/ -}} +import org.objectweb.asm.*; +import java.io.IOException; + +public class DebugClassWriter { + private ClassReader reader; + private ClassWriter writer; + private static String INVOKE = "invoke"; + + + public DebugClassWriter(String className) throws IOException { + reader = new ClassReader(className); + writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES); + } + + public byte[] applyDebugger(String methodName){ + if (methodName == INVOKE) { + MethodDebugAdapter methodDebugAdapter = new MethodDebugAdapter(methodName, writer); + reader.accept(methodDebugAdapter, 0); + } else { + ExpectDebugAdapter expectDebugAdapter = new ExpectDebugAdapter(methodName, writer); + reader.accept(expectDebugAdapter, 0); + } + + return writer.toByteArray(); + } +} \ No newline at end of file diff --git a/jit-debug-agent/src/main/java/EvaluateDebugAdapter.java b/jit-debug-agent/src/main/java/EvaluateDebugAdapter.java new file mode 100644 index 0000000..2d3f557 --- /dev/null +++ b/jit-debug-agent/src/main/java/EvaluateDebugAdapter.java @@ -0,0 +1,48 @@ +{{- /******************************************************************************* + * Copyright (c) 2021, 2021 IBM Corp. and others + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which accompanies this + * distribution and is available at https://www.eclipse.org/legal/epl-2.0/ + * or the Apache License, Version 2.0 which accompanies this distribution and + * is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following + * Secondary Licenses when the conditions for such availability set + * forth in the Eclipse Public License, v. 2.0 are satisfied: GNU + * General Public License, version 2 with the GNU Classpath + * Exception [1] and GNU General Public License, version 2 with the + * OpenJDK Assembly Exception [2]. + * + * [1] https://www.gnu.org/software/classpath/license.html + * [2] http://openjdk.java.net/legal/assembly-exception.html + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception + *******************************************************************************/ -}} +import org.objectweb.asm.*; + +public class EvaluateDebugAdapter extends MethodVisitor { + + public EvaluateDebugAdapter(MethodVisitor mv) { + super(Opcodes.ASM9, mv); + this.mv = mv; + } + + @Override + public void visitCode() { + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, "org/junit/internal/runners/statements/ExpectException", "expected", "Ljava/lang/Class;"); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/ibm/jit/JITHelpers", "setExpectedException", "(Ljava/lang/Class;)V", false); + } + + @Override + public void visitLineNumber(int line, Label start){ + mv.visitLineNumber(line, start); + if (line == 31) { + mv.visitInsn(Opcodes.ACONST_NULL); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/ibm/jit/JITHelpers", "setExpectedException", "(Ljava/lang/Class;)V", false); + } + } + +} \ No newline at end of file diff --git a/jit-debug-agent/src/main/java/ExpectDebugAdapter.java b/jit-debug-agent/src/main/java/ExpectDebugAdapter.java new file mode 100644 index 0000000..741ed58 --- /dev/null +++ b/jit-debug-agent/src/main/java/ExpectDebugAdapter.java @@ -0,0 +1,53 @@ +{{- /******************************************************************************* + * Copyright (c) 2021, 2021 IBM Corp. and others + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which accompanies this + * distribution and is available at https://www.eclipse.org/legal/epl-2.0/ + * or the Apache License, Version 2.0 which accompanies this distribution and + * is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following + * Secondary Licenses when the conditions for such availability set + * forth in the Eclipse Public License, v. 2.0 are satisfied: GNU + * General Public License, version 2 with the GNU Classpath + * Exception [1] and GNU General Public License, version 2 with the + * OpenJDK Assembly Exception [2]. + * + * [1] https://www.gnu.org/software/classpath/license.html + * [2] http://openjdk.java.net/legal/assembly-exception.html + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception + *******************************************************************************/ -}} +import org.objectweb.asm.*; + +public class ExpectDebugAdapter extends ClassVisitor { + private String methodName; + private int access = org.objectweb.asm.Opcodes.ACC_PUBLIC; + + public ExpectDebugAdapter(String methodName, ClassVisitor cv) { + super(org.objectweb.asm.Opcodes.ASM9, cv); + this.cv = cv; + this.methodName = methodName; + } + + @Override + public MethodVisitor visitMethod( + int access, + String name, + String desc, + String signature, + String[] exceptions) { + MethodVisitor mv; + mv = cv.visitMethod( + access, + name, + desc, + signature, + exceptions); + if (name.equals(methodName) && mv != null) { + return new EvaluateDebugAdapter(mv); + } + return mv; + } +} \ No newline at end of file diff --git a/jit-debug-agent/src/main/java/InvokeDebugAdapter.java b/jit-debug-agent/src/main/java/InvokeDebugAdapter.java new file mode 100644 index 0000000..06458c5 --- /dev/null +++ b/jit-debug-agent/src/main/java/InvokeDebugAdapter.java @@ -0,0 +1,42 @@ +{{- /******************************************************************************* + * Copyright (c) 2021, 2021 IBM Corp. and others + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which accompanies this + * distribution and is available at https://www.eclipse.org/legal/epl-2.0/ + * or the Apache License, Version 2.0 which accompanies this distribution and + * is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following + * Secondary Licenses when the conditions for such availability set + * forth in the Eclipse Public License, v. 2.0 are satisfied: GNU + * General Public License, version 2 with the GNU Classpath + * Exception [1] and GNU General Public License, version 2 with the + * OpenJDK Assembly Exception [2]. + * + * [1] https://www.gnu.org/software/classpath/license.html + * [2] http://openjdk.java.net/legal/assembly-exception.html + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception + *******************************************************************************/ -}} +import org.objectweb.asm.*; + +public class InvokeDebugAdapter extends MethodVisitor { + public InvokeDebugAdapter(MethodVisitor mv) { + super(Opcodes.ASM9, mv); + } + + @Override + public void visitMethodInsn(int opcode, java.lang.String owner, java.lang.String name, java.lang.String descriptor, boolean isInterface) { + if(opcode==Opcodes.INVOKEINTERFACE){ + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + "com/ibm/jit/JITHelpers", + "invoke", + "(Ljdk/internal/reflect/MethodAccessor;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", + false); + } else { + mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + } +} \ No newline at end of file diff --git a/jit-debug-agent/src/main/java/MethodDebugAdapter.java b/jit-debug-agent/src/main/java/MethodDebugAdapter.java new file mode 100644 index 0000000..351ef0d --- /dev/null +++ b/jit-debug-agent/src/main/java/MethodDebugAdapter.java @@ -0,0 +1,53 @@ +{{- /******************************************************************************* + * Copyright (c) 2021, 2021 IBM Corp. and others + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which accompanies this + * distribution and is available at https://www.eclipse.org/legal/epl-2.0/ + * or the Apache License, Version 2.0 which accompanies this distribution and + * is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following + * Secondary Licenses when the conditions for such availability set + * forth in the Eclipse Public License, v. 2.0 are satisfied: GNU + * General Public License, version 2 with the GNU Classpath + * Exception [1] and GNU General Public License, version 2 with the + * OpenJDK Assembly Exception [2]. + * + * [1] https://www.gnu.org/software/classpath/license.html + * [2] http://openjdk.java.net/legal/assembly-exception.html + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception + *******************************************************************************/ -}} +import org.objectweb.asm.*; + +public class MethodDebugAdapter extends ClassVisitor { + private String methodName; + private int access = org.objectweb.asm.Opcodes.ACC_PUBLIC; + + public MethodDebugAdapter(String methodName, ClassVisitor cv) { + super(org.objectweb.asm.Opcodes.ASM9, cv); + this.cv = cv; + this.methodName = methodName; + } + + @Override + public MethodVisitor visitMethod( + int access, + String name, + String desc, + String signature, + String[] exceptions) { + MethodVisitor mv; + mv = cv.visitMethod( + access, + name, + desc, + signature, + exceptions); + if (name.equals(methodName) && mv != null) { + return new InvokeDebugAdapter(mv); + } + return mv; + } +} \ No newline at end of file