From 32afc17d273a2b28fada1611972db0536784b6b0 Mon Sep 17 00:00:00 2001 From: LeoDefossez Date: Mon, 7 Jul 2025 15:35:47 +0200 Subject: [PATCH] adding a first idea to make multiple breakpoint in the extracting process --- src/main/java/attach/BreakPointInstaller.java | 130 +++++++++++ src/main/java/attach/JDIAttach.java | 203 +++++++----------- .../java/extractors/CallStackExtractor.java | 25 --- .../java/extractors/StackFrameExtractor.java | 48 +++-- 4 files changed, 229 insertions(+), 177 deletions(-) create mode 100644 src/main/java/attach/BreakPointInstaller.java delete mode 100644 src/main/java/extractors/CallStackExtractor.java diff --git a/src/main/java/attach/BreakPointInstaller.java b/src/main/java/attach/BreakPointInstaller.java new file mode 100644 index 0000000..5964e05 --- /dev/null +++ b/src/main/java/attach/BreakPointInstaller.java @@ -0,0 +1,130 @@ +package attach; + +import java.util.List; + +import com.sun.jdi.ClassNotLoadedException; +import com.sun.jdi.ClassType; +import com.sun.jdi.Location; +import com.sun.jdi.Method; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.Type; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.request.BreakpointRequest; +import com.sun.jdi.request.EventRequestManager; + +public class BreakPointInstaller { + + public static BreakPointInstaller instance = new BreakPointInstaller(); + + private BreakPointInstaller() { + + } + + public boolean addBreakpoint(VirtualMachine vm, Method method) { + return addBreakpoint(vm, method.location().declaringType().name(), method.name(), method.argumentTypeNames()); + } + + /** + * Add a breakpoint at a specified method on the given VM + * + * @param vm the VM + * @param className the name of the class + * @param methodName the name of the method + * @return true iff the breakpoint is successfully added + */ + public boolean addBreakpoint(VirtualMachine vm, String className, String methodName) { + return this.addBreakpoint(vm, className, methodName, null); + } + + /** + * Add a breakpoint at a specified method on the given VM Precise the method argument type names in case there is multiple method having the same + * name + * + * @param vm the VM + * @param className the name of the class where the method is situated + * @param methodName the name of the method + * @param methodArguments name of all arguments type of the method in the declaration order + */ + public boolean addBreakpoint(VirtualMachine vm, String className, String methodName, List methodArguments) { + try { + // Getting the EventRequestManager of the VirtualMachine + EventRequestManager requestManager = vm.eventRequestManager(); + + // Getting the method, adapting the research depending of the amount of information given + Method method = findMethod(vm, className, methodName, methodArguments); + + // Getting the location of the method + Location location = method.location(); + + // Creating the breakpoint at the wanted location + BreakpointRequest breakpointRequest = requestManager.createBreakpointRequest(location); + breakpointRequest.enable(); // activate the breakpoint + breakpointRequest.addCountFilter(1); + + } catch (Exception e) { + return false; + } + return true; + } + + /** + * Find a method matching the given characteristics in the Virtual Machine + * + * @param vm the Virtual Machine + * @param className the name of the class where the method is situated + * @param methodName name of the searched method + * @param methodArguments name of all arguments type of the method in the declaration order + * @return the method if one match + * @throws ClassNotLoadedException if no method matches the characteristics + */ + private Method findMethod(VirtualMachine vm, String className, String methodName, List methodArguments) throws ClassNotLoadedException { + // finding the class + // TODO can only find classes in the JDK + List classes = vm.classesByName(className); + if (classes.isEmpty()) { + throw new IllegalArgumentException("Class not found : " + className); + } + ClassType classType = (ClassType) classes.get(0); + + // getting all the methods with the searched name in the class + List allMethods = classType.methodsByName(methodName); + + // if no method found throw an exception + if (allMethods.isEmpty()) { + throw new IllegalArgumentException("No method named " + methodName + " in class " + className); + } + + // if no arguments given, either we only have one method found and that's fine + // or we have multiple results, we don't make a random choice, we just throw an exception + if (methodArguments == null) { + if (allMethods.size() != 1) { + throw new IllegalArgumentException("Multiple methods named " + methodName + " in class " + className); + } + return allMethods.get(0); + } + + // if we have multiple methods and given arguments types, we search if one correspond + for (Method m : allMethods) { + List paramTypes = m.argumentTypes(); + // if not the same number of types just pass this method + if (paramTypes.size() != methodArguments.size()) { + continue; + } + // starting on the hypothesis that we found the method, and trying to invalidate it + boolean matches = true; + for (int i = 0; i < paramTypes.size(); i++) { + if (!paramTypes.get(i).name().equals(methodArguments.get(i))) { + matches = false; + break; + } + } + // if we can't invalidate the method, then we found it + if (matches) { + return m; + } + } + // if we got here, then no method have been found + throw new IllegalArgumentException("No method named " + methodName + " in class " + className + " with argument types: " + methodArguments); + } + +} diff --git a/src/main/java/attach/JDIAttach.java b/src/main/java/attach/JDIAttach.java index 7a734e2..defc420 100644 --- a/src/main/java/attach/JDIAttach.java +++ b/src/main/java/attach/JDIAttach.java @@ -2,10 +2,8 @@ import com.sun.jdi.*; import com.sun.jdi.connect.*; -import com.sun.jdi.request.BreakpointRequest; -import com.sun.jdi.request.EventRequestManager; -import extractors.CallStackExtractor; +import extractors.StackFrameExtractor; import java.io.IOException; import java.net.ConnectException; @@ -14,11 +12,11 @@ public class JDIAttach { - // TODO divide the method extractCallStack and move it into the main to make more sense out of it public static void main(String[] args) throws Exception { // setting the variable that could become argument of the program String host = "localhost"; - String port = "5006"; + String port1 = "5005"; + String port2 = "5006"; String threadName = "main"; // name of the method creating the callStack @@ -27,7 +25,40 @@ public static void main(String[] args) throws Exception { String methodName = "exec"; List methodArguments = Arrays.asList("java.lang.String"); // can be null if there's no name repetition - extractCallStack(host, port, className, methodName, methodArguments, threadName); + VirtualMachine vm1 = attachToJVM(host, port1); + VirtualMachine vm2 = attachToJVM(host, port2); + + // finding all the lines on the call stack to make an analysis on every line + // this permit to know changes on ObjectReferences between the call stack's lines + List allFramesMethods = getAllFramesMethods(vm1, className, methodName, methodArguments, threadName); + + Iterator it = allFramesMethods.iterator(); + + addNextValidBreakpoint(vm2, it); + //extract the call stack and give the breakpoints to add + extractCallStack(vm2, threadName, it); + } + + private static List getAllFramesMethods(VirtualMachine vm, String className, String methodName, List methodArguments, + String threadName) throws IllegalConnectorArgumentsException, IOException, IncompatibleThreadStateException { + List allFramesMethods = new ArrayList<>(); + + // adding the breakpoint + BreakPointInstaller.instance.addBreakpoint(vm, className, methodName, methodArguments); + + // Searching for the wanted thread + ThreadReference main = collectThread(threadName, vm); + + // waiting for the thread to either finish or stop at a breakpoint + if (!encounterABreakpoint(main)) { + throw new IllegalStateException("Thread has not encounter a breakpoint"); + } + + for (StackFrame frame : main.frames()) { + allFramesMethods.add(frame.location().method()); + } + + return allFramesMethods; } /** @@ -39,32 +70,45 @@ public static void main(String[] args) throws Exception { * @param methodName name of the searched method * @param methodArguments name of all arguments of the method in the declaration order * @param threadName name of the thread to study + * @param iterator * @throws IOException when unable to attach to a VM * @throws IllegalConnectorArgumentsException if no connector socket can be used to attach to the VM + * @throws IncompatibleThreadStateException */ - private static void extractCallStack(String host, String port, String className, String methodName, List methodArguments, - String threadName) throws IOException, IllegalConnectorArgumentsException { - VirtualMachine vm = attachToJVM(host, port); - - // adding the breakpoint - addBreakpoint(vm, className, methodName, methodArguments); + private static void extractCallStack(VirtualMachine vm, String threadName, Iterator iterator) + throws IOException, IllegalConnectorArgumentsException, IncompatibleThreadStateException { // Searching for the wanted thread - ThreadReference main = null; - for (ThreadReference thread : vm.allThreads()) { - if (thread.name().equals(threadName)) { - main = thread; - break; + ThreadReference main = collectThread(threadName, vm); + + int i = 0; + while (encounterABreakpoint(main)) { + // extract the frame + while (i < main.frameCount()) { + + System.out.println("---- Line " + i + " of the call stack ----"); + //(new StackFrameExtractor()).extract(main.frames().get(i)); + + i++; } + //try adding the next possible breakpoint + addNextValidBreakpoint(vm, iterator); } - if (main == null) { - throw new IllegalStateException("No thread nammed " + threadName + "was found"); + + vm.dispose(); // properly disconnecting + } + + private static void addNextValidBreakpoint(VirtualMachine vm, Iterator iterator) { + while(iterator.hasNext() & BreakPointInstaller.instance.addBreakpoint(vm,iterator.next())) { + //TODO try removing this loop and only adding possible breakpoints } + } + + private static boolean encounterABreakpoint(ThreadReference main) { // resuming the process of the main main.resume(); - // waiting for the thread to either finish or stop at a breakpoint while (!(main.status() == ThreadReference.THREAD_STATUS_ZOMBIE || main.isAtBreakpoint())) { try { TimeUnit.MILLISECONDS.sleep(5); @@ -72,20 +116,18 @@ private static void extractCallStack(String host, String port, String className, // No need to take note of this exception } } + return main.isAtBreakpoint(); + } - if (main.status() == ThreadReference.THREAD_STATUS_ZOMBIE) { - throw new IllegalStateException("Thread has not encounter a breakpoint"); - } - - // Parsing the call stack - try { - CallStackExtractor.extract(main.frames()); - } catch (IncompatibleThreadStateException e) { - // Should not happen because we are normally at a breakpoint - throw new IllegalStateException("Thread should be at a breakpoint but isn't"); + private static ThreadReference collectThread(String threadName, VirtualMachine vm) { + for (ThreadReference thread : vm.allThreads()) { + if (thread.name().equals(threadName)) { + return thread; + } } + // should not come here if the thread exists + throw new IllegalStateException("No thread nammed " + threadName + "was found"); - vm.dispose(); // properly disconnecting } /** @@ -126,103 +168,4 @@ public static VirtualMachine attachToJVM(String host, String port) throws Illega } - /** - * Add a breakpoint at a specified method on the given VM - * - * @param vm the VM - * @param className the name of the class - * @param methodName the name of the method - */ - public static void addBreakpoint(VirtualMachine vm, String className, String methodName) { - JDIAttach.addBreakpoint(vm, className, methodName, null); - } - - /** - * Add a breakpoint at a specified method on the given VM Precise the method argument type names in case there is multiple method having the same - * name - * - * @param vm the VM - * @param className the name of the class where the method is situated - * @param methodName the name of the method - * @param methodArguments name of all arguments type of the method in the declaration order - */ - public static void addBreakpoint(VirtualMachine vm, String className, String methodName, List methodArguments) { - try { - // Getting the EventRequestManager of the VirtualMachine - EventRequestManager requestManager = vm.eventRequestManager(); - - // Getting the method, adapting the research depending of the amount of information given - Method method = findMethod(vm, className, methodName, methodArguments); - - // Getting the location of the method - Location location = method.location(); - - // Creating the breakpoint at the wanted location - BreakpointRequest breakpointRequest = requestManager.createBreakpointRequest(location); - breakpointRequest.enable(); // activate the breakpoint - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Find a method matching the given characteristics in the Virtual Machine - * - * @param vm the Virtual Machine - * @param className the name of the class where the method is situated - * @param methodName name of the searched method - * @param methodArguments name of all arguments type of the method in the declaration order - * @return the method if one match - * @throws ClassNotLoadedException if no method matches the characteristics - */ - private static Method findMethod(VirtualMachine vm, String className, String methodName, List methodArguments) - throws ClassNotLoadedException { - // finding the class - List classes = vm.classesByName(className); - if (classes.isEmpty()) { - throw new IllegalArgumentException("Class not found : " + className); - } - ClassType classType = (ClassType) classes.get(0); - - // getting all the methods with the searched name in the class - List allMethods = classType.methodsByName(methodName); - - // if no method found throw an exception - if (allMethods.isEmpty()) { - throw new IllegalArgumentException("No method named " + methodName + " in class " + className); - } - - // if no arguments given, either we only have one method found and that's fine - // or we have multiple results, we don't make a random choice, we just throw an exception - if (methodArguments == null) { - if (allMethods.size() != 1) { - throw new IllegalArgumentException("Multiple methods named " + methodName + " in class " + className); - } - return allMethods.get(0); - } - - // if we have multiple methods and given arguments types, we search if one correspond - for (Method m : allMethods) { - List paramTypes = m.argumentTypes(); - // if not the same number of types just pass this method - if (paramTypes.size() != methodArguments.size()) { - continue; - } - // starting on the hypothesis that we found the method, and trying to invalidate it - boolean matches = true; - for (int i = 0; i < paramTypes.size(); i++) { - if (!paramTypes.get(i).name().equals(methodArguments.get(i))) { - matches = false; - break; - } - } - // if we can't invalidate the method, then we found it - if (matches) { - return m; - } - } - // if we got here, then no method have been found - throw new IllegalArgumentException("No method named " + methodName + " in class " + className + " with argument types: " + methodArguments); - } - } diff --git a/src/main/java/extractors/CallStackExtractor.java b/src/main/java/extractors/CallStackExtractor.java deleted file mode 100644 index d073466..0000000 --- a/src/main/java/extractors/CallStackExtractor.java +++ /dev/null @@ -1,25 +0,0 @@ -package extractors; - -import java.util.List; -import java.util.ListIterator; - -import com.sun.jdi.*; - -public class CallStackExtractor { - - //TODO - //Actually printing the call stack, but should log all necessary information in a file - - public static void extract(List frames) { - //iterating from the end of the list to start the logging from the first method called - ListIterator it = frames.listIterator(frames.size()); - for(int i = 1; i <= frames.size(); i++) { - System.out.println("---- Line " + i + " of the call stack ----"); - - StackFrame frame = it.previous(); - //parsing the frame - StackFrameExtractor.extract(frame); - } - } - -} \ No newline at end of file diff --git a/src/main/java/extractors/StackFrameExtractor.java b/src/main/java/extractors/StackFrameExtractor.java index 501de30..c6f7bca 100644 --- a/src/main/java/extractors/StackFrameExtractor.java +++ b/src/main/java/extractors/StackFrameExtractor.java @@ -26,7 +26,11 @@ public class StackFrameExtractor { * By using its unique ID it should be possible to make a link to the one where * the parsing system has developed the search. */ - private static Set visited = new HashSet(); + private Set visited; + + public StackFrameExtractor() { + this.visited = new HashSet(); + } /** * extract a frame, by parsing the method signature, its arguments, and its @@ -34,10 +38,10 @@ public class StackFrameExtractor { * * @param frame the frame to extract */ - public static void extract(StackFrame frame) { - extractMethod(frame); - extractArguments(frame); - extractReceiver(frame); + public void extract(StackFrame frame) { + this.extractMethod(frame); + this.extractArguments(frame); + this.extractReceiver(frame); } /** @@ -45,7 +49,7 @@ public static void extract(StackFrame frame) { * * @param frame the frame to extract */ - public static void extractMethod(StackFrame frame) { + public void extractMethod(StackFrame frame) { Method method = frame.location().method(); System.out.println("Method signature: " + method.name() + "(" + String.join(",", method.argumentTypeNames()) + ")"); } @@ -55,7 +59,7 @@ public static void extractMethod(StackFrame frame) { * * @param frame the frame to extract */ - public static void extractArguments(StackFrame frame) { + public void extractArguments(StackFrame frame) { System.out.println("Method arguments values : "); // getting the method associated to this frame @@ -77,7 +81,7 @@ public static void extractArguments(StackFrame frame) { // With this supposition being always true, we can just check if one have next // and iterate in both System.out.print(namesIterator.next() + " = "); - extractValueRecursive(argumentsValueIterator.next(), ""); + this.extractValueRecursive(argumentsValueIterator.next(), ""); } } @@ -87,9 +91,9 @@ public static void extractArguments(StackFrame frame) { * * @param frame the frame to extract */ - public static void extractReceiver(StackFrame frame) { + public void extractReceiver(StackFrame frame) { System.out.println("Method receiver : "); - extractValueRecursive(frame.thisObject(), ""); + this.extractValueRecursive(frame.thisObject(), ""); } /** @@ -97,13 +101,13 @@ public static void extractReceiver(StackFrame frame) { * @param value the value to extract * @param indent the indent to add to make human able to understand what happen //TODO should be removed after */ - private static void extractValueRecursive(Value value, String indent) { + private void extractValueRecursive(Value value, String indent) { if (value == null) { System.out.println(indent + "null"); } else if (value instanceof PrimitiveValue) { - extractPrimitiveValue((PrimitiveValue) value, indent); + this.extractPrimitiveValue((PrimitiveValue) value, indent); } else if (value instanceof ObjectReference) { - extractObjectReference((ObjectReference) value, indent); + this.extractObjectReference((ObjectReference) value, indent); } else if (value instanceof VoidValue) { // TODO // implements this if needed @@ -120,7 +124,7 @@ private static void extractValueRecursive(Value value, String indent) { * @param value the primitiveValue to extract * @param indent the indent to add to make human able to understand what happen //TODO should be removed after */ - private static void extractPrimitiveValue(PrimitiveValue value, String indent) { + private void extractPrimitiveValue(PrimitiveValue value, String indent) { System.out.println(indent + value.type().name() + " = " + value.toString()); } @@ -129,7 +133,7 @@ private static void extractPrimitiveValue(PrimitiveValue value, String indent) { * @param value the ObjectReference to extract * @param indent the indent to add to make human able to understand what happen //TODO should be removed after */ - private static void extractObjectReference(ObjectReference value, String indent) { + private void extractObjectReference(ObjectReference value, String indent) { //TODO maybe we can add these object to visited ? if (value instanceof StringReference) { System.out.println( @@ -146,15 +150,15 @@ private static void extractObjectReference(ObjectReference value, String indent) } for (int i = 0; i < arrayValues.size(); i++) { System.out.println(indent + "at: " + i + " = "); - extractValueRecursive(arrayValues.get(i), indent + " "); + this.extractValueRecursive(arrayValues.get(i), indent + " "); } } else if (value instanceof ClassObjectReference) { // using reflectedType because it is said to be more precise than referenceType - extractAllFields(value, indent, ((ClassObjectReference) value).reflectedType()); + this.extractAllFields(value, indent, ((ClassObjectReference) value).reflectedType()); } else { - extractAllFields(value, indent, value.referenceType()); + this.extractAllFields(value, indent, value.referenceType()); } } @@ -165,12 +169,12 @@ private static void extractObjectReference(ObjectReference value, String indent) * @param indent the indent to add to make human able to understand what happen //TODO should be removed after * @param type the reference type of the ObjectReference */ - private static void extractAllFields(ObjectReference ref, String indent, ReferenceType type) { - if (visited.contains(ref)) { + private void extractAllFields(ObjectReference ref, String indent, ReferenceType type) { + if (this.visited.contains(ref)) { System.out.println(indent + type.name() + "[ObjId:" + ref.uniqueID() + "]"); return; } - visited.add(ref); + this.visited.add(ref); System.out.println(indent + type.name() + " [ObjId:" + ref.uniqueID() + "] = "); @@ -193,7 +197,7 @@ private static void extractAllFields(ObjectReference ref, String indent, Referen // it's potential information but could also be noise Value fieldValue = ref.getValue(field); System.out.println(indent + field.name() + " = "); - extractValueRecursive(fieldValue, indent + " "); + this.extractValueRecursive(fieldValue, indent + " "); } catch (IllegalArgumentException e) { // TODO Some fields are not valid, how is that possible?