diff --git a/Ghidra/Debug/Debugger-jpda/build.gradle b/Ghidra/Debug/Debugger-jpda/build.gradle index bd5bdb0e33..cd1b0d0b25 100644 --- a/Ghidra/Debug/Debugger-jpda/build.gradle +++ b/Ghidra/Debug/Debugger-jpda/build.gradle @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,6 +22,8 @@ apply plugin: 'eclipse' eclipse.project.name = 'Debug Debugger-jpda' dependencies { + api project(':Debugger-api') + api project(':Debugger-rmi-trace') api project(':Framework-AsyncComm') api project(':Framework-Debugging') api project(':ProposedUtils') diff --git a/Ghidra/Debug/Debugger-jpda/certification.manifest b/Ghidra/Debug/Debugger-jpda/certification.manifest index 895ac4b698..adcba046d9 100644 --- a/Ghidra/Debug/Debugger-jpda/certification.manifest +++ b/Ghidra/Debug/Debugger-jpda/certification.manifest @@ -1,2 +1,6 @@ ##VERSION: 2.0 Module.manifest||GHIDRA||||END| +data/debugger-launchers/attach-java.jsh||GHIDRA||||END| +data/debugger-launchers/bypid-java.jsh||GHIDRA||||END| +data/debugger-launchers/local-java.jsh||GHIDRA||||END| +src/main/resources/ghidra/app/plugin/core/debug/client/tracermi/jdi_schema.xml||GHIDRA||||END| diff --git a/Ghidra/Debug/Debugger-jpda/data/debugger-launchers/attach-java.jsh b/Ghidra/Debug/Debugger-jpda/data/debugger-launchers/attach-java.jsh new file mode 100755 index 0000000000..d4a28213c9 --- /dev/null +++ b/Ghidra/Debug/Debugger-jpda/data/debugger-launchers/attach-java.jsh @@ -0,0 +1,28 @@ +//@title java attach port +//@timeout 20000 +//@desc +//@desc

Attach with java

+//@desc

+//@desc This will attach to the target at HOST:PORT. +//@desc For setup instructions, press F1. +//@desc

+//@desc +//@menu-group attach +//@icon icon.debugger +//@help TraceRmiLauncherServicePlugin#java_attach +//@enum Arch:str JVM Dalvik +//@env OPT_ARCH:Arch="JVM" "Arch" "Target architecture" +//@env OPT_HOST:str="localhost" "Host" "The hostname of the target" +//@env OPT_PORT:str="54321" "Port" "The host's listening port" +//@env OPT_TIMEOUT:str="0" "Timeout" "Connection timeout" +//@env OPT_JSHELL_PATH:file="" "JShell cmd (if desired)" "The full path to jshell." + +import ghidra.dbg.jdi.rmi.jpda.*; + +// NB. The jshell code here is user modifiable; however, the user must provide OPT_JSHEL_PATH when +// prompted in Ghidra' UI, or else this script is completely bypassed. Without a jshell, Ghidra +// calls new JdiClientThread(env).start() directly. + +GhidraJdiInit.initApp() +JdiClientThread thread = new JdiClientThread(System.getenv()); +thread.start(); diff --git a/Ghidra/Debug/Debugger-jpda/data/debugger-launchers/bypid-java.jsh b/Ghidra/Debug/Debugger-jpda/data/debugger-launchers/bypid-java.jsh new file mode 100755 index 0000000000..0dd22a5d34 --- /dev/null +++ b/Ghidra/Debug/Debugger-jpda/data/debugger-launchers/bypid-java.jsh @@ -0,0 +1,27 @@ +//@title java attach PID +//@timeout 20000 +//@desc +//@desc

Attach with java

+//@desc

+//@desc This will attach to the target with a specified PID. +//@desc For setup instructions, press F1. +//@desc

+//@desc +//@menu-group attach +//@icon icon.debugger +//@help TraceRmiLauncherServicePlugin#java_bypid +//@enum Arch:str JVM Dalvik +//@env OPT_ARCH:Arch="JVM" "Arch" "Target architecture" +//@env OPT_PID:str="" "Pid" "The target process id" +//@env OPT_TIMEOUT:str="0" "Timeout" "Connection timeout" +//@env OPT_JSHELL_PATH:file="" "JShell cmd (if desired)" "The full path to jshell." + +import ghidra.dbg.jdi.rmi.jpda.*; + +// NB. The jshell code here is user modifiable; however, the user must provide OPT_JSHEL_PATH when +// prompted in Ghidra' UI, or else this script is completely bypassed. Without a jshell, Ghidra +// calls new JdiClientThread(env).start() directly. + +GhidraJdiInit.initApp() +JdiClientThread thread = new JdiClientThread(System.getenv()); +thread.start(); diff --git a/Ghidra/Debug/Debugger-jpda/data/debugger-launchers/local-java.jsh b/Ghidra/Debug/Debugger-jpda/data/debugger-launchers/local-java.jsh new file mode 100755 index 0000000000..409b733e43 --- /dev/null +++ b/Ghidra/Debug/Debugger-jpda/data/debugger-launchers/local-java.jsh @@ -0,0 +1,29 @@ +//@title java launch +//@timeout 20000 +//@desc +//@desc

Launch with java

+//@desc

+//@desc This will launch the target on the local machine using java. +//@desc For setup instructions, press F1. +//@desc

+//@desc +//@menu-group local +//@icon icon.debugger +//@help TraceRmiLauncherServicePlugin#java +//@args "Arguments" "Command-line arguments to pass to the target" +//@enum Arch:str JVM Dalvik +//@env OPT_ARCH:Arch="JVM" "Arch" "Either 'JVM' or 'Dalvik'" +//@env OPT_TARGET_CLASS:str="" "Image" "The Main Class to launch (defaults to current program)." +////@env OPT_SUSPEND:bool=true "Suspend" "Suspend the VM on launch." +//@env OPT_INCLUDE:str=n "Include virtual threads" "Include virtual threads." +//@env OPT_JSHELL_PATH:file="" "JShell cmd (if desired)" "The full path to jshell." + +import ghidra.dbg.jdi.rmi.jpda.*; + +// NB. The jshell code here is user modifiable; however, the user must provide OPT_JSHEL_PATH when +// prompted in Ghidra' UI, or else this script is completely bypassed. Without a jshell, Ghidra +// calls new JdiClientThread(env).start() directly. + +GhidraJdiInit.initApp() +JdiClientThread thread = new JdiClientThread(System.getenv()); +thread.start(); diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventHandler.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventHandler.java index 30304cc01b..10176849da 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventHandler.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventHandler.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,6 +15,8 @@ */ package ghidra.dbg.jdi.manager; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.*; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -26,7 +28,6 @@ import com.sun.jdi.request.EventRequest; import ghidra.async.AsyncReference; import ghidra.dbg.jdi.manager.impl.DebugStatus; import ghidra.util.Msg; -import ghidra.util.datastruct.ListenerSet; public class JdiEventHandler implements Runnable { @@ -35,16 +36,16 @@ public class JdiEventHandler implements Runnable { String shutdownMessageKey; private VirtualMachine vm; - private Thread thread; + private Thread handlerThread; private JdiEventHandler global; protected final AsyncReference state = new AsyncReference<>(ThreadReference.THREAD_STATUS_NOT_STARTED); - public final ListenerSet listenersEvent = - new ListenerSet<>(JdiEventsListener.class, true); + public final Set listenersEvent = new HashSet<>(); protected final ExecutorService eventThread = Executors.newSingleThreadExecutor(); public JdiEventHandler() { + // Nothing to do here } public JdiEventHandler(VirtualMachine vm, JdiEventHandler global) { @@ -54,18 +55,19 @@ public class JdiEventHandler implements Runnable { } public void start() { - this.thread = new Thread(this, "event-handler"); - thread.start(); + this.handlerThread = new Thread(this, "event-handler"); + handlerThread.start(); } synchronized void shutdown() { connected = false; // force run() loop termination - thread.interrupt(); + handlerThread.interrupt(); while (!completed) { try { wait(); } catch (InterruptedException exc) { + // IGNORE } } } @@ -115,9 +117,9 @@ public class JdiEventHandler implements Runnable { } else if (eventSet.suspendPolicy() == EventRequest.SUSPEND_ALL) { setCurrentThread(eventSet); - event( - () -> listenersEvent.invoke().processStop(eventSet, JdiCause.Causes.UNCLAIMED), - "processStopped"); + for (JdiEventsListener listener : listenersEvent) { + listener.processStop(eventSet, JdiCause.Causes.UNCLAIMED); + } } } catch (InterruptedException exc) { @@ -135,68 +137,33 @@ public class JdiEventHandler implements Runnable { } private DebugStatus processEvent(Event event) { - System.err.println(event + ":" + vm); - if (event instanceof ExceptionEvent) { - return processException((ExceptionEvent) event); - } - else if (event instanceof BreakpointEvent) { - return processBreakpoint((BreakpointEvent) event); - } - else if (event instanceof WatchpointEvent) { - return processWatchpoint((WatchpointEvent) event); - } - else if (event instanceof AccessWatchpointEvent) { - return processAccessWatchpoint((AccessWatchpointEvent) event); - } - else if (event instanceof ModificationWatchpointEvent) { - return processWatchpointModification((ModificationWatchpointEvent) event); - } - else if (event instanceof StepEvent) { - return processStep((StepEvent) event); - } - else if (event instanceof MethodEntryEvent) { - return processMethodEntry((MethodEntryEvent) event); - } - else if (event instanceof MethodExitEvent) { - return processMethodExit((MethodExitEvent) event); - } - else if (event instanceof MonitorContendedEnteredEvent) { - return processMCEntered((MonitorContendedEnteredEvent) event); - } - else if (event instanceof MonitorContendedEnterEvent) { - return processMCEnter((MonitorContendedEnterEvent) event); - } - else if (event instanceof MonitorWaitedEvent) { - return processMonitorWaited((MonitorWaitedEvent) event); - } - else if (event instanceof MonitorWaitEvent) { - return processMonitorWait((MonitorWaitEvent) event); - } - else if (event instanceof ClassPrepareEvent) { - return processClassPrepare((ClassPrepareEvent) event); - } - else if (event instanceof ClassUnloadEvent) { - return processClassUnload((ClassUnloadEvent) event); - } - else if (event instanceof ThreadStartEvent) { - return processThreadStart((ThreadStartEvent) event); - } - else if (event instanceof ThreadDeathEvent) { - return processThreadDeath((ThreadDeathEvent) event); - } - else if (event instanceof VMStartEvent) { - return processVMStart((VMStartEvent) event); - } - else if (event instanceof VMDisconnectEvent) { - return processVMDisconnect((VMDisconnectEvent) event); - } - else if (event instanceof VMDeathEvent) { - return processVMDeath((VMDeathEvent) event); - } - else { - System.err.println("Unknown event: " + event); - return null; - } + //System.err.println(event + ":" + vm); + return switch (event) { + case ExceptionEvent ev -> processException(ev); + case AccessWatchpointEvent ev -> processAccessWatchpoint(ev); + case ModificationWatchpointEvent ev -> processWatchpointModification(ev); + case WatchpointEvent ev -> processWatchpoint(ev); + case StepEvent ev -> processStep(ev); + case MethodEntryEvent ev -> processMethodEntry(ev); + case MethodExitEvent ev -> processMethodExit(ev); + case MonitorContendedEnteredEvent ev -> processMCEntered(ev); + case MonitorContendedEnterEvent ev -> processMCEnter(ev); + case MonitorWaitedEvent ev -> processMonitorWaited(ev); + case MonitorWaitEvent ev -> processMonitorWait(ev); + case ClassPrepareEvent ev -> processClassPrepare(ev); + case ClassUnloadEvent ev -> processClassUnload(ev); + case ThreadStartEvent ev -> processThreadStart(ev); + case ThreadDeathEvent ev -> processThreadDeath(ev); + case VMStartEvent ev -> processVMStart(ev); + case VMDisconnectEvent ev -> processVMDisconnect(ev); + case VMDeathEvent ev -> processVMDeath(ev); + default -> processUnknown(event); + }; + } + + private DebugStatus processUnknown(Event event) { + System.err.println("Unknown event: " + event); + return null; } private boolean vmDied = false; @@ -214,9 +181,11 @@ public class JdiEventHandler implements Runnable { /* * Inform jdb command line processor that jdb is being shutdown. JDK-8154144. */ - event(() -> listenersEvent.invoke().processShutdown(event, JdiCause.Causes.UNCLAIMED), - "processStopped"); - return null; ///false; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.processShutdown(event, JdiCause.Causes.UNCLAIMED)); + } + return status; } else { throw new InternalError(); @@ -293,257 +262,300 @@ public class JdiEventHandler implements Runnable { JdiThreadInfo.setCurrentThread(thread); } + private DebugStatus update(DebugStatus status, DebugStatus update) { + if (update == null) { + update = DebugStatus.BREAK; + } + return update.equals(DebugStatus.NO_CHANGE) ? status : update; + } + /** * Handler for breakpoint events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processBreakpoint(BreakpointEvent evt) { - event(() -> listenersEvent.invoke().breakpointHit(evt, JdiCause.Causes.UNCLAIMED), - "breakpointHit"); - return DebugStatus.BREAK; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.breakpointHit(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for exception events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processException(ExceptionEvent evt) { - event(() -> listenersEvent.invoke().exceptionHit(evt, JdiCause.Causes.UNCLAIMED), - "exceptionHit"); - return DebugStatus.BREAK; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.exceptionHit(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for method entry events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processMethodEntry(MethodEntryEvent evt) { - event(() -> listenersEvent.invoke().methodEntry(evt, JdiCause.Causes.UNCLAIMED), "methodEntry"); - return DebugStatus.GO; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.methodEntry(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for method exit events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processMethodExit(MethodExitEvent evt) { - event(() -> listenersEvent.invoke().methodExit(evt, JdiCause.Causes.UNCLAIMED), "methodExit"); - return DebugStatus.GO; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.methodExit(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for class prepared events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processClassPrepare(ClassPrepareEvent evt) { - event(() -> listenersEvent.invoke().classPrepare(evt, JdiCause.Causes.UNCLAIMED), - "classPrepare"); - /* - if (!Env.specList.resolve(cle)) { - MessageOutput.lnprint("Stopping due to deferred breakpoint errors."); - return true; - } else { - return false; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.classPrepare(evt, JdiCause.Causes.UNCLAIMED)); } - */ - return DebugStatus.GO; + return status; } /** * Handler for class unload events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processClassUnload(ClassUnloadEvent evt) { - event(() -> listenersEvent.invoke().classUnload(evt, JdiCause.Causes.UNCLAIMED), "classUnload"); - return DebugStatus.GO; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.classUnload(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for monitor contended entered events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processMCEntered(MonitorContendedEnteredEvent evt) { - event(() -> listenersEvent.invoke().monitorContendedEntered(evt, JdiCause.Causes.UNCLAIMED), - "monitorContendedEntered"); - return DebugStatus.GO; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = + update(status, listener.monitorContendedEntered(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for monitor contended enter events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processMCEnter(MonitorContendedEnterEvent evt) { - event(() -> listenersEvent.invoke().monitorContendedEnter(evt, JdiCause.Causes.UNCLAIMED), - "monitorContendedEnter"); - return DebugStatus.GO; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.monitorContendedEnter(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for monitor waited events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processMonitorWaited(MonitorWaitedEvent evt) { - event(() -> listenersEvent.invoke().monitorWaited(evt, JdiCause.Causes.UNCLAIMED), - "monitorWaited"); - return DebugStatus.GO; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.monitorWaited(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for monitor waited events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processMonitorWait(MonitorWaitEvent evt) { - event(() -> listenersEvent.invoke().monitorWait(evt, JdiCause.Causes.UNCLAIMED), "monitorWait"); - return DebugStatus.GO; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.monitorWait(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for step events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processStep(StepEvent evt) { evt.request().disable(); - event(() -> listenersEvent.invoke().stepComplete(evt, JdiCause.Causes.UNCLAIMED), "step"); - return DebugStatus.STEP_INTO; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.stepComplete(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for watchpoint events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processWatchpoint(WatchpointEvent evt) { - event(() -> listenersEvent.invoke().watchpointHit(evt, JdiCause.Causes.UNCLAIMED), - "watchpointHit"); - return DebugStatus.BREAK; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.watchpointHit(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for access watchpoint events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processAccessWatchpoint(AccessWatchpointEvent evt) { - event(() -> listenersEvent.invoke().accessWatchpointHit(evt, JdiCause.Causes.UNCLAIMED), - "accessWatchpointHit"); - return DebugStatus.BREAK; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.accessWatchpointHit(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for watchpoint modified events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processWatchpointModification(ModificationWatchpointEvent evt) { - event(() -> listenersEvent.invoke().watchpointModified(evt, JdiCause.Causes.UNCLAIMED), - "watchpointModified"); - return DebugStatus.GO; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.watchpointModified(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for thread death events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processThreadDeath(ThreadDeathEvent evt) { - event(() -> listenersEvent.invoke().threadExited(evt, JdiCause.Causes.UNCLAIMED), - "threadExited"); - JdiThreadInfo.removeThread(evt.thread()); - return DebugStatus.GO; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.threadExited(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; + } + + /** + * Handler for vm start events + * + * @param thread eventThread + * @param threadState state + * @param reason reason + * @return status + */ + public DebugStatus processThreadStateChanged(ThreadReference thread, int threadState, + JdiReason reason) { + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.threadStateChanged(thread, threadState, + JdiCause.Causes.UNCLAIMED, reason)); + } + return status; } /** * Handler for thread start events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processThreadStart(ThreadStartEvent evt) { JdiThreadInfo.addThread(evt.thread()); - event(() -> listenersEvent.invoke().threadStarted(evt, JdiCause.Causes.UNCLAIMED), - "threadStarted"); - return DebugStatus.GO; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.threadStarted(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for vm death events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processVMDeath(VMDeathEvent evt) { shutdownMessageKey = "The application exited"; - event(() -> listenersEvent.invoke().vmDied(evt, JdiCause.Causes.UNCLAIMED), "vmDied"); - return DebugStatus.BREAK; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.vmDied(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for vm disconnect events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processVMDisconnect(VMDisconnectEvent evt) { shutdownMessageKey = "The application has been disconnected"; - event(() -> listenersEvent.invoke().vmDisconnected(evt, JdiCause.Causes.UNCLAIMED), - "vmDisconnected"); - return DebugStatus.BREAK; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.vmDisconnected(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } /** * Handler for vm start events * * @param evt the event - * @param v nothing - * @return + * @return status */ protected DebugStatus processVMStart(VMStartEvent evt) { - event(() -> listenersEvent.invoke().vmStarted(evt, JdiCause.Causes.UNCLAIMED), "vmStarted"); - return DebugStatus.BREAK; + DebugStatus status = DebugStatus.NO_CHANGE; + for (JdiEventsListener listener : listenersEvent) { + status = update(status, listener.vmStarted(evt, JdiCause.Causes.UNCLAIMED)); + } + return status; } public Integer getState() { @@ -560,4 +572,5 @@ public class JdiEventHandler implements Runnable { } return set; } + } diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventsListener.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventsListener.java index 96d5a32665..aa9273c781 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventsListener.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventsListener.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,6 +19,7 @@ import com.sun.jdi.*; import com.sun.jdi.event.*; import ghidra.dbg.jdi.manager.breakpoint.JdiBreakpointInfo; +import ghidra.dbg.jdi.manager.impl.DebugStatus; /** * A listener for events related to objects known to the manager @@ -30,8 +31,9 @@ public interface JdiEventsListener { * * @param vm a handle to the selected vm * @param cause the cause of this event + * @return status */ - void vmSelected(VirtualMachine vm, JdiCause cause); + DebugStatus vmSelected(VirtualMachine vm, JdiCause cause); /** * A different thread has been selected (gained focus) @@ -39,8 +41,9 @@ public interface JdiEventsListener { * @param thread a handle to the selected thread * @param frame a handle to the current frame * @param cause the cause of this event + * @return status */ - void threadSelected(ThreadReference thread, StackFrame frame, JdiCause cause); + DebugStatus threadSelected(ThreadReference thread, StackFrame frame, JdiCause cause); /** * A library has been loaded by an vm @@ -48,8 +51,9 @@ public interface JdiEventsListener { * @param vm a handle to the vm which loaded the library * @param name the name of the library on the target * @param cause the cause of this event + * @return status */ - void libraryLoaded(VirtualMachine vm, String name, JdiCause cause); + DebugStatus classLoaded(VirtualMachine vm, String name, JdiCause cause); /** * A library has been unloaded from an vm @@ -57,16 +61,18 @@ public interface JdiEventsListener { * @param vm a handle to the vm which unloaded the library * @param name the name of the library on the target * @param cause the cause of this event + * @return status */ - void libraryUnloaded(VirtualMachine vm, String name, JdiCause cause); + DebugStatus classUnloaded(VirtualMachine vm, String name, JdiCause cause); /** * A breakpoint has been created in the session * * @param info information about the new breakpoint * @param cause the cause of this event + * @return status */ - void breakpointCreated(JdiBreakpointInfo info, JdiCause cause); + DebugStatus breakpointCreated(JdiBreakpointInfo info, JdiCause cause); /** * A breakpoint in the session has been modified @@ -74,16 +80,19 @@ public interface JdiEventsListener { * @param newInfo new information about the modified breakpoint * @param oldInfo old information about the modified breakpoint * @param cause the cause of this event + * @return status */ - void breakpointModified(JdiBreakpointInfo newInfo, JdiBreakpointInfo oldInfo, JdiCause cause); + DebugStatus breakpointModified(JdiBreakpointInfo newInfo, JdiBreakpointInfo oldInfo, + JdiCause cause); /** * A breakpoint has been deleted from the session * * @param info information about the now-deleted breakpoint * @param cause the cause of this event + * @return status */ - void breakpointDeleted(JdiBreakpointInfo info, JdiCause cause); + DebugStatus breakpointDeleted(JdiBreakpointInfo info, JdiCause cause); /** * TODO: This is not yet implemented @@ -95,145 +104,165 @@ public interface JdiEventsListener { * @param addr the address of the change * @param len the length, with the address, bounding the region of change * @param cause the cause of this event + * @return status */ - void memoryChanged(VirtualMachine vm, long addr, int len, JdiCause cause); + DebugStatus memoryChanged(VirtualMachine vm, long addr, int len, JdiCause cause); - void vmInterrupted(); + DebugStatus vmInterrupted(); /** * A breakpoint has been hit * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void breakpointHit(BreakpointEvent evt, JdiCause cause); + DebugStatus breakpointHit(BreakpointEvent evt, JdiCause cause); /** * An exception has been hit * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void exceptionHit(ExceptionEvent evt, JdiCause cause); + DebugStatus exceptionHit(ExceptionEvent evt, JdiCause cause); /** * A method has been invoked * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void methodEntry(MethodEntryEvent evt, JdiCause cause); + DebugStatus methodEntry(MethodEntryEvent evt, JdiCause cause); /** * A method is about to finish * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void methodExit(MethodExitEvent evt, JdiCause cause); + DebugStatus methodExit(MethodExitEvent evt, JdiCause cause); /** * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void classPrepare(ClassPrepareEvent evt, JdiCause cause); + DebugStatus classPrepare(ClassPrepareEvent evt, JdiCause cause); /** * A calls is being unloaded * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void classUnload(ClassUnloadEvent evt, JdiCause cause); + DebugStatus classUnload(ClassUnloadEvent evt, JdiCause cause); /** * A thread has entered a monitor after release from another thread * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void monitorContendedEntered(MonitorContendedEnteredEvent evt, JdiCause cause); + DebugStatus monitorContendedEntered(MonitorContendedEnteredEvent evt, JdiCause cause); /** * A thread is attempting to enter monitor acquired by another thread * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void monitorContendedEnter(MonitorContendedEnterEvent evt, JdiCause cause); + DebugStatus monitorContendedEnter(MonitorContendedEnterEvent evt, JdiCause cause); /** * A vm has finished waiting on a monitor object * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void monitorWaited(MonitorWaitedEvent evt, JdiCause cause); + DebugStatus monitorWaited(MonitorWaitedEvent evt, JdiCause cause); /** * A vm is about to wait on a monitor object * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void monitorWait(MonitorWaitEvent evt, JdiCause cause); + DebugStatus monitorWait(MonitorWaitEvent evt, JdiCause cause); /** * A step has completed * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void stepComplete(StepEvent evt, JdiCause cause); + DebugStatus stepComplete(StepEvent evt, JdiCause cause); /** * A watchpoint has been hit * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void watchpointHit(WatchpointEvent evt, JdiCause cause); + DebugStatus watchpointHit(WatchpointEvent evt, JdiCause cause); /** * A field has been accessed * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void accessWatchpointHit(AccessWatchpointEvent evt, JdiCause cause); + DebugStatus accessWatchpointHit(AccessWatchpointEvent evt, JdiCause cause); /** * A field has been modified * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void watchpointModified(ModificationWatchpointEvent evt, JdiCause cause); + DebugStatus watchpointModified(ModificationWatchpointEvent evt, JdiCause cause); /** * A thread has exited * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void threadExited(ThreadDeathEvent evt, JdiCause cause); + DebugStatus threadExited(ThreadDeathEvent evt, JdiCause cause); /** * A thread has started * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void threadStarted(ThreadStartEvent evt, JdiCause cause); + DebugStatus threadStarted(ThreadStartEvent evt, JdiCause cause); /** * A thread has changed state * - * @param evt the triggering event + * @param thread thread + * @param state state * @param cause the cause of this event + * @param reason reason + * @return status */ - void threadStateChanged(ThreadReference thread, Integer state, JdiCause cause, + DebugStatus threadStateChanged(ThreadReference thread, Integer state, JdiCause cause, JdiReason reason); /** @@ -241,27 +270,30 @@ public interface JdiEventsListener { * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void vmDied(VMDeathEvent evt, JdiCause cause); + DebugStatus vmDied(VMDeathEvent evt, JdiCause cause); /** * A vm has been disconnected * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void vmDisconnected(VMDisconnectEvent evt, JdiCause cause); + DebugStatus vmDisconnected(VMDisconnectEvent evt, JdiCause cause); /** * A vm has started * * @param evt the triggering event * @param cause the cause of this event + * @return status */ - void vmStarted(VMStartEvent evt, JdiCause cause); + DebugStatus vmStarted(VMStartEvent evt, JdiCause cause); - void processStop(EventSet eventSet, JdiCause cause); + DebugStatus processStop(EventSet eventSet, JdiCause cause); - void processShutdown(Event event, JdiCause cause); + DebugStatus processShutdown(Event event, JdiCause cause); } diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventsListenerAdapter.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventsListenerAdapter.java index 044adbf6a7..fc4e6e40c5 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventsListenerAdapter.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiEventsListenerAdapter.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,12 +15,11 @@ */ package ghidra.dbg.jdi.manager; -import java.util.Collection; - import com.sun.jdi.*; import com.sun.jdi.event.*; import ghidra.dbg.jdi.manager.breakpoint.JdiBreakpointInfo; +import ghidra.dbg.jdi.manager.impl.DebugStatus; /** * An adapter for {@link JdiEventsListener} @@ -30,128 +29,159 @@ import ghidra.dbg.jdi.manager.breakpoint.JdiBreakpointInfo; public interface JdiEventsListenerAdapter extends JdiEventsListener { @Override - default void vmSelected(VirtualMachine vm, JdiCause cause) { + default DebugStatus vmSelected(VirtualMachine vm, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void threadSelected(ThreadReference thread, StackFrame frame, JdiCause cause) { + default DebugStatus threadSelected(ThreadReference thread, StackFrame frame, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void threadStateChanged(ThreadReference thread, Integer state, JdiCause cause, + default DebugStatus threadStateChanged(ThreadReference thread, Integer state, JdiCause cause, JdiReason reason) { + return DebugStatus.NO_CHANGE; } @Override - default void libraryLoaded(VirtualMachine vm, String name, JdiCause cause) { + default DebugStatus classLoaded(VirtualMachine vm, String name, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void libraryUnloaded(VirtualMachine vm, String name, JdiCause cause) { + default DebugStatus classUnloaded(VirtualMachine vm, String name, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void breakpointCreated(JdiBreakpointInfo info, JdiCause cause) { + default DebugStatus breakpointCreated(JdiBreakpointInfo info, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void breakpointModified(JdiBreakpointInfo newInfo, JdiBreakpointInfo oldInfo, + default DebugStatus breakpointModified(JdiBreakpointInfo newInfo, JdiBreakpointInfo oldInfo, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void breakpointDeleted(JdiBreakpointInfo info, JdiCause cause) { + default DebugStatus breakpointDeleted(JdiBreakpointInfo info, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void memoryChanged(VirtualMachine vm, long addr, int len, JdiCause cause) { + default DebugStatus memoryChanged(VirtualMachine vm, long addr, int len, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void vmInterrupted() { + default DebugStatus vmInterrupted() { + return DebugStatus.BREAK; } @Override - default void breakpointHit(BreakpointEvent evt, JdiCause cause) { + default DebugStatus breakpointHit(BreakpointEvent evt, JdiCause cause) { + return DebugStatus.BREAK; } @Override - default void exceptionHit(ExceptionEvent evt, JdiCause cause) { + default DebugStatus exceptionHit(ExceptionEvent evt, JdiCause cause) { + return DebugStatus.BREAK; } @Override - default void methodEntry(MethodEntryEvent evt, JdiCause cause) { + default DebugStatus methodEntry(MethodEntryEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void methodExit(MethodExitEvent evt, JdiCause cause) { + default DebugStatus methodExit(MethodExitEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void classPrepare(ClassPrepareEvent evt, JdiCause cause) { + default DebugStatus classPrepare(ClassPrepareEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void classUnload(ClassUnloadEvent evt, JdiCause cause) { + default DebugStatus classUnload(ClassUnloadEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void monitorContendedEntered(MonitorContendedEnteredEvent evt, JdiCause cause) { + default DebugStatus monitorContendedEntered(MonitorContendedEnteredEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void monitorContendedEnter(MonitorContendedEnterEvent evt, JdiCause cause) { + default DebugStatus monitorContendedEnter(MonitorContendedEnterEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void monitorWaited(MonitorWaitedEvent evt, JdiCause cause) { + default DebugStatus monitorWaited(MonitorWaitedEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void monitorWait(MonitorWaitEvent evt, JdiCause cause) { + default DebugStatus monitorWait(MonitorWaitEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void stepComplete(StepEvent evt, JdiCause cause) { + default DebugStatus stepComplete(StepEvent evt, JdiCause cause) { + return DebugStatus.STEP_INTO; } @Override - default void watchpointHit(WatchpointEvent evt, JdiCause cause) { + default DebugStatus watchpointHit(WatchpointEvent evt, JdiCause cause) { + return DebugStatus.BREAK; } @Override - default void accessWatchpointHit(AccessWatchpointEvent evt, JdiCause cause) { + default DebugStatus accessWatchpointHit(AccessWatchpointEvent evt, JdiCause cause) { + return DebugStatus.BREAK; } @Override - default void watchpointModified(ModificationWatchpointEvent evt, JdiCause cause) { + default DebugStatus watchpointModified(ModificationWatchpointEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void threadExited(ThreadDeathEvent evt, JdiCause cause) { + default DebugStatus threadExited(ThreadDeathEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void threadStarted(ThreadStartEvent evt, JdiCause cause) { + default DebugStatus threadStarted(ThreadStartEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void vmDied(VMDeathEvent evt, JdiCause cause) { + default DebugStatus vmDied(VMDeathEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void vmDisconnected(VMDisconnectEvent evt, JdiCause cause) { + default DebugStatus vmDisconnected(VMDisconnectEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void vmStarted(VMStartEvent evt, JdiCause cause) { + default DebugStatus vmStarted(VMStartEvent evt, JdiCause cause) { + return DebugStatus.NO_CHANGE; } @Override - default void processStop(EventSet eventSet, JdiCause cause) { + default DebugStatus processStop(EventSet eventSet, JdiCause cause) { + return DebugStatus.BREAK; } @Override - default void processShutdown(Event event, JdiCause cause) { + default DebugStatus processShutdown(Event event, JdiCause cause) { + return DebugStatus.NO_CHANGE; } } diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiManager.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiManager.java index de0e14ce30..3d80b97cfa 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiManager.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/JdiManager.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -63,7 +63,6 @@ public interface JdiManager extends AutoCloseable { /** * Add a listener for JDI's state * - * @see #getState() * @param vm the virtual machine * @param listener the listener to add */ @@ -72,7 +71,6 @@ public interface JdiManager extends AutoCloseable { /** * Remove a listener for JDI's state * - * @see #getState() * @param vm the virtual machine * @param listener the listener to remove */ @@ -105,7 +103,7 @@ public interface JdiManager extends AutoCloseable { * Remove a listener for target output * * @see #addTargetOutputListener(JdiTargetOutputListener) - * @param listener + * @param listener for output */ void removeTargetOutputListener(JdiTargetOutputListener listener); @@ -119,7 +117,7 @@ public interface JdiManager extends AutoCloseable { /** * Remove a listener for console output * - * @param listener + * @param listener for output */ void removeConsoleOutputListener(JdiConsoleOutputListener listener); @@ -127,9 +125,8 @@ public interface JdiManager extends AutoCloseable { * Get an inferior by its JDI-assigned ID * * JDI numbers virtual machines incrementally. All vms and created and destroyed by the user. - * See {@link #getVM()}. * - * @param iid the inferior ID + * @param id the inferior ID * @return a handle to the inferior, if it exists */ VirtualMachine getVM(String id); @@ -150,7 +147,6 @@ public interface JdiManager extends AutoCloseable { * This may be useful if the manager's command queue is stalled because an inferior is running. * * @throws IOException if an I/O error occurs - * @throws InterruptedException */ void sendInterruptNow() throws IOException; @@ -162,9 +158,9 @@ public interface JdiManager extends AutoCloseable { * * @return a future which completes with the handle to the new vm */ - CompletableFuture addVM(Connector cx, List args); + VirtualMachine addVM(Connector cx, List args); - CompletableFuture addVM(Connector cx, Map args); + VirtualMachine addVM(Connector cx, Map args); /** * Remove a vm @@ -178,7 +174,6 @@ public interface JdiManager extends AutoCloseable { * Execute an arbitrary CLI command, printing output to the CLI console * * Note: to ensure a certain thread or inferior has focus for a console command, see - * {@link JdiThread#console(String)} and {@link JdiVM#console(String)}. * * @param command the command to execute * @return a future that completes when JDI has executed the command @@ -189,8 +184,7 @@ public interface JdiManager extends AutoCloseable { * Execute an arbitrary CLI command, capturing its console output * * The output will not be printed to the CLI console. To ensure a certain thread or inferior has - * focus for a console command, see {@link JdiThread#consoleCapture(String)} and - * {@link JdiVM#consoleCapture(String)}. + * focus for a console command * * @param command the command to execute * @return a future that completes with the captured output when JDI has executed the command diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/breakpoint/JdiBreakpointInfo.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/breakpoint/JdiBreakpointInfo.java index 29786d2734..a63175e32b 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/breakpoint/JdiBreakpointInfo.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/breakpoint/JdiBreakpointInfo.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,7 +15,7 @@ */ package ghidra.dbg.jdi.manager.breakpoint; -import java.util.*; +import java.util.Objects; import com.sun.jdi.*; import com.sun.jdi.request.*; @@ -69,14 +69,12 @@ public class JdiBreakpointInfo { @Override public boolean equals(Object obj) { - if (!((obj instanceof JdiBreakpointInfo))) { - return false; + if (obj instanceof JdiBreakpointInfo that) { + if (this.request == that.request) { + return true; + } } - JdiBreakpointInfo that = (JdiBreakpointInfo) obj; - if (this.request != that.request) { - return false; - } - return true; + return false; } /** @@ -130,11 +128,11 @@ public class JdiBreakpointInfo { } public boolean isEnabled() { - if (request instanceof BreakpointRequest) { - return ((BreakpointRequest) request).isEnabled(); + if (request instanceof BreakpointRequest breakreq) { + return breakreq.isEnabled(); } - if (request instanceof WatchpointRequest) { - return ((WatchpointRequest) request).isEnabled(); + if (request instanceof WatchpointRequest watchReq) { + return watchReq.isEnabled(); } return false; } diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/impl/JdiManagerImpl.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/impl/JdiManagerImpl.java index 2f58b67e13..d8c58efe95 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/impl/JdiManagerImpl.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/manager/impl/JdiManagerImpl.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,29 +15,33 @@ */ package ghidra.dbg.jdi.manager.impl; -import static ghidra.lifecycle.Unfinished.*; +import static ghidra.lifecycle.Unfinished.TODO; import java.io.IOException; import java.util.*; import java.util.concurrent.*; -import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.tuple.Pair; import com.sun.jdi.*; import com.sun.jdi.connect.*; +import com.sun.jdi.event.Event; import ghidra.dbg.jdi.manager.*; import ghidra.dbg.jdi.manager.JdiCause.Causes; +import ghidra.util.Msg; import ghidra.util.datastruct.ListenerSet; public class JdiManagerImpl implements JdiManager { - public DebugStatus status; - private VirtualMachineManager virtualMachineManager; private final Map vms = new LinkedHashMap<>(); private VirtualMachine curVM = null; + private ThreadReference curThread = null; + private StackFrame curFrame = null; + private Location curLocation = null; + private Event curEvent = null; + private final Map unmodifiableVMs = Collections.unmodifiableMap(vms); protected final ListenerSet listenersTargetOutput = @@ -77,7 +81,7 @@ public class JdiManagerImpl implements JdiManager { @Override public void terminate() { /** - * NB: can use manager.connectedVMs, because technically, other things could be using the + * NB: can't use manager.connectedVMs, because technically, other things could be using the * JDI outside of this manager. */ for (VirtualMachine vm : vms.values()) { @@ -174,7 +178,7 @@ public class JdiManagerImpl implements JdiManager { } @Override - public CompletableFuture addVM(Connector cx, List args) { + public VirtualMachine addVM(Connector cx, List args) { Map arguments = cx.defaultArguments(); if (cx instanceof LaunchingConnector) { if (arguments.containsKey("command")) { @@ -206,33 +210,39 @@ public class JdiManagerImpl implements JdiManager { } @Override - public CompletableFuture addVM(Connector cx, - Map args) { - return CompletableFuture.supplyAsync(() -> { - try { - curVM = connectVM(cx, args); - JdiEventHandler eventHandler = new JdiEventHandler(curVM, globalEventHandler); - eventHandler.start(); - eventHandler.setState(ThreadReference.THREAD_STATUS_NOT_STARTED, Causes.UNCLAIMED); - eventHandlers.put(curVM, eventHandler); - vms.put(curVM.name(), curVM); - connectors.put(curVM, cx); - } - catch (VMDisconnectedException e) { - System.out.println("Virtual Machine is disconnected."); - return ExceptionUtils.rethrow(e); - } - catch (Exception e) { - return ExceptionUtils.rethrow(e); - } - return curVM; - }); + public VirtualMachine addVM(Connector cx, Map args) { + try { + setCurrentVM(connectVM(cx, args)); + JdiEventHandler eventHandler = new JdiEventHandler(getCurrentVM(), globalEventHandler); + eventHandler.start(); + eventHandler.setState(ThreadReference.THREAD_STATUS_NOT_STARTED, Causes.UNCLAIMED); + eventHandlers.put(getCurrentVM(), eventHandler); + vms.put(getCurrentVM().name(), getCurrentVM()); + connectors.put(getCurrentVM(), cx); + } + catch (VMDisconnectedException e) { + Msg.error(this, "Virtual Machine is disconnected."); + return null; + } + catch (Exception e) { + Msg.error(this, "Could not connect Virtual Machine", e); + return null; + } + return getCurrentVM(); + } + + public void addVM(VirtualMachine vm) { + JdiEventHandler eventHandler = new JdiEventHandler(vm, globalEventHandler); + eventHandler.start(); + eventHandler.setState(ThreadReference.THREAD_STATUS_NOT_STARTED, Causes.UNCLAIMED); + eventHandlers.put(getCurrentVM(), eventHandler); + vms.put(getCurrentVM().name(), getCurrentVM()); } @Override public CompletableFuture removeVM(VirtualMachine vm) { - if (curVM == vm) { - curVM = null; + if (getCurrentVM() == vm) { + setCurrentVM(null); } vms.remove(vm.name()); connectors.remove(vm); @@ -275,4 +285,50 @@ public class JdiManagerImpl implements JdiManager { return eventHandlers.get(vm); } + public VirtualMachine getCurrentVM() { + return curVM; + } + + public void setCurrentVM(VirtualMachine vm) { + this.curVM = vm; + if (!vms.containsValue(vm)) { + addVM(vm); + } + } + + public ThreadReference getCurrentThread() { + if (curThread == null) { + List threads = curVM.allThreads(); + curThread = threads.getFirst(); + } + return curThread; + } + + public void setCurrentThread(ThreadReference thread) { + this.curThread = thread; + } + + public StackFrame getCurrentFrame() { + return curFrame; + } + + public void setCurrentFrame(StackFrame frame) { + this.curFrame = frame; + } + + public void setCurrentLocation(Location location) { + this.curLocation = location; + } + + public Location getCurrentLocation() { + return curLocation; + } + + public void setCurrentEvent(Event event) { + this.curEvent = event; + } + + public Event getCurrentEvent() { + return curEvent; + } } diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetBreakpointContainer.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetBreakpointContainer.java index 7906701964..4b3da6560a 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetBreakpointContainer.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetBreakpointContainer.java @@ -25,6 +25,7 @@ import ghidra.dbg.DebuggerObjectModel.RefreshBehavior; import ghidra.dbg.jdi.manager.JdiCause; import ghidra.dbg.jdi.manager.JdiEventsListenerAdapter; import ghidra.dbg.jdi.manager.breakpoint.JdiBreakpointInfo; +import ghidra.dbg.jdi.manager.impl.DebugStatus; import ghidra.dbg.jdi.model.iface2.JdiModelTargetObject; import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; import ghidra.dbg.target.TargetBreakpointSpecContainer; @@ -62,22 +63,25 @@ public class JdiModelTargetBreakpointContainer extends JdiModelTargetObjectImpl } @Override - public void breakpointCreated(JdiBreakpointInfo info, JdiCause cause) { + public DebugStatus breakpointCreated(JdiBreakpointInfo info, JdiCause cause) { changeElements(List.of(), List.of(getTargetBreakpointSpec(info)), Map.of(), "Created"); + return DebugStatus.NO_CHANGE; } @Override - public void breakpointModified(JdiBreakpointInfo newInfo, JdiBreakpointInfo oldInfo, + public DebugStatus breakpointModified(JdiBreakpointInfo newInfo, JdiBreakpointInfo oldInfo, JdiCause cause) { getTargetBreakpointSpec(oldInfo).updateInfo(oldInfo, newInfo, "Modified"); + return DebugStatus.NO_CHANGE; } @Override - public void breakpointDeleted(JdiBreakpointInfo info, JdiCause cause) { + public DebugStatus breakpointDeleted(JdiBreakpointInfo info, JdiCause cause) { synchronized (this) { specsByInfo.remove(info); } changeElements(List.of(info.toString()), List.of(), Map.of(), "Deleted"); + return DebugStatus.NO_CHANGE; } @Override diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetBreakpointSpec.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetBreakpointSpec.java index 4fd166e87b..cef3341b19 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetBreakpointSpec.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetBreakpointSpec.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -29,10 +29,17 @@ import ghidra.dbg.target.schema.TargetAttributeType; import ghidra.dbg.target.schema.TargetObjectSchemaInfo; import ghidra.util.datastruct.ListenerSet; -@TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = { - @TargetAttributeType(name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME, type = JdiModelTargetBreakpointContainer.class), - @TargetAttributeType(name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME, type = JdiModelTargetBreakpointSpec.class), - @TargetAttributeType(type = Void.class) }, canonicalContainer = true) +@TargetObjectSchemaInfo( + name = "BreakpointSpec", + attributes = { + @TargetAttributeType( + name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME, + type = JdiModelTargetBreakpointContainer.class), + @TargetAttributeType( + name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME, + type = JdiModelTargetBreakpointSpec.class), + @TargetAttributeType(type = Void.class) }, + canonicalContainer = true) public class JdiModelTargetBreakpointSpec extends JdiModelTargetObjectImpl implements TargetBreakpointSpec, JdiModelTargetDeletable { diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetConnector.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetConnector.java index 6eedb0861b..c5d853d809 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetConnector.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetConnector.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -107,6 +107,7 @@ public class JdiModelTargetConnector extends JdiModelTargetObjectImpl public CompletableFuture launch(Map args) { Map jdiArgs = JdiModelTargetLauncher.getArguments(cx.defaultArguments(), paramDescs, args); - return getManager().addVM(cx, jdiArgs).thenApply(__ -> null); + getManager().addVM(cx, jdiArgs); + return CompletableFuture.completedFuture(null); } } diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetRoot.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetRoot.java index 675f4b0689..7af4eb5295 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetRoot.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetRoot.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,6 +27,7 @@ import ghidra.async.AsyncUtils; import ghidra.dbg.DebugModelConventions; import ghidra.dbg.agent.DefaultTargetModelRoot; import ghidra.dbg.jdi.manager.*; +import ghidra.dbg.jdi.manager.impl.DebugStatus; import ghidra.dbg.jdi.model.iface1.*; import ghidra.dbg.jdi.model.iface2.JdiModelTargetObject; import ghidra.dbg.target.*; @@ -61,7 +62,7 @@ import ghidra.util.Msg; required = true, fixed = true), @TargetAttributeType( - name = "VirtualMachines", + name = "VMs", type = JdiModelTargetVMContainer.class, required = true, fixed = true), @@ -153,24 +154,26 @@ public class JdiModelTargetRoot extends DefaultTargetModelRoot implements // */ @Override - public void vmSelected(VirtualMachine vm, JdiCause cause) { + public DebugStatus vmSelected(VirtualMachine vm, JdiCause cause) { if (vm.allThreads().isEmpty()) { JdiModelTargetVM targetVM = vms.getTargetVM(vm); setFocus(targetVM); } // Otherwise, we'll presumably get the =thread-selected event + return DebugStatus.NO_CHANGE; } @Override - public void threadSelected(ThreadReference thread, StackFrame frame, JdiCause cause) { + public DebugStatus threadSelected(ThreadReference thread, StackFrame frame, JdiCause cause) { JdiModelTargetVM vm = vms.getTargetVM(thread.threadGroup().virtualMachine()); JdiModelTargetThread t = vm.threads.getTargetThread(thread); if (frame == null) { setFocus(t); - return; + return DebugStatus.NO_CHANGE; } JdiModelTargetStackFrame f = t.stack.getTargetFrame(frame); setFocus(f); + return DebugStatus.NO_CHANGE; } public void setAccessible(boolean accessible) { @@ -199,7 +202,8 @@ public class JdiModelTargetRoot extends DefaultTargetModelRoot implements // Map defaultArguments = cx.defaultArguments(); Map jdiArgs = JdiModelTargetLauncher.getArguments(defaultArguments, JdiModelTargetLauncher.getParameters(defaultArguments), args); - return getManager().addVM(cx, jdiArgs).thenApply(__ -> null); + getManager().addVM(cx, jdiArgs); + return CompletableFuture.completedFuture(null); } /** diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetStackFrame.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetStackFrame.java index 0c0b4254f4..a8369377af 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetStackFrame.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetStackFrame.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -24,6 +24,7 @@ import com.sun.jdi.*; import ghidra.dbg.DebuggerObjectModel.RefreshBehavior; import ghidra.dbg.jdi.manager.JdiCause; import ghidra.dbg.jdi.manager.JdiEventsListenerAdapter; +import ghidra.dbg.jdi.manager.impl.DebugStatus; import ghidra.dbg.jdi.model.iface1.JdiModelSelectableObject; import ghidra.dbg.jdi.model.iface1.JdiModelTargetFocusScope; import ghidra.dbg.target.TargetFocusScope; @@ -32,8 +33,11 @@ import ghidra.dbg.target.schema.*; import ghidra.program.model.address.Address; import ghidra.util.Msg; -@TargetObjectSchemaInfo(name = "StackFrame", elements = { - @TargetElementType(type = Void.class) }, attributes = { +@TargetObjectSchemaInfo( + name = "StackFrame", + elements = { + @TargetElementType(type = Void.class) }, + attributes = { @TargetAttributeType(type = Object.class) }) public class JdiModelTargetStackFrame extends JdiModelTargetObjectImpl implements TargetStackFrame, // //TargetRegisterBank, // @@ -120,10 +124,12 @@ public class JdiModelTargetStackFrame extends JdiModelTargetObjectImpl implement } @Override - public void threadSelected(ThreadReference eventThread, StackFrame eventFrame, JdiCause cause) { + public DebugStatus threadSelected(ThreadReference eventThread, StackFrame eventFrame, + JdiCause cause) { if (eventThread.equals(thread.thread) && eventFrame.equals(frame)) { ((JdiModelTargetFocusScope) searchForSuitable(TargetFocusScope.class)).setFocus(this); } + return DebugStatus.NO_CHANGE; } @Override diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThread.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThread.java index f6c9b7e6ec..4e10a13ffc 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThread.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThread.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,6 +26,7 @@ import com.sun.jdi.request.StepRequest; import ghidra.async.AsyncFence; import ghidra.dbg.DebuggerObjectModel.RefreshBehavior; import ghidra.dbg.jdi.manager.*; +import ghidra.dbg.jdi.manager.impl.DebugStatus; import ghidra.dbg.jdi.model.iface1.*; import ghidra.dbg.jdi.model.iface2.JdiModelTargetObject; import ghidra.dbg.target.TargetFocusScope; @@ -34,15 +35,27 @@ import ghidra.dbg.target.schema.*; import ghidra.lifecycle.Internal; import ghidra.util.Msg; -@TargetObjectSchemaInfo(name = "Thread", elements = { // - @TargetElementType(type = Void.class) }, attributes = { +@TargetObjectSchemaInfo( + name = "Thread", + elements = { // + @TargetElementType(type = Void.class) }, + attributes = { @TargetAttributeType(name = "Attributes", type = JdiModelTargetAttributesContainer.class), - @TargetAttributeType(name = "Registers", type = JdiModelTargetRegisterContainer.class, required = true, fixed = true), - @TargetAttributeType(name = "Stack", type = JdiModelTargetStack.class, required = true, fixed = true), + @TargetAttributeType( + name = "Registers", + type = JdiModelTargetRegisterContainer.class, + required = true, + fixed = true), + @TargetAttributeType( + name = "Stack", + type = JdiModelTargetStack.class, + required = true, + fixed = true), @TargetAttributeType(name = "Status", type = Integer.class), @TargetAttributeType(name = "UID", type = Long.class, fixed = true), @TargetAttributeType(type = Object.class) // -}, canonicalContainer = true) + }, + canonicalContainer = true) public class JdiModelTargetThread extends JdiModelTargetObjectReference implements // TargetThread, // JdiModelTargetAccessConditioned, // @@ -214,7 +227,7 @@ public class JdiModelTargetThread extends JdiModelTargetObjectReference implemen } @Override - public void stepComplete(StepEvent evt, JdiCause cause) { + public DebugStatus stepComplete(StepEvent evt, JdiCause cause) { if (evt.thread().equals(thread)) { setLocation(evt.location()); changeAttributes(List.of(), List.of(), Map.of( // @@ -222,10 +235,11 @@ public class JdiModelTargetThread extends JdiModelTargetObjectReference implemen ), "Refreshed"); stateChanged(thread.status(), JdiReason.Reasons.STEP); } + return DebugStatus.BREAK; } @Override - public void breakpointHit(BreakpointEvent evt, JdiCause cause) { + public DebugStatus breakpointHit(BreakpointEvent evt, JdiCause cause) { if (evt.thread().equals(thread)) { setLocation(evt.location()); changeAttributes(List.of(), List.of(), Map.of( // @@ -233,12 +247,13 @@ public class JdiModelTargetThread extends JdiModelTargetObjectReference implemen ), "Refreshed"); stateChanged(thread.status(), JdiReason.Reasons.BREAKPOINT_HIT); } + return DebugStatus.BREAK; } // Which of these is actually going to fire, i.e. are separate events generated for subclasses? @Override - public void watchpointHit(WatchpointEvent evt, JdiCause cause) { + public DebugStatus watchpointHit(WatchpointEvent evt, JdiCause cause) { if (evt.thread().equals(thread)) { setLocation(evt.location()); changeAttributes(List.of(), List.of(), Map.of( // @@ -246,10 +261,11 @@ public class JdiModelTargetThread extends JdiModelTargetObjectReference implemen ), "Refreshed"); stateChanged(thread.status(), JdiReason.Reasons.WATCHPOINT_HIT); } + return DebugStatus.BREAK; } @Override - public void accessWatchpointHit(AccessWatchpointEvent evt, JdiCause cause) { + public DebugStatus accessWatchpointHit(AccessWatchpointEvent evt, JdiCause cause) { if (evt.thread().equals(thread)) { setLocation(evt.location()); changeAttributes(List.of(), List.of(), Map.of( // @@ -257,13 +273,16 @@ public class JdiModelTargetThread extends JdiModelTargetObjectReference implemen ), "Refreshed"); stateChanged(thread.status(), JdiReason.Reasons.ACCESS_WATCHPOINT_HIT); } + return DebugStatus.BREAK; } @Override - public void threadSelected(ThreadReference eventThread, StackFrame frame, JdiCause cause) { + public DebugStatus threadSelected(ThreadReference eventThread, StackFrame frame, + JdiCause cause) { if (eventThread.equals(thread) && frame == null) { ((JdiModelTargetFocusScope) searchForSuitable(TargetFocusScope.class)).setFocus(this); } + return DebugStatus.NO_CHANGE; } private void stateChanged(int state, JdiReason reason) { @@ -274,8 +293,7 @@ public class JdiModelTargetThread extends JdiModelTargetObjectReference implemen } targetVM.vmStateChanged(targetState, reason); JdiEventHandler eventHandler = getManager().getEventHandler(targetVM.vm); - eventHandler.listenersEvent.invoke().threadStateChanged(thread, state, - JdiCause.Causes.UNCLAIMED, reason); + eventHandler.processThreadStateChanged(thread, state, reason); } public void threadStateChanged(TargetExecutionState targetState) { @@ -385,8 +403,9 @@ public class JdiModelTargetThread extends JdiModelTargetObjectReference implemen } @Override - public void threadStarted(ThreadStartEvent evt, JdiCause cause) { + public DebugStatus threadStarted(ThreadStartEvent evt, JdiCause cause) { threadSelected(evt.thread(), null, JdiCause.Causes.UNCLAIMED); + return DebugStatus.NO_CHANGE; } @Override diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThreadContainer.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThreadContainer.java index e4a863fd4d..c1e1254640 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThreadContainer.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetThreadContainer.java @@ -25,6 +25,7 @@ import com.sun.jdi.ThreadReference; import ghidra.async.AsyncFence; import ghidra.dbg.DebuggerObjectModel.RefreshBehavior; import ghidra.dbg.jdi.manager.*; +import ghidra.dbg.jdi.manager.impl.DebugStatus; import ghidra.dbg.jdi.model.iface1.JdiModelTargetEventScope; import ghidra.dbg.jdi.model.iface2.JdiModelTargetObject; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; @@ -72,7 +73,7 @@ public class JdiModelTargetThreadContainer extends JdiModelTargetObjectImpl } @Override - public void threadStateChanged(ThreadReference thread, Integer state, JdiCause cause, + public DebugStatus threadStateChanged(ThreadReference thread, Integer state, JdiCause cause, JdiReason reason) { JdiModelTargetThread targetThread = getTargetThread(thread); TargetExecutionState targetState = targetThread.convertState(state); @@ -80,6 +81,7 @@ public class JdiModelTargetThreadContainer extends JdiModelTargetObjectImpl TargetEventType eventType = getEventType(reason); broadcast().event(this, targetThread, eventType, "Thread " + targetThread.getName() + " state changed", List.of(targetThread)); + return DebugStatus.NO_CHANGE; } private TargetEventType getEventType(JdiReason reason) { diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetVM.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetVM.java index 7f38c8c5ea..4b78f581b7 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetVM.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetVM.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -29,6 +29,7 @@ import com.sun.jdi.request.*; import ghidra.async.AsyncFence; import ghidra.dbg.DebuggerObjectModel.RefreshBehavior; import ghidra.dbg.jdi.manager.*; +import ghidra.dbg.jdi.manager.impl.DebugStatus; import ghidra.dbg.jdi.manager.impl.JdiManagerImpl; import ghidra.dbg.jdi.model.iface1.*; import ghidra.dbg.jdi.model.iface2.JdiModelTargetObject; @@ -43,15 +44,35 @@ import ghidra.lifecycle.Internal; * TODO: Implementing {@link TargetLauncher} here doesn't seem right. While it's convenient from a * UI perspective, it doesn't make sense semantically. */ -@TargetObjectSchemaInfo(name = "VM", elements = { - @TargetElementType(type = Void.class) }, attributes = { +@TargetObjectSchemaInfo( + name = "VM", + elements = { + @TargetElementType(type = Void.class) }, + attributes = { @TargetAttributeType(name = "Attributes", type = JdiModelTargetAttributesContainer.class), - @TargetAttributeType(name = "Breakpoints", type = JdiModelTargetBreakpointContainer.class, fixed = true), - @TargetAttributeType(name = "Classes", type = JdiModelTargetClassContainer.class, fixed = true), - @TargetAttributeType(name = "Modules", type = JdiModelTargetModuleContainer.class, fixed = true), - @TargetAttributeType(name = "Threads", type = JdiModelTargetThreadContainer.class, required = true, fixed = true), - @TargetAttributeType(name = "ThreadGroups", type = JdiModelTargetThreadGroupContainer.class, fixed = true), - @TargetAttributeType(type = Object.class) }, canonicalContainer = true) + @TargetAttributeType( + name = "Breakpoints", + type = JdiModelTargetBreakpointContainer.class, + fixed = true), + @TargetAttributeType( + name = "Classes", + type = JdiModelTargetClassContainer.class, + fixed = true), + @TargetAttributeType( + name = "Modules", + type = JdiModelTargetModuleContainer.class, + fixed = true), + @TargetAttributeType( + name = "Threads", + type = JdiModelTargetThreadContainer.class, + required = true, + fixed = true), + @TargetAttributeType( + name = "ThreadGroups", + type = JdiModelTargetThreadGroupContainer.class, + fixed = true), + @TargetAttributeType(type = Object.class) }, + canonicalContainer = true) public class JdiModelTargetVM extends JdiModelTargetObjectImpl implements // TargetProcess, // TargetAggregate, // @@ -156,7 +177,6 @@ public class JdiModelTargetVM extends JdiModelTargetObjectImpl implements // Map attrs = new HashMap<>(); attrs.put("version", vm.version()); attrs.put("description", vm.description()); - attrs.put("canAddMethods", Boolean.valueOf(vm.canAddMethod())); attrs.put("canBeModified", Boolean.valueOf(vm.canBeModified())); attrs.put("canForceEarlyReturn", Boolean.valueOf(vm.canForceEarlyReturn())); attrs.put("canGetBytecodes", Boolean.valueOf(vm.canGetBytecodes())); @@ -176,8 +196,6 @@ public class JdiModelTargetVM extends JdiModelTargetObjectImpl implements // attrs.put("canRedefineClasses", Boolean.valueOf(vm.canRedefineClasses())); attrs.put("canRequestMonitorEvents", Boolean.valueOf(vm.canRequestMonitorEvents())); attrs.put("canRequestVMDeathEvent", Boolean.valueOf(vm.canRequestVMDeathEvent())); - attrs.put("canUnrestrictedlyRedefineClasses", - Boolean.valueOf(vm.canUnrestrictedlyRedefineClasses())); attrs.put("canUseInstanceFilters", Boolean.valueOf(vm.canUseInstanceFilters())); attrs.put("canUseSourceNameFilters", Boolean.valueOf(vm.canUseSourceNameFilters())); attrs.put("canWatchFieldAccess", Boolean.valueOf(vm.canWatchFieldAccess())); @@ -219,7 +237,8 @@ public class JdiModelTargetVM extends JdiModelTargetObjectImpl implements // Map defaultArguments = cx.defaultArguments(); Map jdiArgs = JdiModelTargetLauncher.getArguments(defaultArguments, JdiModelTargetLauncher.getParameters(defaultArguments), args); - return getManager().addVM(cx, jdiArgs).thenApply(__ -> null); + getManager().addVM(cx, jdiArgs); + return CompletableFuture.completedFuture(null); } @Override @@ -293,36 +312,42 @@ public class JdiModelTargetVM extends JdiModelTargetObjectImpl implements // } @Override - public void vmSelected(VirtualMachine eventVM, JdiCause cause) { + public DebugStatus vmSelected(VirtualMachine eventVM, JdiCause cause) { if (eventVM.equals(vm)) { ((JdiModelTargetFocusScope) searchForSuitable(TargetFocusScope.class)).setFocus(this); } + return DebugStatus.NO_CHANGE; } - public void vmStateChanged(TargetExecutionState targetState, JdiReason reason) { + public DebugStatus vmStateChanged(TargetExecutionState targetState, JdiReason reason) { changeAttributes(List.of(), List.of(), Map.of( // STATE_ATTRIBUTE_NAME, targetState // ), reason.desc()); + return DebugStatus.NO_CHANGE; } @Override - public void monitorContendedEntered(MonitorContendedEnteredEvent evt, JdiCause cause) { + public DebugStatus monitorContendedEntered(MonitorContendedEnteredEvent evt, JdiCause cause) { System.err.println(this + ":" + evt); + return DebugStatus.NO_CHANGE; } @Override - public void monitorContendedEnter(MonitorContendedEnterEvent evt, JdiCause cause) { + public DebugStatus monitorContendedEnter(MonitorContendedEnterEvent evt, JdiCause cause) { System.err.println(this + ":" + evt); + return DebugStatus.NO_CHANGE; } @Override - public void monitorWaited(MonitorWaitedEvent evt, JdiCause cause) { + public DebugStatus monitorWaited(MonitorWaitedEvent evt, JdiCause cause) { System.err.println(this + ":" + evt); + return DebugStatus.NO_CHANGE; } @Override - public void monitorWait(MonitorWaitEvent evt, JdiCause cause) { + public DebugStatus monitorWait(MonitorWaitEvent evt, JdiCause cause) { System.err.println(this + ":" + evt); + return DebugStatus.NO_CHANGE; } protected void updateDisplayAttribute() { diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetVMContainer.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetVMContainer.java index 729ef20084..94e03f3ebd 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetVMContainer.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetVMContainer.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,6 +27,7 @@ import ghidra.async.AsyncUtils; import ghidra.dbg.DebuggerObjectModel.RefreshBehavior; import ghidra.dbg.jdi.manager.JdiCause; import ghidra.dbg.jdi.manager.JdiEventsListenerAdapter; +import ghidra.dbg.jdi.manager.impl.DebugStatus; import ghidra.dbg.target.TargetEventScope.TargetEventType; import ghidra.dbg.target.schema.*; import ghidra.util.Msg; @@ -48,14 +49,14 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl protected final Map vmsById = new WeakValueHashMap<>(); public JdiModelTargetVMContainer(JdiModelTargetRoot session) { - super(session, "VirtualMachines"); + super(session, "VMs"); this.session = session; impl.getManager().addEventsListener(null, this); } @Override - public void vmStarted(VMStartEvent event, JdiCause cause) { + public DebugStatus vmStarted(VMStartEvent event, JdiCause cause) { VirtualMachine vm = event.virtualMachine(); JdiModelTargetVM target = getTargetVM(vm); // TODO: Move PROCESS_CREATED here to restore proper order of event reporting @@ -69,10 +70,11 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl }); changeElements(List.of(), List.of(target), Map.of(), "Added"); + return DebugStatus.NO_CHANGE; } @Override - public void vmDied(VMDeathEvent event, JdiCause cause) { + public DebugStatus vmDied(VMDeathEvent event, JdiCause cause) { VirtualMachine vm = event.virtualMachine(); JdiModelTargetVM tgtVM = vmsById.get(vm.name()); broadcast().event(session, null, TargetEventType.PROCESS_EXITED, "VM " + vm.name(), @@ -83,6 +85,7 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl getManager().removeVM(vm); } changeElements(List.of(vm.name()), List.of(), Map.of(), "Removed"); + return DebugStatus.NO_CHANGE; } protected void gatherThreads(List into, JdiModelTargetVM vm, @@ -96,30 +99,32 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl } @Override - public void threadStarted(ThreadStartEvent event, JdiCause cause) { + public DebugStatus threadStarted(ThreadStartEvent event, JdiCause cause) { ThreadReference thread = event.thread(); JdiModelTargetVM vm = getTargetVM(thread.threadGroup().virtualMachine()); if (!vmsById.containsValue(vm)) { Msg.info(this, event + " ignored as vm may have exited"); - return; + return DebugStatus.NO_CHANGE; } JdiModelTargetThread targetThread = vm.threads.threadCreated(thread); broadcast().event(session, targetThread, TargetEventType.THREAD_CREATED, "Thread " + thread.name() + " started", List.of(targetThread)); + return DebugStatus.NO_CHANGE; } @Override - public void threadExited(ThreadDeathEvent event, JdiCause cause) { + public DebugStatus threadExited(ThreadDeathEvent event, JdiCause cause) { ThreadReference thread = event.thread(); JdiModelTargetVM tgtVM = vmsById.get(thread.virtualMachine().name()); JdiModelTargetThread targetThread = tgtVM.threads.threadsById.get(thread.name()); broadcast().event(session, targetThread, TargetEventType.THREAD_EXITED, "Thread " + thread.name() + " exited", List.of(targetThread)); tgtVM.threads.threadExited(thread); + return DebugStatus.NO_CHANGE; } @Override - public void libraryLoaded(VirtualMachine vm, String name, JdiCause cause) { + public DebugStatus classLoaded(VirtualMachine vm, String name, JdiCause cause) { /* JdiModelTargetVM vm = getTargetInferior(inf); JdiModelTargetModule module = vm.modules.libraryLoaded(name); @@ -128,10 +133,11 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl .event(parent, null, TargetEventType.MODULE_LOADED, "Library " + name + " loaded", List.of(module)); */ + return DebugStatus.NO_CHANGE; } @Override - public void libraryUnloaded(VirtualMachine vm, String name, JdiCause cause) { + public DebugStatus classUnloaded(VirtualMachine vm, String name, JdiCause cause) { /* JdiModelTargetVM vm = getTargetInferior(inf); JdiModelTargetModule module = vm.modules.getTargetModuleIfPresent(name); @@ -141,6 +147,7 @@ public class JdiModelTargetVMContainer extends JdiModelTargetObjectImpl "Library " + name + " unloaded", List.of(module)); vm.modules.libraryUnloaded(name); */ + return DebugStatus.NO_CHANGE; } private void updateUsingVMs(Map byName) { diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/JavaTraceRmiLaunchOffer.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/JavaTraceRmiLaunchOffer.java new file mode 100644 index 0000000000..170736ff31 --- /dev/null +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/JavaTraceRmiLaunchOffer.java @@ -0,0 +1,130 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.dbg.jdi.rmi; + +import java.io.File; +import java.io.FileNotFoundException; +import java.net.SocketAddress; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import ghidra.app.plugin.core.debug.gui.tracermi.launcher.*; +import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ScriptAttributes; +import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.TtyCondition; +import ghidra.dbg.jdi.rmi.jpda.JdiClientThread; +import ghidra.debug.api.ValStr; +import ghidra.debug.api.tracermi.TerminalSession; +import ghidra.program.model.listing.Program; +import ghidra.util.task.TaskMonitor; + +/** + * A launcher implemented by a simple UNIX shell script. + * + *

+ * The script must start with an attributes header in a comment block. See + * {@link ScriptAttributesParser}. + */ +public class JavaTraceRmiLaunchOffer extends AbstractScriptTraceRmiLaunchOffer { + public static final String REM = "//"; + public static final int REM_LEN = REM.length(); + + /** + * Create a launch offer from the given shell script. + * + * @param plugin the launcher service plugin + * @param program the current program, usually the target image. In general, this should be used + * for at least two purposes. 1) To populate the default command line. 2) To ensure + * the target image is mapped in the resulting target trace. + * @param script the script file that implements this offer + * @return the offer + * @throws FileNotFoundException if the script file does not exist + */ + public static JavaTraceRmiLaunchOffer create(TraceRmiLauncherServicePlugin plugin, + Program program, File script) throws FileNotFoundException { + ScriptAttributesParser parser = new ScriptAttributesParser() { + @Override + protected boolean ignoreLine(int lineNo, String line) { + return line.isBlank(); + } + + @Override + protected String removeDelimiter(String line) { + String stripped = line.stripLeading(); + if (!stripped.startsWith(REM)) { + return null; + } + return stripped.substring(REM_LEN); + } + }; + ScriptAttributes attrs = parser.parseFile(script); + return new JavaTraceRmiLaunchOffer(plugin, program, script, + "JAVA:" + script.getName(), attrs); + } + + private JavaTraceRmiLaunchOffer(TraceRmiLauncherServicePlugin plugin, + Program program, File script, String configName, ScriptAttributes attrs) { + super(plugin, program, script, configName, attrs); + } + + boolean hasKeyReally(Map env, String key) { + String val = env.get(key); + return val != null && !val.isBlank(); + } + + @Override + protected void launchBackEnd(TaskMonitor monitor, Map sessions, + Map> args, SocketAddress address) throws Exception { + List commandLine = new ArrayList<>(); + Map env = new HashMap<>(System.getenv()); + prepareSubprocess(commandLine, env, args, address); + + for (Map.Entry ent : attrs.extraTtys().entrySet()) { + if (!ent.getValue().isActive(args)) { + continue; + } + NullPtyTerminalSession ns = nullPtyTerminal(); + env.put(ent.getKey(), ns.name()); + sessions.put(ns.name(), ns); + } + + if (hasKeyReally(env, "OPT_JSHELL_PATH")) { + String classPath = computeClassPath(env); + commandLine.add(0, "--startup"); + commandLine.add(0, "--class-path=" + classPath); + commandLine.add(0, env.get("OPT_JSHELL_PATH")); + sessions.put("Shell", + runInTerminal(commandLine, env, script.getParentFile(), sessions.values())); + } + else { + JdiClientThread thread = new JdiClientThread(env); + thread.start(); + } + } + + private String computeClassPath(Map env) { + String sep = File.pathSeparator; + return Stream.of(System.getProperty("java.class.path").split(sep)) + .filter(p -> new File(p).exists()) + .collect(Collectors.joining(sep)); + } + + @Override + public boolean requiresImage() { + return false; + } + +} diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/JavaTraceRmiLaunchOpinion.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/JavaTraceRmiLaunchOpinion.java new file mode 100644 index 0000000000..be64f020fa --- /dev/null +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/JavaTraceRmiLaunchOpinion.java @@ -0,0 +1,51 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.dbg.jdi.rmi; + +import java.util.Collection; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import generic.jar.ResourceFile; +import ghidra.app.plugin.core.debug.gui.tracermi.launcher.AbstractTraceRmiLaunchOpinion; +import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLauncherServicePlugin; +import ghidra.debug.api.tracermi.TraceRmiLaunchOffer; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; + +public class JavaTraceRmiLaunchOpinion extends AbstractTraceRmiLaunchOpinion { + + @Override + public Collection getOffers(TraceRmiLauncherServicePlugin plugin, + Program program) { + return getScriptPaths(plugin.getTool()) + .flatMap(rf -> Stream.of(rf.listFiles(crf -> crf.getName().endsWith(".jsh")))) + .flatMap(sf -> createOffer(plugin, program, sf)) + .collect(Collectors.toList()); + } + + protected Stream createOffer(TraceRmiLauncherServicePlugin plugin, + Program program, ResourceFile scriptFile) { + try { + return Stream.of(JavaTraceRmiLaunchOffer.create(plugin, program, + scriptFile.getFile(false))); + } + catch (Exception e) { + Msg.error(this, "Could not offer " + scriptFile + ": " + e.getMessage(), e); + return Stream.of(); + } + } +} diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/GhidraJdiInit.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/GhidraJdiInit.java new file mode 100644 index 0000000000..d1cf1c5af2 --- /dev/null +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/GhidraJdiInit.java @@ -0,0 +1,56 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.dbg.jdi.rmi.jpda; + +import java.io.IOException; + +import ghidra.GhidraApplicationLayout; +import ghidra.framework.*; + +/** + * This is a convenience class for preparing the JDI Client in a stand-alone jshell. + * + *

+ * For any of the Ghidra stuff to work (including logging), the application needs to be initialized. + * If we're in Ghidra's JVM, then we do not need this. Do not call it! If we're in a separate + * subprocess, e.g., a stand-alone jshell, then we need to call this. Putting it all here lets the + * scripts/user avoid tons of imports. + */ +public class GhidraJdiInit { + /** + * Initialize the Ghidra application using all the defaults. + * + * @throws IOException if the file system can't be read + */ + public static void initApp() throws IOException { + GhidraApplicationLayout layout = new GhidraApplicationLayout(); + GhidraApplicationConfiguration config = new GhidraApplicationConfiguration(); + config.setShowSplashScreen(false); + Application.initializeApplication(layout, config); + } + + /** + * Initialize the Ghidra application in headless mode. + * + * @throws IOException if the file system can't be read + */ + public static void initHeadless() throws IOException { + GhidraApplicationLayout layout = new GhidraApplicationLayout(); + HeadlessGhidraApplicationConfiguration config = + new HeadlessGhidraApplicationConfiguration(); + Application.initializeApplication(layout, config); + } +} diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/JdiClientThread.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/JdiClientThread.java new file mode 100644 index 0000000000..ab2ac07b91 --- /dev/null +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/JdiClientThread.java @@ -0,0 +1,129 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.dbg.jdi.rmi.jpda; + +import java.util.Map; + +import com.sun.jdi.connect.AttachingConnector; +import com.sun.jdi.connect.Connector; +import com.sun.jdi.connect.Connector.Argument; + +import ghidra.dbg.jdi.manager.JdiCause.Causes; +import ghidra.dbg.jdi.manager.impl.JdiManagerImpl; +import ghidra.util.Msg; + +public class JdiClientThread extends Thread { + enum Mode { + ATTACH_PORT, ATTACH_PID, LAUNCH; + } + + private final Map env; + private final Mode mode; + + private JdiManagerImpl manager; + private TraceJdiManager traceJdiManager; + + public JdiClientThread(Map env) { + this.env = env; + this.mode = computeMode(); + } + + /** + * Compute/detect the launch mode using the environment map. + * + *

+ * It'd be nice if this were selected/specified in the script body, rather than by what options + * are present in its header. The reason we can't, though, is that the JDI client thread needs + * to also work within Ghidra's JVM, i.e., without launching a jshell subprocess. By far, the + * simplest way to accomplish this is to keep all the logic here, and just pass the environment + * map in. For the jshell-subprocess case, it's the environment map proper. For the + * in-Ghidra's-VM case, it's the map we would have passed when creating the subprocess. + * + * @return the mode. + */ + Mode computeMode() { + if (env.containsKey("OPT_PORT")) { + return Mode.ATTACH_PORT; + } + if (env.containsKey("OPT_PID")) { + return Mode.ATTACH_PID; + } + return Mode.LAUNCH; + } + + AttachingConnector findConnectorByArgKey(String key) { + return manager.getVirtualMachineManager() + .attachingConnectors() + .stream() + .filter(ac -> ac.defaultArguments().containsKey(key)) + .findFirst() + .orElseThrow(); + } + + @Override + public void run() { + try { + manager = new JdiManagerImpl(); + traceJdiManager = new TraceJdiManager(manager, env); + + Connector cx = switch (mode) { + case ATTACH_PORT -> findConnectorByArgKey("port"); + case ATTACH_PID -> findConnectorByArgKey("pid"); + case LAUNCH -> manager.getVirtualMachineManager().defaultConnector(); + }; + + Map args = cx.defaultArguments(); + putArguments(args); + if (manager.addVM(cx, args) != null) { + traceJdiManager.getCommands().ghidraTraceSyncEnable(); + traceJdiManager.getHooks().vmStarted(null, Causes.UNCLAIMED); + } + else { + // Nothing. addVM should already have reported the error. + } + } + catch (Exception e) { + Msg.error(this, "Could not start the JDI client", e); + } + } + + protected void putArguments(Map args) { + switch (mode) { + case ATTACH_PORT -> { + args.get("hostname").setValue(env.get("OPT_HOST").toString()); + args.get("port").setValue(env.get("OPT_PORT").toString()); + args.get("timeout").setValue(env.get("OPT_TIMEOUT").toString()); + } + case ATTACH_PID -> { + args.get("pid").setValue(env.get("OPT_PID").toString()); + args.get("timeout").setValue(env.get("OPT_TIMEOUT").toString()); + } + case LAUNCH -> { + args.get("main").setValue(env.get("OPT_TARGET_CLASS")); + //args.get("suspend").setValue(env.get("OPT_SUSPEND")); + args.get("includevirtualthreads").setValue(env.get("OPT_INCLUDE")); + } + } + } + + public TraceJdiManager mgr() { + return traceJdiManager; + } + + public TraceJdiCommands cmds() { + return traceJdiManager.getCommands(); + } +} diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiArch.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiArch.java new file mode 100644 index 0000000000..93d360eacd --- /dev/null +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiArch.java @@ -0,0 +1,91 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.dbg.jdi.rmi.jpda; + +import java.util.HashMap; +import java.util.Map; + +import ghidra.app.plugin.core.debug.client.tracermi.DefaultMemoryMapper; +import ghidra.app.plugin.core.debug.client.tracermi.DefaultRegisterMapper; +import ghidra.program.model.lang.*; +import ghidra.program.util.DefaultLanguageService; + +public class TraceJdiArch { + + private LanguageID langID; + private Language language; + + private final LanguageService languageService = DefaultLanguageService.getLanguageService(); + + public String getArch() { + Map env = new HashMap<>(System.getenv()); + String arch = "JVM"; + if (env.containsKey("OPT_ARCH")) { + arch = env.get("OPT_ARCH"); + } + return arch.equals("Dalvik") ? "Dalvik" : "JVM"; + } + + public String getEndian() { + return "big"; + } + + public String getOSABI() { + Map env = new HashMap<>(System.getenv()); + String arch = "JVM"; + if (env.containsKey("OPT_ARCH")) { + arch = env.get("OPT_ARCH"); + } + return arch.equals("Dalvik") ? "Dalvik:LE:32:default" : "JVM:BE:32:default"; + } + + public LanguageID computeGhidraLanguage() { + return new LanguageID(getOSABI()); + } + + public CompilerSpecID computeGhidraCompiler(LanguageID id) { + return new CompilerSpecID("default"); + } + + public void computeGhidraLcsp() { + langID = computeGhidraLanguage(); + try { + language = languageService.getLanguage(langID); + } + catch (LanguageNotFoundException e) { + throw new RuntimeException(e); + } + } + + public DefaultMemoryMapper computeMemoryMapper() { + if (langID == null) { + computeGhidraLcsp(); + } + return new DefaultMemoryMapper(langID); + } + + public DefaultRegisterMapper computeRegisterMapper() { + if (langID == null) { + computeGhidraLcsp(); + } + return new DefaultRegisterMapper(langID); + } + + public Language getLanguage() { + return language; + } + +} diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiCommands.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiCommands.java new file mode 100644 index 0000000000..4299d79b2b --- /dev/null +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiCommands.java @@ -0,0 +1,2081 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.dbg.jdi.rmi.jpda; + +import java.io.IOException; +import java.lang.ProcessHandle.Info; +import java.math.BigInteger; +import java.net.InetSocketAddress; +import java.nio.channels.*; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ExecutionException; + +import com.sun.jdi.*; +import com.sun.jdi.Method; +import com.sun.jdi.Value; +import com.sun.jdi.event.Event; +import com.sun.jdi.request.*; + +import ghidra.app.plugin.core.debug.client.tracermi.*; +import ghidra.dbg.jdi.manager.impl.JdiManagerImpl; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.lang.Language; +import ghidra.rmi.trace.TraceRmi.*; +import ghidra.util.Msg; + +/* + * Some notes: + * ghidraTracePutX: batch wrapper around putXxxx + * putX: generally creates the object and calls putXDetails + * putXDetails: assumes the object already exists + * putXContainer: creates one or more objects + * xProxy: container for exactly one X + */ + +class State { + + RmiClient client; + public RmiTrace trace; + RmiTransaction tx; + + public RmiClient requireClient() { + if (client == null) { + throw new RuntimeException("Not connected"); + } + return client; + } + + public void requireNoClient() { + if (client != null) { + client = null; + throw new RuntimeException("Already connected"); + } + } + + public void resetClient() { + client = null; + resetTrace(); + } + + public RmiTrace requireTrace() { + if (trace == null) { + throw new RuntimeException("No trace started"); + } + return trace; + } + + public void requireNoTrace() { + if (trace != null) { + throw new RuntimeException("Trace already started"); + } + } + + public void resetTrace() { + trace = null; + resetTx(); + } + + public RmiTransaction requireTx() { + if (tx == null) { + throw new RuntimeException("No transaction"); + } + return tx; + } + + public void requireNoTx() { + if (tx != null) { + throw new RuntimeException("Transaction already started"); + } + } + + public void resetTx() { + tx = null; + } + +} + +public class TraceJdiCommands { + + private TraceJdiManager manager; + private JdiManagerImpl jdi; + + public State state; + private String[] regNames = { "PC", "return_address" }; + public long MAX_REFS = 100; + +// protected static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of( // +// TargetStepKind.FINISH, // +// TargetStepKind.LINE, // +// TargetStepKind.OVER, // +// TargetStepKind.OVER_LINE, // +// TargetStepKind.RETURN, // +// TargetStepKind.UNTIL, // +// TargetStepKind.EXTENDED); + + public TraceJdiCommands(TraceJdiManager manager) { + this.manager = manager; + this.jdi = manager.getJdi(); + state = new State(); + } + + public void ghidraTraceConnect(String address) { + state.requireNoClient(); + String[] addr = address.split(":"); + if (addr.length != 2) { + throw new RuntimeException("Address must be in the form 'host:port'"); + } + try { + SocketChannel channel = + SocketChannel.open(new InetSocketAddress(addr[0], Integer.parseInt(addr[1]))); + state.client = new RmiClient(channel, "jdi"); + state.client.setRegistry(manager.remoteMethodRegistry); + state.client.negotiate("Connect"); + Msg.info(this, "Connected to " + state.client.getDescription() + " at " + address); + } + catch (NumberFormatException e) { + throw new RuntimeException("Port must be numeric"); + } + catch (IOException e) { + throw new RuntimeException("Error connecting to " + address + ": " + e); + } + } + + public void ghidraTraceListen(String address) { + // TODO: UNTESTED + state.requireNoClient(); + String host = "0.0.0.0"; + int port = 0; + if (address != null) { + String[] parts = address.split(":"); + if (parts.length == 1) { + port = Integer.parseInt(parts[0]); + } + else { + host = parts[0]; + port = Integer.parseInt(parts[1]); + } + } + try { + InetSocketAddress socketAddress = new InetSocketAddress(host, port); + ServerSocketChannel channel = ServerSocketChannel.open(); + channel.bind(socketAddress); + Selector selector = Selector.open(); + while (true) { + selector.select(); + Set selKeys = selector.selectedKeys(); + Iterator keyIterator = selKeys.iterator(); + + while (keyIterator.hasNext()) { + SelectionKey key = keyIterator.next(); + if (key.isAcceptable()) { + SocketChannel client = channel.accept(); + state.client = new RmiClient(client, "jdi"); + state.client.setRegistry(manager.remoteMethodRegistry); + client.configureBlocking(false); + Msg.info(this, "Connected from " + state.client.getDescription() + " at " + + client.getLocalAddress()); + } + } + keyIterator.remove(); + } + } + catch (NumberFormatException e) { + throw new RuntimeException("Port must be numeric"); + } + catch (IOException e) { + throw new RuntimeException("Error connecting to " + address + ": " + e); + } + } + + public void ghidraTraceDisconnect() { + state.requireClient().close(); + state.resetClient(); + } + + private String computeName() { + VirtualMachine currentVM = manager.getJdi().getCurrentVM(); + if (currentVM != null) { + Optional command = currentVM.process().info().command(); + if (command.isPresent()) { + return "jdi/" + command; + } + } + return "jdi/noname"; + } + + public void startTrace(String name) { + TraceJdiArch arch = manager.getArch(); + LanguageID language = arch.computeGhidraLanguage(); + CompilerSpecID compiler = arch.computeGhidraCompiler(language); + state.trace = state.client.createTrace(name, language, compiler); + + state.trace.memoryMapper = arch.computeMemoryMapper(); + state.trace.registerMapper = arch.computeRegisterMapper(); + + try (RmiTransaction tx = state.trace.startTx("Create snapshots", false)) { + state.trace.createRootObject(manager.rootSchema.getContext(), + manager.rootSchema.getName().toString()); + //activate(null); + + // add the DEFAULT_SECTION + AddressRange range = manager.defaultRange; + byte[] bytes = new byte[(int) range.getLength()]; + Arrays.fill(bytes, (byte) 0xFF); + state.trace.putBytes(range.getMinAddress(), bytes, state.trace.getSnap()); + } + } + + public void ghidraTraceStart(String name) { + state.requireClient(); + if (name == null) { + name = computeName(); + } + state.requireNoTrace(); + startTrace(name); + } + + public void ghidraTraceStop() { + state.requireTrace().close(); + state.resetTrace(); + } + + public void ghidraTraceRestart(String name) { + state.requireClient(); + if (state.trace != null) { + state.trace.close(); + state.resetTrace(); + } + if (name == null) { + name = computeName(); + } + startTrace(name); + } + + public void ghidraTraceInfo() { + if (state.client == null) { + Msg.error(this, "Not connected to Ghidra"); + } + Msg.info(this, "Connected to" + state.client.getDescription()); + if (state.trace == null) { + Msg.error(this, "No trace"); + } + Msg.info(this, "Trace active"); + } + + public void ghidraTraceInfoLcsp() { + TraceJdiArch arch = manager.getArch(); + LanguageID language = arch.computeGhidraLanguage(); + CompilerSpecID compiler = arch.computeGhidraCompiler(language); + Msg.info(this, "Selected Ghidra language: " + language); + Msg.info(this, "Selected Ghidra compiler: " + compiler); + } + + public void ghidraTraceTxStart(String description) { + state.requireTx(); + state.tx = state.requireTrace().startTx(description, false); + } + + public void ghidraTraceTxCommit() { + state.requireTx().commit(); + state.resetTx(); + } + + public void ghidraTraceTxAbort() { + RmiTransaction tx = state.requireTx(); + Msg.info(this, "Aborting trace transaction!"); + tx.abort(); + state.resetTx(); + } + + public void ghidraTraceSave() { + state.requireTrace().save(); + } + + public long ghidraTraceNewSnap(String description) { + state.requireTx(); + return state.requireTrace().snapshot(description, null, null); + } + + public void ghidraTraceSetSnap(long snap) { + state.requireTrace().setSnap(snap); + } + + public void ghidraTracePutMem(Address address, long length) { + state.requireTx(); + try (RmiTransaction tx = state.trace.startTx("ghidraTracePutMem", false)) { + putMem(address, length, true); + } + } + + public void ghidraTracePutMemState(Address address, long length, MemoryState memState) { + state.requireTrace(); + try (RmiTransaction tx = state.trace.startTx("ghidraTracePutMemState", false)) { + putMemState(address, length, memState, true); + } + } + + public void ghidraTraceDelMem(Address address, long length) { + state.requireTrace(); + try (RmiTransaction tx = state.trace.startTx("ghidraTraceDelMem", false)) { + VirtualMachine currentVM = manager.getJdi().getCurrentVM(); + Address mapped = state.trace.memoryMapper.map(address); + AddressRangeImpl range = new AddressRangeImpl(mapped, mapped.add(length - 1)); + state.trace.deleteBytes(range, state.trace.getSnap()); + } + } + + public void ghidraTracePutReg(StackFrame frame) { + state.requireTrace(); + try (RmiTransaction tx = state.trace.startTx("ghidraTracePutReg", false)) { + putReg(frame); + } + } + + public void ghidraTraceDelReg(StackFrame frame) { + state.requireTrace(); + try (RmiTransaction tx = state.trace.startTx("ghidraTraceDelReg", false)) { + String ppath = getPath(frame); + if (ppath == null) { + Msg.error(this, "Null path for " + frame); + return; + } + String path = ppath + ".Registers"; + state.trace.deleteRegisters(path, regNames, state.trace.getSnap()); + } + } + + public void ghidraTraceCreateObj(String path) { + state.requireTx(); + try (RmiTransaction tx = state.trace.startTx("ghidraTraceCreateObj", false)) { + createObject(path); + } + } + + public void ghidraTraceInsertObj(String path) { + state.requireTx(); + try (RmiTransaction tx = state.trace.startTx("ghidraTraceInsertObj", false)) { + state.trace.proxyObjectPath(path).insert(state.trace.getSnap(), Resolution.CR_ADJUST); + } + } + + public void ghidraTraceRemoveObj(String path) { + state.requireTx(); + try (RmiTransaction tx = state.trace.startTx("ghidraTraceRemoveObj", false)) { + state.trace.proxyObjectPath(path).remove(state.trace.getSnap(), false); + } + } + +// public void ghidraTraceSetValue(String path, String key, Object value, TargetObjectSchema schema) { +// } + + public void ghidraTraceRetainValues(String kind, String path, Set keys) { + state.requireTx(); + ValueKinds kinds = ValueKinds.VK_ELEMENTS; + if (kind != null && kind.startsWith("--")) { + if (kind.equals("--elements")) { + kinds = ValueKinds.VK_ELEMENTS; + } + if (kind.equals("--attributes")) { + kinds = ValueKinds.VK_ATTRIBUTES; + } + if (kind.equals("--both")) { + kinds = ValueKinds.VK_BOTH; + } + } + state.trace.proxyObjectPath(path).retainValues(keys, state.trace.getSnap(), kinds); + } + + public RmiTraceObject ghidraTraceGetObj(String path) { + state.requireTrace(); + return state.trace.proxyObjectPath(path); + } + +// public void ghidraTraceGetValues(String pattern) { +// } +// +// public void ghidraTraceGetValuesRng() { +// } + + public void ghidraTracePutVMs() { + state.requireTrace(); + try (RmiTransaction tx = state.trace.startTx("ghidraTracePutVMs", false)) { + putVMs(); + } + } + + public void ghidraTracePutProcesses() { + state.requireTrace(); + try (RmiTransaction tx = state.trace.startTx("ghidraTracePutVMs", false)) { + putProcesses(); + } + } + + public void ghidraTracePutBreakpoints() { + state.requireTrace(); + try (RmiTransaction tx = state.trace.startTx("ghidraTracePutBreakpoints", false)) { + putBreakpoints(); + } + } + + public void ghidraTracePutEvents() { + state.requireTrace(); + try (RmiTransaction tx = state.trace.startTx("ghidraTracePutEvents", false)) { + putEvents(); + } + } + + public void activate(String path) { + state.requireTrace(); + if (path == null) { + VirtualMachine currentVM = manager.getJdi().getCurrentVM(); + path = getPath(currentVM); + try { + ThreadReference currentThread = manager.getJdi().getCurrentThread(); + if (currentThread != null) { + path = getPath(currentThread); + } + StackFrame currentFrame = manager.getJdi().getCurrentFrame(); + if (currentFrame != null) { + path = getPath(currentFrame); + } + } + catch (VMDisconnectedException discExc) { + Msg.info(this, "Activate failed - VM disconnected"); + } + } + state.trace.activate(path); + } + + public void ghidraTraceActivate(String path) { + activate(path); + } + + public void ghidraTraceDisassemble(Address address) { + state.requireTrace(); + VirtualMachine currentVM = manager.getJdi().getCurrentVM(); + MemoryMapper mapper = state.trace.memoryMapper; + Address mappedAddress = mapper.map(address); + AddressSpace addressSpace = mappedAddress.getAddressSpace(); + if (!addressSpace.equals(address.getAddressSpace())) { + state.trace.createOverlaySpace(mappedAddress, address); + } + state.trace.disassemble(mappedAddress, state.trace.getSnap()); + } + + // STATE // + + public void putMemState(Address start, long length, MemoryState memState, boolean usePages) { + VirtualMachine currentVM = manager.getJdi().getCurrentVM(); + Address mapped = state.trace.memoryMapper.map(start); + if (mapped.getAddressSpace() != start.getAddressSpace() && + !memState.equals(MemoryState.MS_UNKNOWN)) { + state.trace.createOverlaySpace(mapped, start); + } + AddressRangeImpl range = new AddressRangeImpl(mapped, mapped.add(length - 1)); + state.trace.setMemoryState(range, memState, state.trace.getSnap()); + } + + public void putReg(StackFrame frame) { + String ppath = getPath(frame); + if (ppath == null) { + Msg.error(this, "Null path for " + frame); + return; + } + String path = ppath + ".Registers"; + state.trace.createOverlaySpace("register", path); + RegisterValue[] rvs = putRegisters(frame, path); + state.trace.putRegisters(path, rvs, state.trace.getSnap()); + } + + public RegisterValue[] putRegisters(StackFrame frame, String ppath) { + TraceJdiArch arch = manager.getArch(); + Language lang = arch.getLanguage(); + Set keys = new HashSet<>(); + RegisterValue[] rvs = new RegisterValue[regNames.length]; + + int ireg = 0; + String r = regNames[0]; + Register register = lang.getRegister(r); + keys.add(manager.key(r)); + Location loc = frame.location(); + Address addr = putRegister(ppath, r, loc); + RegisterValue rv = new RegisterValue(register, BigInteger.valueOf(addr.getOffset())); + rvs[ireg++] = rv; + + r = regNames[1]; + register = lang.getRegister(r); + keys.add(manager.key(r)); + ThreadReference thread = frame.thread(); + Location ploc = null; + int frameCount; + try { + frameCount = thread.frameCount(); + for (int i = 0; i < frameCount; i++) { + StackFrame f = thread.frame(i); + if (f.equals(frame) && i < frameCount - 1) { + ploc = thread.frame(i + 1).location(); + } + } + } + catch (IncompatibleThreadStateException e) { + // IGNORE + } + if (ploc != null) { + addr = putRegister(ppath, r, ploc); + rv = new RegisterValue(register, BigInteger.valueOf(addr.getOffset())); + rvs[ireg++] = rv; + } + else { + rv = new RegisterValue(register, BigInteger.valueOf(0L)); + rvs[ireg++] = rv; + } + + retainKeys(ppath, keys); + return rvs; + } + + public void putCurrentLocation() { + Location loc = manager.getJdi().getCurrentLocation(); + if (loc == null) { + return; + } + Method m = loc.method(); + VirtualMachine vm = manager.getJdi().getCurrentVM(); + if (manager.getAddressRange(m.declaringType()) == null) { + putReferenceType(getPath(vm) + ".Classes", m.declaringType(), true); + } + else { + updateMemoryForMethod(m); + } + } + + public Address putRegister(String ppath, String name, Location loc) { + Address addr = manager.getAddressFromLocation(loc); + RegisterMapper mapper = state.trace.registerMapper; + String regName = mapper.mapName(name); + TraceJdiArch arch = manager.getArch(); + Language lang = arch.getLanguage(); + Register register = lang.getRegister(name); + RegisterValue rv = new RegisterValue(register, addr.getOffsetAsBigInteger()); + RegisterValue mapped = mapper.mapValue(name, rv); + Address regAddr = addr.getNewAddress(mapped.getUnsignedValue().longValue()); + setValue(ppath, manager.key(regName), Long.toHexString(regAddr.getOffset())); + + int codeIndex = (int) loc.codeIndex(); + regAddr = regAddr.subtract(codeIndex); + putMem(regAddr, codeIndex + 1, false); + + return addr; + } + + public void putMem(Address address, long length, boolean create) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + MemoryMapper mapper = state.trace.memoryMapper; + Address mappedAddress = mapper.map(address); + AddressSpace addressSpace = mappedAddress.getAddressSpace(); + if (!addressSpace.equals(address.getAddressSpace())) { + state.trace.createOverlaySpace(mappedAddress, address); + } + int ilen = (int) length; + // NB: Right now, we return a full page even if the method/reftype + // is missing. Probably should do something saner, e.g. mark it as an error, + // but gets tricky given all the possible callers. + byte[] bytes = new byte[ilen]; + Arrays.fill(bytes, (byte) 0xFF); + if (addressSpace.getName().equals("ram")) { + Method method = manager.getMethodForAddress(address); + if (method != null) { + byte[] bytecodes = method.bytecodes(); + if (bytecodes != null) { + bytes = Arrays.copyOf(bytecodes, ilen); + } + state.trace.putBytes(mappedAddress, bytes, state.trace.getSnap()); + } + else { + if (create) { + throw new RuntimeException("Attempt to create existing memory"); + } + } + return; + } + if (addressSpace.getName().equals("constantPool")) { + ReferenceType reftype = manager.getReferenceTypeForPoolAddress(address); + if (reftype != null) { + byte[] bytecodes = reftype.constantPool(); + if (bytecodes != null) { + bytes = Arrays.copyOf(bytecodes, ilen); + } + state.trace.putBytes(mappedAddress, bytes, state.trace.getSnap()); + } + return; + } + throw new RuntimeException(); + } + + // TYPES // + + public void putType(String ppath, String key, Type type) { + String path = createObject(type, key, ppath); + putTypeDetails(path, type); + insertObject(path); + } + + public void putTypeDetails(String path, Type type) { + setValue(path, "_display", "Type: " + type.name()); + setValue(path, "Signature", type.signature()); + } + + public void putReferenceTypeContainer(String ppath, List reftypes) { + Set keys = new HashSet<>(); + for (ReferenceType ref : reftypes) { + keys.add(manager.key(ref.name())); + putReferenceType(ppath, ref, false); + } + retainKeys(ppath, keys); + } + + public void putReferenceType(String ppath, ReferenceType reftype, boolean load) { + String path = createObject(reftype, reftype.name(), ppath); + if (manager.getAddressRange(reftype) == null) { + manager.bumpRamIndex(); + } + if (load) { + registerMemory(path, reftype); + } + putReferenceTypeDetails(path, reftype); + insertObject(path); + } + + public void putReferenceTypeDetails(String path, ReferenceType reftype) { + String name = reftype.name(); + if (name.indexOf(".") > 0) { + name = name.substring(name.lastIndexOf(".") + 1); + } + setValue(path, TraceJdiManager.MODULE_NAME_ATTRIBUTE_NAME, name + ".class"); + putRefTypeAttributes(path, reftype); + createObject(path + ".Fields"); + createObject(path + ".Instances"); + createObject(path + ".Locations"); + //createObject(path + ".Methods"); + putMethodContainer(path + ".Methods", reftype); + + String rpath = createObject(path + ".Relations"); + ModuleReference module = reftype.module(); + createObject(module, module.name(), rpath + ".ModuleRef"); + if (reftype instanceof ArrayType at) { + putArrayTypeDetails(rpath, at); + } + if (reftype instanceof ClassType ct) { + putClassTypeDetails(rpath, ct); + } + if (reftype instanceof InterfaceType it) { + putInterfaceTypeDetails(rpath, it); + } + } + + private void putRefTypeAttributes(String ppath, ReferenceType reftype) { + String path = createObject(ppath + ".Attributes"); + if (reftype instanceof ArrayType) { + return; + } + try { + setValue(path, "isAbstract", reftype.isAbstract()); + setValue(path, "isFinal", reftype.isFinal()); + setValue(path, "isInitialized", reftype.isInitialized()); + setValue(path, "isPackagePrivate", reftype.isPackagePrivate()); + setValue(path, "isPrepared", reftype.isPrepared()); + setValue(path, "isPrivate", reftype.isPrivate()); + setValue(path, "isProtected", reftype.isProtected()); + setValue(path, "isPublic", reftype.isPublic()); + setValue(path, "isStatic", reftype.isStatic()); + setValue(path, "isVerified", reftype.isVerified()); + } + catch (Exception e) { + if (e instanceof ClassNotLoadedException) { + setValue(path, "status", "Class not loaded"); + } + } + setValue(path, "defaultStratum", reftype.defaultStratum()); + setValue(path, "availableStata", reftype.availableStrata()); + setValue(path, "failedToInitialize", reftype.failedToInitialize()); + } + + private void registerMemory(String path, ReferenceType reftype) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + String mempath = getPath(vm) + ".Memory"; + AddressSet bounds = new AddressSet(); + for (Method m : reftype.methods()) { + if (m.location() != null) { + AddressRange range = manager.registerAddressesForMethod(m); + if (range != null && range.getMinAddress().getOffset() != 0) { + putMem(range.getMinAddress(), range.getLength(), true); + bounds.add(range); + + String mpath = createObject(mempath + manager.key(m.toString())); + setValue(mpath, "Range", range); + insertObject(path); + } + } + } + AddressRange range = manager.putAddressRange(reftype, bounds); + setValue(path, "Range", range); + + setValue(path, "Count", reftype.constantPoolCount()); + range = manager.getPoolAddressRange(reftype, getSize(reftype) - 1); + setValue(path, "RangeCP", range); + try { + putMem(range.getMinAddress(), range.getLength(), true); + } + catch (RuntimeException e) { + // Ignore + } + } + + private void updateMemoryForMethod(Method m) { + if (m.location() != null) { + AddressRange range = manager.registerAddressesForMethod(m); + if (range != null && range.getMinAddress().getOffset() != 0) { + putMem(range.getMinAddress(), range.getLength(), true); + } + } + } + + public void loadReferenceType(String ppath, List reftypes, String targetClass) { + List classes = reftypes; + for (ReferenceType ref : classes) { + if (ref.name().contains(targetClass)) { + putReferenceType(ppath, ref, true); + } + } + } + + public void putArrayTypeDetails(String path, ArrayType type) { + setValue(path, "ComponentSignature", type.componentSignature()); + setValue(path, "ComponentTypeName", type.componentTypeName()); + createObject(path + ".ComponentType"); + } + + public void putClassTypes(String ppath, List reftypes) { + Set keys = new HashSet<>(); + for (ClassType ref : reftypes) { + keys.add(manager.key(ref.name())); + putReferenceType(ppath, ref, true); + } + retainKeys(ppath, keys); + } + + public void putClassTypeDetails(String path, ClassType type) { + setValue(path, "IsEnum", type.isEnum()); + createObject(path + ".AllInterfaces"); + createObject(path + ".Interfaces"); + createObject(path + ".SubClasses"); + createObject(path + ".ClassType"); + } + + public void putInterfaceTypes(String ppath, List reftypes) { + Set keys = new HashSet<>(); + for (ReferenceType ref : reftypes) { + keys.add(manager.key(ref.name())); + putReferenceType(ppath, ref, true); + } + retainKeys(ppath, keys); + } + + public void putInterfaceTypeDetails(String path, InterfaceType type) { + createObject(path + ".Implementors"); + createObject(path + ".SubInterfaces"); + createObject(path + ".SuperInterfaces"); + } + + // VALUES // + + public void putValueContainer(String path, List values) { + for (Value v : values) { + putValue(path, v.toString(), v); + } + } + + public void putValue(String ppath, String key, Value value) { + String path = createObject(value, key, ppath); + setValue(path, "_display", "Value: " + value.toString()); + //putValueDetailsByType(path, value); + insertObject(path); + } + + public void putValueDetailsByType(String path, Value value) { + if (value instanceof PrimitiveValue pval) { + putPrimitiveValue(path, pval); + } + else if (value instanceof ArrayReference aref) { + putArrayReferenceDetails(path, aref); + } + else if (value instanceof ClassLoaderReference aref) { + putClassLoaderReferenceDetails(path, aref); + } + else if (value instanceof ClassObjectReference aref) { + putClassObjectReferenceDetails(path, aref); + } + else if (value instanceof ModuleReference aref) { + putModuleReferenceDetails(path, aref); + } + else if (value instanceof StringReference aref) { + putStringReferenceDetails(path, aref); + } + else if (value instanceof ThreadGroupReference aref) { + putThreadGroupReferenceDetails(path, aref); + } + else if (value instanceof ThreadReference aref) { + putThreadReferenceDetails(path, aref); + } + else if (value instanceof ObjectReference oref) { + putObjectReferenceDetails(path, oref); + } + } + + public void putValueDetails(String path, Value value) { + putType(path, "Type", value.type()); + } + + public void putPrimitiveValue(String ppath, PrimitiveValue value) { + String path = createObject(value, value.toString(), ppath); + putValueDetails(path, value); + if (value instanceof BooleanValue v) { + setValue(path, "Value", v.booleanValue()); + } + if (value instanceof ByteValue b) { + setValue(path, "Value", b.byteValue()); + } + if (value instanceof CharValue v) { + setValue(path, "Value", v.charValue()); + } + if (value instanceof ShortValue v) { + setValue(path, "Value", v.shortValue()); + } + if (value instanceof IntegerValue v) { + setValue(path, "Value", v.intValue()); + } + if (value instanceof LongValue v) { + setValue(path, "Value", v.longValue()); + } + if (value instanceof FloatValue v) { + setValue(path, "Value", v.floatValue()); + } + if (value instanceof DoubleValue v) { + setValue(path, "Value", v.doubleValue()); + } + insertObject(path); + } + + private Value getPrimitiveValue(Value value, String newVal) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + if (value instanceof BooleanValue) { + return vm.mirrorOf(Boolean.valueOf(newVal)); + } + if (value instanceof ByteValue) { + return vm.mirrorOf(Byte.valueOf(newVal)); + } + if (value instanceof CharValue) { + return vm.mirrorOf(newVal.charAt(0)); + } + if (value instanceof ShortValue) { + return vm.mirrorOf(Short.valueOf(newVal)); + } + if (value instanceof IntegerValue) { + return vm.mirrorOf(Integer.valueOf(newVal)); + } + if (value instanceof LongValue) { + return vm.mirrorOf(Long.valueOf(newVal)); + } + if (value instanceof FloatValue) { + return vm.mirrorOf(Float.valueOf(newVal)); + } + if (value instanceof DoubleValue) { + return vm.mirrorOf(Double.valueOf(newVal)); + } + if (value instanceof StringReference) { + return vm.mirrorOf(newVal); + } + return null; + } + + public void modifyValue(LocalVariable lvar, String valstr) { + String path = getPath(lvar); + String ppath = getParentPath(path); + Object parent = manager.objForPath(ppath); + if (parent instanceof StackFrame frame) { + Value orig = frame.getValue(lvar); + Value repl = getPrimitiveValue(orig, valstr); + if (repl != null) { + try { + frame.setValue(lvar, repl); + } + catch (InvalidTypeException e) { + Msg.error(this, "Invalid type for " + lvar); + } + catch (ClassNotLoadedException e) { + Msg.error(this, "Class not loaded for " + lvar); + } + + putLocalVariable(ppath + ".Variables", lvar, repl); + } + } + Msg.error(this, "Cannot set value for " + lvar); + } + + public void modifyValue(Field field, String valstr) { + String path = getPath(field); + String ppath = getParentPath(path); + Object parent = manager.objForPath(ppath); + if (parent instanceof ObjectReference ref) { + Value orig = ref.getValue(field); + Value repl = getPrimitiveValue(orig, valstr); + if (repl != null) { + try { + ref.setValue(field, repl); + } + catch (InvalidTypeException e) { + Msg.error(this, "Invalid type for " + field); + } + catch (ClassNotLoadedException e) { + Msg.error(this, "Class not loaded for " + field); + } + putField(ppath + ".Variables", field, repl); + } + } + Msg.error(this, "Cannot set value for " + field); + } + + public void putObjectContainer(String path, List objects) { + for (ObjectReference obj : objects) { + createObject(obj, obj.toString(), path); + } + } + + public void putObjectReference(String ppath, ObjectReference ref) { + String path = createObject(ref, ref.toString(), ppath); + putObjectReferenceDetails(path, ref); + insertObject(path); + } + + public void putObjectReferenceDetails(String path, ObjectReference ref) { + putValueDetails(path, ref); + setValue(path, "UniqueId", ref.uniqueID()); + String apath = createObject(path + ".Attributes"); + try { + setValue(apath, "entryCount", ref.entryCount()); + } + catch (IncompatibleThreadStateException e) { + // IGNORE + } + setValue(apath, "isCollected", ref.isCollected()); + String rpath = createObject(path + ".Relations"); + try { + if (ref.owningThread() != null) { + createObject(rpath + ".OwningThread"); + } + if (ref.waitingThreads() != null) { + createObject(rpath + ".WaitingThreads"); + } + } + catch (IncompatibleThreadStateException e) { + // IGNORE + } + if (ref.referenceType() != null) { + createObject(rpath + ".ReferenceType"); + } + if (ref.referringObjects(MAX_REFS) != null) { + createObject(rpath + ".ReferringObjects"); + } + if (!(ref instanceof ArrayReference)) { + createObject(path + ".Variables"); + } + } + + public void putArrayReference(String ppath, ArrayReference ref) { + String path = createObject(ref, ref.toString(), ppath); + putArrayReferenceDetails(path, ref); + insertObject(path); + } + + public void putArrayReferenceDetails(String path, ArrayReference ref) { + putObjectReferenceDetails(path, ref); + setValue(path, "Length", ref.length()); + createObject(path + ".Values"); + } + + public void putClassLoaderReference(String ppath, ClassLoaderReference ref) { + String path = createObject(ref, ref.toString(), ppath); + putClassLoaderReferenceDetails(path, ref); + insertObject(path); + } + + public void putClassLoaderReferenceDetails(String path, ClassLoaderReference ref) { + putObjectReferenceDetails(path, ref); + createObject(path + ".DefinedClasses"); + createObject(path + ".VisibleClasses"); + } + + public void putClassObjectReference(String ppath, ClassObjectReference ref) { + String path = createObject(ref, ref.toString(), ppath); + putClassObjectReferenceDetails(path, ref); + insertObject(path); + } + + public void putClassObjectReferenceDetails(String path, ClassObjectReference ref) { + putObjectReferenceDetails(path, ref); + createObject(path + ".ReflectedType"); + } + + public void putModuleReferenceContainer() { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + String ppath = getPath(vm) + ".ModuleRefs"; + Set keys = new HashSet<>(); + List modules = vm.allModules(); + for (ModuleReference ref : modules) { + keys.add(manager.key(ref.name())); + createObject(ref, ref.name(), ppath); + } + retainKeys(ppath, keys); + } + + public void putModuleReference(String ppath, ModuleReference ref) { + String path = createObject(ref, ref.name(), ppath); + putModuleReferenceDetails(path, ref); + insertObject(path); + } + + public void putModuleReferenceDetails(String path, ModuleReference ref) { + putObjectReferenceDetails(path, ref); + createObject(path + ".ClassLoader"); + } + + public void putStringReference(String ppath, StringReference ref) { + String path = createObject(ref, ref.toString(), ppath); + putStringReferenceDetails(path, ref); + insertObject(path); + } + + public void putStringReferenceDetails(String path, StringReference ref) { + putObjectReferenceDetails(path, ref); + setValue(path, "Value", ref.value()); + } + + public void putThreadGroupContainer(String refpath, List refs) { + String ppath = refpath + ".ThreadGroups"; + Set keys = new HashSet<>(); + for (ThreadGroupReference subref : refs) { + keys.add(manager.key(subref.name())); + putThreadGroupReference(ppath, subref); + } + retainKeys(ppath, keys); + } + + public void putThreadGroupReference(String ppath, ThreadGroupReference ref) { + String path = createObject(ref, ref.name(), ppath); + putThreadGroupReferenceDetails(path, ref); + insertObject(path); + } + + public void putThreadGroupReferenceDetails(String path, ThreadGroupReference ref) { + putObjectReferenceDetails(path, ref); + if (ref.parent() != null) { + createObject(path + ".Parent"); + } + createObject(path + ".ThreadGroups"); + createObject(path + ".Threads"); + } + + public void putThreadContainer(String refpath, List refs, boolean asLink) { + String ppath = refpath + ".Threads"; + Set keys = new HashSet<>(); + for (ThreadReference subref : refs) { + keys.add(manager.key(subref.name())); + if (asLink) { + createLink(ppath, subref.name(), subref); + } + else { + putThreadReference(ppath, subref); + } + } + retainKeys(ppath, keys); + } + + public void putThreadReference(String ppath, ThreadReference ref) { + String path = createObject(ref, ref.name(), ppath); + putThreadReferenceDetails(path, ref); + insertObject(path); + } + + public void putThreadReferenceDetails(String path, ThreadReference ref) { + putObjectReferenceDetails(path, ref); + createObject(path + ".Stack"); + String rpath = createObject(path + ".Relations"); + createObject(rpath + ".CurrentContendedMonitor"); + createObject(rpath + ".OwnedMonitors"); + createObject(rpath + ".OwnedMonitorsAndFrames"); + createObject(rpath + ".ThreadGroup"); + putThreadAttributes(ref, path); + } + + void putThreadAttributes(ThreadReference thread, String ppath) { + String path = createObject(ppath + ".Attributes"); + setValue(path, "Status", thread.status()); + setValue(path, "isAtBreakpoint", thread.isAtBreakpoint()); + setValue(path, "isCollected", thread.isCollected()); + setValue(path, "isSuspended", thread.isSuspended()); + setValue(path, "isVirtual", thread.isVirtual()); + try { + setValue(path, "entryCount", thread.entryCount()); + } + catch (IncompatibleThreadStateException e) { + // Ignore + } + try { + setValue(path, "frameCount", thread.frameCount()); + } + catch (IncompatibleThreadStateException e) { + // Ignore + } + setValue(path, "suspendCount", thread.suspendCount()); + } + + public void putMonitorInfoContainer(String path, List info) { + for (MonitorInfo f : info) { + createObject(f, f.toString(), path); + } + } + + public void putMonitorInfoDetails(String path, MonitorInfo info) { + setValue(path, "StackDepth", info.stackDepth()); + createObject(path + ".Monitor"); + createObject(path + ".Thread"); + } + + // TYPE COMPONENTS + + public void putFieldContainer(String path, ReferenceType reftype) { + boolean scope = manager.getScope(reftype); + List fields = scope ? reftype.allFields() : reftype.fields(); + Set keys = new HashSet<>(); + for (Field f : fields) { + Value value = null; + try { + value = reftype.getValue(f); + if (value != null) { + keys.add(manager.key(value.toString())); + } + } + catch (IllegalArgumentException iae) { + // IGNORE + } + putField(path, f, value); + } + retainKeys(path, keys); + } + + public void putVariableContainer(String path, ObjectReference ref) { + boolean scope = manager.getScope(ref); + List fields = scope ? ref.referenceType().allFields() : ref.referenceType().fields(); + Set keys = new HashSet<>(); + for (Field f : fields) { + Value value = null; + try { + value = ref.getValue(f); + keys.add(manager.key(value.toString())); + } + catch (IllegalArgumentException iae) { + // IGNORE + } + putField(path, f, value); + } + retainKeys(path, keys); + } + + public void putField(String ppath, Field f, Value value) { + String path = createObject(f, f.name(), ppath); + putFieldDetails(path, f); + if (value != null) { + putValue(path, "Value", value); + setValue(path, "_display", f.name() + " (" + f.typeName() + ") : " + value); + } + else { + setValue(path, "_display", f.name() + " (" + f.typeName() + ")"); + } + insertObject(path); + } + + public void putFieldDetails(String path, Field f) { + setValue(path, TraceJdiManager.MODULE_NAME_ATTRIBUTE_NAME, f.declaringType().name()); + if (f.genericSignature() != null) { + setValue(path, "GenericSignature", f.genericSignature()); + } + putFieldAttributes(path, f); + try { + putType(path, "Type", f.type()); + } + catch (ClassNotLoadedException e) { + // IGNORE + } + } + + private void putFieldAttributes(String ppath, Field f) { + String path = createObject(ppath + ".Attributes"); + setValue(path, "Modifiers", Integer.toHexString(f.modifiers())); + setValue(path, "Signature", f.signature()); + setValue(path, "isEnumConstant", f.isEnumConstant()); + setValue(path, "isFinal", f.isFinal()); + setValue(path, "isPackagePrivate", f.isPackagePrivate()); + setValue(path, "isPrivate", f.isPrivate()); + setValue(path, "isProtected", f.isProtected()); + setValue(path, "isPublic", f.isPublic()); + setValue(path, "isStatic", f.isStatic()); + setValue(path, "isSynthetic", f.isSynthetic()); + setValue(path, "isTransient", f.isTransient()); + setValue(path, "isVolatile", f.isVolatile()); + } + + public void putMethodContainer(String path, ReferenceType reftype) { + boolean scope = manager.getScope(reftype); + List methods = scope ? reftype.allMethods() : reftype.methods(); + Set keys = new HashSet<>(); + for (Method m : methods) { + keys.add(manager.key(m.name())); + putMethod(path, m); + } + retainKeys(path, keys); + } + + public void putMethod(String ppath, Method m) { + String path = createObject(m, m.name(), ppath); + putMethodDetails(path, m, true); + insertObject(path); + } + + public void putMethodDetails(String path, Method m, boolean partial) { + ReferenceType declaringType = m.declaringType(); + setValue(path, TraceJdiManager.MODULE_NAME_ATTRIBUTE_NAME, declaringType.name()); + createLink(m, "DeclaringType", declaringType); + if (!partial) { + createObject(path + ".Arguments"); + if (m.genericSignature() != null) { + setValue(path, "GenericSignature", m.genericSignature()); + } + createObject(path + ".Locations"); + setValue(path, "Modifiers", m.modifiers()); + setValue(path, "ReturnType", m.returnTypeName()); + setValue(path, "Signature", m.signature()); + createObject(path + ".Variables"); + putMethodAttributes(path, m); + } + if (m.location() != null) { + AddressRange range = manager.getAddressRange(m); + if (!range.equals(manager.defaultRange)) { + setValue(path, "Range", range); + } + } + String bytes = ""; + for (byte b : m.bytecodes()) { + bytes += Integer.toHexString(b & 0xff); + } + setValue(path, "ByteCodes", bytes); + } + + private void putMethodAttributes(String ppath, Method m) { + String path = createObject(ppath + ".Attributes"); + setValue(path, "isAbstract", m.isAbstract()); + setValue(path, "isBridge", m.isBridge()); + setValue(path, "isConstructor", m.isConstructor()); + setValue(path, "isDefault", m.isDefault()); + setValue(path, "isFinal", m.isFinal()); + setValue(path, "isNative", m.isNative()); + setValue(path, "isObsolete", m.isObsolete()); + setValue(path, "isPackagePrivate", m.isPackagePrivate()); + setValue(path, "isPrivate", m.isPrivate()); + setValue(path, "isProtected", m.isProtected()); + setValue(path, "isPublic", m.isPublic()); + } + + // OTHER OBJECTS // + + public void putVMs() { + try { + Set keys = new HashSet<>(); + for (Entry entry : jdi.listVMs().get().entrySet()) { + VirtualMachine vm = entry.getValue(); + keys.add(manager.key(vm.name())); + putVM("VMs", vm); + } + retainKeys("VMs", keys); + } + catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + + public void putVM(String ppath, VirtualMachine vm) { + String path = createObject(vm, vm.name(), ppath); + putVMDetails(path, vm); + insertObject(path); + } + + public void putVMDetails(String path, VirtualMachine vm) { + createObject(path + ".Classes"); + createObject(path + ".Memory"); + createObject(path + ".ThreadGroups"); + createObject(path + ".Threads"); + Event currentEvent = jdi.getCurrentEvent(); + String shortName = vm.name().substring(0, vm.name().indexOf(" ")); + String display = currentEvent == null ? shortName : shortName + " [" + currentEvent + "]"; + setValue(path, TraceJdiManager.DISPLAY_ATTRIBUTE_NAME, display); + setValue(path, TraceJdiManager.ARCH_ATTRIBUTE_NAME, vm.name()); + setValue(path, TraceJdiManager.DEBUGGER_ATTRIBUTE_NAME, vm.description()); + setValue(path, TraceJdiManager.OS_ATTRIBUTE_NAME, vm.version()); + } + + public void putProcesses() { + Map vms; + try { + vms = jdi.listVMs().get(); + } + catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + return; + } + for (Entry entry : vms.entrySet()) { + Set keys = new HashSet<>(); + VirtualMachine vm = entry.getValue(); + String path = getPath(vm); + if (path != null) { + String ppath = path + ".Processes"; + Process proc = vm.process(); + if (proc != null) { + String key = Long.toString(proc.pid()); + keys.add(manager.key(key)); + putProcess(ppath, proc); + } + retainKeys(ppath, keys); + } + } + } + + public void putProcess(String ppath, Process proc) { + String path = createObject(proc, Long.toString(proc.pid()), ppath); + putProcessDetails(path, proc); + insertObject(path); + } + + public void putProcessDetails(String path, Process proc) { + Info info = proc.info(); + Optional optional = info.command(); + if (optional.isPresent()) { + setValue(path, "Executable", optional.get()); + } + optional = info.commandLine(); + if (optional.isPresent()) { + setValue(path, "CommandLine", optional.get()); + } + setValue(path, "Alive", proc.isAlive()); + } + + public void putFrames() { + ThreadReference thread = manager.getJdi().getCurrentThread(); + String ppath = createObject(getPath(thread) + ".Stack"); + Set keys = new HashSet<>(); + try { + int frameCount = thread.frameCount(); + for (int i = 0; i < frameCount; i++) { + StackFrame frame = thread.frame(i); + String key = Integer.toString(i); + keys.add(manager.key(key)); + putFrame(ppath, frame, key); + } + } + catch (IncompatibleThreadStateException e) { + // IGNORE + } + retainKeys(ppath, keys); + } + + private void putFrame(String ppath, StackFrame frame, String key) { + String path = createObject(frame, key, ppath); + putFrameDetails(path, frame, key); + insertObject(path); + } + + private void putFrameDetails(String path, StackFrame frame, String key) { + Location location = frame.location(); + setValue(path, "_display", "[" + key + "] " + location + ":" + location.method().name() + + ":" + location.codeIndex()); + putLocation(path, "Location", location); + Address addr = manager.getAddressFromLocation(location); + setValue(path, "PC", addr); + + String rpath = createObject(path + ".Registers"); + putRegisters(frame, rpath); + createObject(path + ".Variables"); + try { + createObject(frame.thisObject(), "This", path); + } + catch (Exception e) { + // Ignore + } + } + + public void putLocationContainer(String path, Method m) { + try { + for (Location loc : m.allLineLocations()) { + createObject(loc, loc.toString(), path); + } + } + catch (AbsentInformationException e) { + // Ignore + } + } + + public void putLocationContainer(String path, ReferenceType ref) { + try { + for (Location loc : ref.allLineLocations()) { + createObject(loc, loc.toString(), path); + } + } + catch (AbsentInformationException e) { + // Ignore + } + } + + public void putLocation(String ppath, String key, Location location) { + String path = createObject(location, key, ppath); + putLocationDetails(path, location); + insertObject(path); + } + + public void putLocationDetails(String path, Location location) { + Address addr = manager.getAddressFromLocation(location); + if (isLoaded(location)) { + setValue(path, "_display", manager.key(location.toString()) + ": " + addr); + setValue(path, "Addr", addr); + } + setValue(path, "Index", location.codeIndex()); + setValue(path, "Line#", location.lineNumber()); + try { + setValue(path, "Name", location.sourceName()); + } + catch (AbsentInformationException e) { + // IGNORE + } + try { + setValue(path, "Path", location.sourcePath()); + } + catch (AbsentInformationException e) { + // IGNORE + } + Method method = location.method(); + createLink(location, "Method", method); + createLink(location, "DeclaringType", location.declaringType()); + createLink(location, "ModuleRef", location.declaringType().module()); + //createObject(method, method.name(), path+".Method"); + //putMethodDetails(path, method); + } + + private boolean isLoaded(Location location) { + AddressRange range = manager.getAddressRange(location.method()); + return !range.equals(manager.defaultRange); + } + + public void putLocalVariableContainer(String path, Map variables) { + for (LocalVariable lv : variables.keySet()) { + putLocalVariable(path, lv, variables.get(lv)); + } + } + + public void putLocalVariableContainer(String path, List variables) { + for (LocalVariable lv : variables) { + putLocalVariable(path, lv, null); + } + } + + public void putLocalVariable(String ppath, LocalVariable lv, Value value) { + String path = createObject(lv, lv.name(), ppath); + putLocalVariableDetails(path, lv); + if (value != null) { + putValue(path, "Value", value); + setValue(path, "_display", lv.name() + ": " + value); + } + insertObject(path); + } + + public void putLocalVariableDetails(String path, LocalVariable lv) { + try { + putType(path, "Type", lv.type()); + } + catch (ClassNotLoadedException e) { + // IGNORE + } + putLocalVariableAttributes(path, lv); + } + + private void putLocalVariableAttributes(String ppath, LocalVariable lv) { + String path = createObject(ppath + ".Attributes"); + setValue(path, "isArgument", lv.isArgument()); + if (lv.genericSignature() != null) { + setValue(path, "GenericSignature", lv.genericSignature()); + } + setValue(path, "Signature", lv.signature()); + } + + public void putMethodTypeContainer(String ppath, Method m) { + try { + for (Type type : m.argumentTypes()) { + createObject(type, type.name(), ppath); + } + } + catch (ClassNotLoadedException e) { + createObject(ppath + "Class Not Loaded"); + } + } + + public void putBreakpoints() { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + EventRequestManager requestManager = vm.eventRequestManager(); + String ppath = getPath(vm) + ".Breakpoints"; + createObject(ppath); + Set keys = new HashSet<>(); + + List brkReqs = requestManager.breakpointRequests(); + for (BreakpointRequest req : brkReqs) { + String key = manager.key(req.toString()); + keys.add(key); + putReqBreakpoint(ppath, req, key); + } + + List watchReqs = requestManager.accessWatchpointRequests(); + for (AccessWatchpointRequest req : watchReqs) { + String key = manager.key(req.toString()); + keys.add(key); + putReqAccessWatchpoint(ppath, req, key); + } + + List modReqs = + requestManager.modificationWatchpointRequests(); + for (ModificationWatchpointRequest req : modReqs) { + String key = manager.key(req.toString()); + keys.add(key); + putReqModificationWatchpoint(ppath, req, key); + } + + retainKeys(ppath, keys); + } + + public void putEvents() { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + EventRequestManager requestManager = vm.eventRequestManager(); + String ppath = getPath(vm) + ".Events"; + createObject(ppath); + Set keys = new HashSet<>(); + + List deathReqs = requestManager.vmDeathRequests(); + for (VMDeathRequest req : deathReqs) { + keys.add(manager.key(req.toString())); + putReqVMDeath(ppath, req, req.toString()); + } + + List threadStartReqs = requestManager.threadStartRequests(); + for (ThreadStartRequest req : threadStartReqs) { + keys.add(manager.key(req.toString())); + putReqThreadStarted(ppath, req, req.toString()); + } + + List threadDeathReqs = requestManager.threadDeathRequests(); + for (ThreadDeathRequest req : threadDeathReqs) { + keys.add(manager.key(req.toString())); + putReqThreadExited(ppath, req, req.toString()); + } + + List excReqs = requestManager.exceptionRequests(); + for (ExceptionRequest req : excReqs) { + keys.add(manager.key(req.toString())); + putReqException(ppath, req, req.toString()); + } + + List loadReqs = requestManager.classPrepareRequests(); + for (ClassPrepareRequest req : loadReqs) { + keys.add(manager.key(req.toString())); + putReqClassLoad(ppath, req, req.toString()); + } + + List unloadReqs = requestManager.classUnloadRequests(); + for (ClassUnloadRequest req : unloadReqs) { + keys.add(manager.key(req.toString())); + putReqClassUnload(ppath, req, req.toString()); + } + + List entryReqs = requestManager.methodEntryRequests(); + for (MethodEntryRequest req : entryReqs) { + keys.add(manager.key(req.toString())); + putReqMethodEntry(ppath, req, req.toString()); + } + + List exitReqs = requestManager.methodExitRequests(); + for (MethodExitRequest req : exitReqs) { + keys.add(manager.key(req.toString())); + putReqMethodExit(ppath, req, req.toString()); + } + + List stepReqs = requestManager.stepRequests(); + for (StepRequest req : stepReqs) { + keys.add(manager.key(req.toString())); + putReqStep(ppath, req, req.toString()); + } + + List monEnterReqs = + requestManager.monitorContendedEnterRequests(); + for (MonitorContendedEnterRequest req : monEnterReqs) { + keys.add(manager.key(req.toString())); + putReqMonContendedEnter(ppath, req, req.toString()); + } + + List monEnteredReqs = + requestManager.monitorContendedEnteredRequests(); + for (MonitorContendedEnteredRequest req : monEnteredReqs) { + keys.add(manager.key(req.toString())); + putReqMonContendedEntered(ppath, req, req.toString()); + } + + List monWaitReqs = requestManager.monitorWaitRequests(); + for (MonitorWaitRequest req : monWaitReqs) { + keys.add(manager.key(req.toString())); + putReqMonWait(ppath, req, req.toString()); + } + + List monWaitedReqs = requestManager.monitorWaitedRequests(); + for (MonitorWaitedRequest req : monWaitedReqs) { + keys.add(manager.key(req.toString())); + putReqMonWaited(ppath, req, req.toString()); + } + + retainKeys(ppath, keys); + } + + // REQUESTS // + + private void putReqVMDeath(String ppath, VMDeathRequest req, String key) { + String path = createObject(req, key, ppath); + putReqVMDeathDetails(path, req, key); + insertObject(path); + } + + private void putReqVMDeathDetails(String path, VMDeathRequest req, String key) { + putFilterDetails(path, req); + } + + private void putReqThreadStarted(String ppath, ThreadStartRequest req, String key) { + String path = createObject(req, key, ppath); + putReqThreadStartedDetails(path, req, key); + insertObject(path); + } + + private void putReqThreadStartedDetails(String path, ThreadStartRequest req, String key) { + putFilterDetails(path, req); + } + + private void putReqThreadExited(String ppath, ThreadDeathRequest req, String key) { + String path = createObject(req, key, ppath); + putReqThreadExitedDetails(path, req, key); + insertObject(path); + } + + private void putReqThreadExitedDetails(String path, ThreadDeathRequest req, String key) { + putFilterDetails(path, req); + } + + private void putReqBreakpoint(String ppath, BreakpointRequest req, String key) { + String path = createObject(req, key, ppath); + putReqBreakpointDetails(path, req, key); + insertObject(path); + } + + private void putReqBreakpointDetails(String path, BreakpointRequest req, String key) { + Location location = req.location(); + setValue(path, "_display", "[" + key + "] " + location + ":" + location.method().name() + + ":" + location.codeIndex()); + Address addr = manager.getAddressFromLocation(location); + AddressRangeImpl range = new AddressRangeImpl(addr, addr); + setValue(path, "Range", range); + createObject(location, location.toString(), path + ".Location"); + setValue(path, "Enabled", req.isEnabled()); + putFilterDetails(path, req); + } + + private void putReqAccessWatchpoint(String ppath, AccessWatchpointRequest req, String key) { + String path = createObject(req, key, ppath); + putReqAccessWatchpointDetails(path, req, key); + insertObject(path); + } + + private void putReqAccessWatchpointDetails(String path, AccessWatchpointRequest req, + String key) { + Field field = req.field(); + setValue(path, "_display", "[" + key + "] " + field + ":" + field.declaringType()); + // NB: This isn't correct, but we need a range (any range) + AddressRange range = + manager.getPoolAddressRange(field.declaringType(), getSize(field.declaringType())); + setValue(path, "Range", range); + createObject(field, field.toString(), path + ".Field"); + setValue(path, "Enabled", req.isEnabled()); + putFilterDetails(path, req); + } + + private void putReqModificationWatchpoint(String ppath, ModificationWatchpointRequest req, + String key) { + String path = createObject(req, key, ppath); + putReqModificationWatchpointDetails(path, req, key); + insertObject(path); + } + + private void putReqModificationWatchpointDetails(String path, ModificationWatchpointRequest req, + String key) { + Field field = req.field(); + setValue(path, "_display", "[" + key + "] " + field + ":" + field.declaringType()); + // NB: This isn't correct, but we need a range (any range) + AddressRange range = + manager.getPoolAddressRange(field.declaringType(), getSize(field.declaringType())); + setValue(path, "Range", range); + createObject(field, field.toString(), path + ".Field"); + setValue(path, "Enabled", req.isEnabled()); + putFilterDetails(path, req); + } + + private void putReqException(String ppath, ExceptionRequest req, String key) { + String path = createObject(req, key, ppath); + putReqExceptionDetails(path, req, key); + insertObject(path); + } + + private void putReqExceptionDetails(String path, ExceptionRequest req, String key) { + setValue(path, "Enabled", req.isEnabled()); + } + + private void putReqClassLoad(String ppath, ClassPrepareRequest req, String key) { + String path = createObject(req, key, ppath); + putReqClassLoadDetails(path, req, key); + insertObject(path); + } + + private void putReqClassLoadDetails(String path, ClassPrepareRequest req, String key) { + setValue(path, "Enabled", req.isEnabled()); + putFilterDetails(path, req); + } + + private void putReqClassUnload(String ppath, ClassUnloadRequest req, String key) { + String path = createObject(req, key, ppath); + putReqClassUnloadDetails(path, req, key); + insertObject(path); + } + + private void putReqClassUnloadDetails(String path, ClassUnloadRequest req, String key) { + setValue(path, "Enabled", req.isEnabled()); + putFilterDetails(path, req); + } + + private void putReqMethodEntry(String ppath, MethodEntryRequest req, String key) { + String path = createObject(req, key, ppath); + putReqMethodEntryDetails(path, req, key); + insertObject(path); + } + + private void putReqMethodEntryDetails(String path, MethodEntryRequest req, String key) { + setValue(path, "Enabled", req.isEnabled()); + putFilterDetails(path, req); + } + + private void putReqMethodExit(String ppath, MethodExitRequest req, String key) { + String path = createObject(req, key, ppath); + putReqMethodExitDetails(path, req, key); + insertObject(path); + } + + private void putReqMethodExitDetails(String path, MethodExitRequest req, String key) { + setValue(path, "Enabled", req.isEnabled()); + putFilterDetails(path, req); + } + + private void putReqStep(String ppath, StepRequest req, String key) { + String path = createObject(req, key, ppath); + putReqStepRequestDetails(path, req, key); + insertObject(path); + } + + private void putReqStepRequestDetails(String path, StepRequest req, String key) { + setValue(path, "Enabled", req.isEnabled()); + putFilterDetails(path, req); + } + + private void putReqMonContendedEnter(String ppath, MonitorContendedEnterRequest req, + String key) { + String path = createObject(req, key, ppath); + putReqMonContendedEnterDetails(path, req, key); + insertObject(path); + } + + private void putReqMonContendedEnterDetails(String path, MonitorContendedEnterRequest req, + String key) { + putFilterDetails(path, req); + } + + private void putReqMonContendedEntered(String ppath, MonitorContendedEnteredRequest req, + String key) { + String path = createObject(req, key, ppath); + putReqMonContendedEnteredDetails(path, req, key); + insertObject(path); + } + + private void putReqMonContendedEnteredDetails(String path, MonitorContendedEnteredRequest req, + String key) { + putFilterDetails(path, req); + } + + private void putReqMonWait(String ppath, MonitorWaitRequest req, String key) { + String path = createObject(req, key, ppath); + putReqMonWaitDetails(path, req, key); + insertObject(path); + } + + private void putReqMonWaitDetails(String path, MonitorWaitRequest req, String key) { + putFilterDetails(path, req); + } + + private void putReqMonWaited(String ppath, MonitorWaitedRequest req, String key) { + String path = createObject(req, key, ppath); + putReqMonWaitedDetails(path, req, key); + insertObject(path); + } + + private void putReqMonWaitedDetails(String path, MonitorWaitedRequest req, String key) { + putFilterDetails(path, req); + } + + private void putFilterDetails(String path, EventRequest req) { + Object property = req.getProperty("Class"); + if (property != null) { + if (property instanceof ReferenceType reftype) { + setValue(path, "Class", reftype.name()); + } + } + property = req.getProperty("Instance"); + if (property != null) { + if (property instanceof ObjectReference ref) { + setValue(path, "Instance", ref.toString()); + } + } + property = req.getProperty("Thread"); + if (property != null) { + if (property instanceof ThreadReference ref) { + setValue(path, "Thread", ref.name()); + } + } + } + + public void ghidraTracePutModules() { + state.requireTrace(); + try (RmiTransaction tx = state.trace.startTx("ghidraTracePutModules", false)) { + putModuleReferenceContainer(); + } + } + + public void ghidraTracePutClasses() { + state.requireTrace(); + try (RmiTransaction tx = state.trace.startTx("ghidraTracePutClasses", false)) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + putReferenceTypeContainer(getPath(vm) + ".Classes", vm.allClasses()); + } + } + + public void ghidraTracePutThreads() { + state.requireTrace(); + try (RmiTransaction tx = state.trace.startTx("ghidraTracePutThreads", false)) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + putThreadContainer(getPath(vm), vm.allThreads(), false); // Do this first + putThreadGroupContainer(getPath(vm), vm.topLevelThreadGroups()); + } + } + + public void ghidraTracePutFrames() { + state.requireTrace(); + try (RmiTransaction tx = state.trace.startTx("ghidraTracePutFrames", false)) { + putFrames(); + } + } + + public void ghidraTracePutAll() { + state.requireTrace(); + try (RmiTransaction tx = state.trace.startTx("ghidraTracePutAll", false)) { + putVMs(); + VirtualMachine vm = manager.getJdi().getCurrentVM(); + putProcesses(); + putThreadContainer(getPath(vm), vm.allThreads(), false); + putThreadGroupContainer(getPath(vm), vm.topLevelThreadGroups()); + putFrames(); + putBreakpoints(); + putEvents(); + putReferenceTypeContainer(getPath(vm) + ".Classes", vm.allClasses()); + } + } + + public void ghidraTraceInstallHooks() { + manager.getHooks().installHooks(); + } + + public void ghidraTraceRemoveHooks() { + manager.getHooks().removeHooks(); + } + + public void ghidraTraceSyncEnable() { + try (RmiTransaction tx = state.trace.startTx("ghidraTraceSyncEnable", false)) { + TraceJdiHooks hooks = manager.getHooks(); + hooks.installHooks(); + hooks.enableCurrentVM(); + } + } + + public void ghidraTraceSyncDisable() { + manager.getHooks().disableCurrentVM(); + } + + public void ghidraTraceSyncSynthStopped() { + manager.getHooks().onStop(null, state.trace); + } + + public void ghidraTraceWaitStopped(int timeout) { + ThreadReference currentThread = manager.getJdi().getCurrentThread(); + if (currentThread == null) { + return; + } + long start = System.currentTimeMillis(); + while (!currentThread.isSuspended()) { + currentThread = manager.getJdi().getCurrentThread(); + try { + Thread.sleep(100); + long elapsed = System.currentTimeMillis() - start; + if (elapsed > timeout) { + throw new RuntimeException("Timed out waiting for thread to stop"); + } + } + catch (InterruptedException e) { + Msg.error(this, "Wait interrupted"); + } + } + } + + private int getSize(ReferenceType reftype) { + byte[] cp = reftype.constantPool(); + int sz = 1; + if (cp != null && cp.length > 0) { + sz = cp.length; + } + return sz; + } + + public void setValue(String path, String key, Object value) { + state.trace.setValue(path, key, value); + } + + String getPath(Object obj) { + return manager.pathForObj(obj); + } + + RmiTraceObject proxyObject(Object obj) { + String path = getPath(obj); + return path == null ? null : RmiTraceObject.fromPath(state.trace, path); + } + + private String createObject(String path) { + state.trace.createObject(path); + return path; + } + + private String createObject(Object obj, String key, String ppath) { + if (obj == null) { + return null; + } + String path = manager.recordPath(obj, ppath, key); + state.trace.createObject(path); + return path; + } + + private String insertObject(String path) { + state.trace.insertObject(path); + return path; + } + + private void retainKeys(String ppath, Set keys) { + state.trace.retainValues(ppath, keys, ValueKinds.VK_ELEMENTS); + } + + public void createLink(Object parent, String label, Object child) { + String ppath = parent instanceof String ? (String) parent : getPath(parent); + RmiTraceObject proxy = proxyObject(child); + if (proxy != null) { + setValue(ppath, label, proxy); + } + else { + // TODO: is this really what we want to do? + String key = child.toString(); + if (child instanceof Method m) { + key = m.name(); + } + createObject(child, key, ppath + "." + label); + } + } + + public String getVmPath(VirtualMachine vm) { + return manager.recordPath(vm, "VMs", vm.name()); + } + + public String getParentPath(String path) { + String ppath = path.substring(0, path.lastIndexOf(".")); + if (ppath.endsWith(".Relations")) { + return getParentPath(ppath); + } + return ppath; + } + + public boolean setStatus(Object obj, boolean stopped) { + String path = getPath(obj); + if (obj == null || path == null) { + return stopped; + } + boolean suspended = stopped; + String name = obj.toString(); + if (obj instanceof ThreadReference thread) { + suspended = thread.isSuspended(); + name = thread.name(); + } + if (obj instanceof VirtualMachine vm) { + Event currentEvent = jdi.getCurrentEvent(); + String shortName = vm.name().substring(0, vm.name().indexOf(" ")); + name = currentEvent == null ? shortName : shortName + " [" + currentEvent + "]"; + } + setValue(path, TraceJdiManager.ACCESSIBLE_ATTRIBUTE_NAME, suspended); + String annotation = suspended ? "(S)" : "(R)"; + setValue(path, TraceJdiManager.DISPLAY_ATTRIBUTE_NAME, name + " " + annotation); + String tstate = suspended ? "STOPPED" : "RUNNING"; + setValue(path, TraceJdiManager.STATE_ATTRIBUTE_NAME, tstate); + stopped |= suspended; + return stopped; + } +} diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiHooks.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiHooks.java new file mode 100644 index 0000000000..3375788ab1 --- /dev/null +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiHooks.java @@ -0,0 +1,486 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.dbg.jdi.rmi.jpda; + +import java.util.*; + +import com.sun.jdi.*; +import com.sun.jdi.event.*; + +import ghidra.app.plugin.core.debug.client.tracermi.RmiTrace; +import ghidra.app.plugin.core.debug.client.tracermi.RmiTransaction; +import ghidra.dbg.jdi.manager.*; +import ghidra.dbg.jdi.manager.impl.DebugStatus; +import ghidra.dbg.jdi.manager.impl.JdiManagerImpl; + +class HookState { + + private TraceJdiCommands cmds; + private Object batch; + + public HookState(TraceJdiCommands cmds) { + this.cmds = cmds; + this.batch = null; + } + + public void ensureBatch() { + if (batch == null) { + batch = cmds.state.client.startBatch(); + } + } + + public void endBatch() { + if (batch == null) { + return; + } + batch = null; + cmds.state.client.endBatch(); + } + +} + +class VmState { + + private TraceJdiManager manager; + private TraceJdiCommands cmds; + private boolean firstPass; + boolean classes; + boolean modules; + boolean regions; + boolean threads; + boolean breaks; + boolean events; + Set visited; + + public VmState(TraceJdiManager manager) { + this.manager = manager; + this.cmds = manager.getCommands(); + this.firstPass = true; + this.classes = false; + this.modules = false; + this.regions = false; + this.threads = false; + this.breaks = false; + this.events = false; + this.visited = new HashSet<>(); + } + + public void recordState(String description) { + boolean first = this.firstPass; + if (description != null) { + cmds.state.trace.snapshot(description, "", null); + } + this.firstPass = false; + if (first) { + cmds.putProcesses(); + } + + VirtualMachine vm = manager.getJdi().getCurrentVM(); + cmds.putVM("VMs", vm); + setState(vm); + + if (first || threads) { + String path = cmds.getPath(vm); + cmds.putThreadContainer(path, vm.allThreads(), false); + cmds.putThreadGroupContainer(path, vm.topLevelThreadGroups()); + threads = false; + } + + cmds.putCurrentLocation(); + ThreadReference thread = manager.getJdi().getCurrentThread(); + if (thread != null) { + cmds.createLink(vm, "_event_thread", thread); + if (first || !visited.contains(thread)) { + cmds.putFrames(); + visited.add(thread); + } + StackFrame frame = manager.getJdi().getCurrentFrame(); + if (frame != null) { + if (first || !visited.contains(frame)) { + cmds.putReg(frame); + visited.add(frame); + } + } + } + + if (classes) { + classes = false; + cmds.putReferenceTypeContainer(cmds.getPath(vm) + ".Classes", vm.allClasses()); + } + if (first || modules) { + modules = false; + cmds.putModuleReferenceContainer(); + } + if (first || breaks) { + breaks = false; + cmds.putBreakpoints(); + } + if (first || events) { + events = false; + cmds.putEvents(); + } + } + + public void setState(VirtualMachine vm) { + boolean stopped = false; + for (ThreadReference thread : vm.allThreads()) { + stopped |= cmds.setStatus(thread, stopped); + } + cmds.setStatus(vm, stopped); + Process process = vm.process(); + if (process != null) { + cmds.setStatus(process, stopped); + } + if (stopped) { + breaks = true; + events = true; + } + } + + public void recordStateContinued(VirtualMachine vm) { + Process proc = vm.process(); + String path = cmds.getPath(proc); + if (path != null) { + cmds.setValue(path, "Alive", proc.isAlive()); + } + setState(vm); + } + + public void recordStateExited(VirtualMachine eventVM, String description) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + String path = cmds.getPath(vm); + int exitCode = -1; + try { + Process process = eventVM.process(); + if (process != null) { + exitCode = process.exitValue(); + String procpath = cmds.getPath(vm.process()); + cmds.setValue(procpath, "ExitCode", exitCode); + cmds.setValue(procpath, TraceJdiManager.STATE_ATTRIBUTE_NAME, "TERMINATED"); + } + } + catch (IllegalThreadStateException e) { + // IGNORE + } + if (description != null) { + cmds.state.trace.snapshot(description, "", null); + } + cmds.setValue(path, "ExitCode", exitCode); + cmds.setValue(path, TraceJdiManager.STATE_ATTRIBUTE_NAME, "TERMINATED"); + } + +} + +public class TraceJdiHooks implements JdiEventsListenerAdapter { + + private TraceJdiManager manager; + private TraceJdiCommands cmds; + private HookState hookState; + private Map vmStates = new HashMap<>(); + + public TraceJdiHooks(TraceJdiManager manager) { + this.manager = manager; + this.cmds = manager.getCommands(); + } + + private void setCommands(TraceJdiCommands commands) { + this.cmds = commands; + hookState = new HookState(commands); + } + + @Override + public DebugStatus vmStarted(VMStartEvent event, JdiCause cause) { + setCommands(manager.getCommands()); + hookState.ensureBatch(); + RmiTrace trace = cmds.state.trace; + JdiManagerImpl jdi = manager.getJdi(); + VirtualMachine vm = event == null ? jdi.getCurrentVM() : event.virtualMachine(); + try (RmiTransaction tx = trace.openTx("New VM " + vm.description())) { + jdi.setCurrentVM(vm); + jdi.addVM(vm); + cmds.putVMs(); + enableCurrentVM(); + } + hookState.endBatch(); + return DebugStatus.NO_CHANGE; + } + + @Override + public DebugStatus vmDied(VMDeathEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return manager.getReturnStatus("VMDeathEvent"); + } + + @Override + public DebugStatus vmDisconnected(VMDisconnectEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + VirtualMachine eventVM = evt.virtualMachine(); + VmState state = vmStates.get(eventVM); + try (RmiTransaction tx = trace.openTx("VM disconnected: " + eventVM.description())) { + state.recordStateExited(eventVM, "VM disconnected"); + } + disableCurrentVM(); + return DebugStatus.NO_CHANGE; + } + + @Override + public DebugStatus threadStarted(ThreadStartEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return manager.getReturnStatus("ThreadStartEvent"); + } + + @Override + public DebugStatus threadExited(ThreadDeathEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return manager.getReturnStatus("ThreadDeathEvent"); + } + + @Override + public DebugStatus stepComplete(StepEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return DebugStatus.BREAK; + } + + @Override + public DebugStatus breakpointHit(BreakpointEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return DebugStatus.BREAK; + } + + @Override + public DebugStatus accessWatchpointHit(AccessWatchpointEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return DebugStatus.BREAK; + } + + @Override + public DebugStatus watchpointHit(WatchpointEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return DebugStatus.BREAK; + } + + @Override + public DebugStatus watchpointModified(ModificationWatchpointEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return DebugStatus.BREAK; + } + + @Override + public DebugStatus exceptionHit(ExceptionEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return manager.getReturnStatus("ExceptionEvent"); + } + + @Override + public DebugStatus methodEntry(MethodEntryEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return manager.getReturnStatus("MethodEntryEvent"); + } + + @Override + public DebugStatus methodExit(MethodExitEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return manager.getReturnStatus("MethodExitEvent"); + } + + @Override + public DebugStatus classPrepare(ClassPrepareEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return manager.getReturnStatus("ClassPrepareEvent"); + } + + @Override + public DebugStatus classUnload(ClassUnloadEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return manager.getReturnStatus("ClassUnloadEvent"); + } + + @Override + public DebugStatus monitorContendedEnter(MonitorContendedEnterEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return manager.getReturnStatus("MonitorContendedEnterEvent"); + } + + @Override + public DebugStatus monitorContendedEntered(MonitorContendedEnteredEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return manager.getReturnStatus("MonitorContendedEnteredEvent"); + } + + @Override + public DebugStatus monitorWait(MonitorWaitEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return manager.getReturnStatus("MonitorWaitEvent"); + } + + @Override + public DebugStatus monitorWaited(MonitorWaitedEvent evt, JdiCause cause) { + RmiTrace trace = cmds.state.trace; + if (trace == null) { + return DebugStatus.NO_CHANGE; + } + onStop(evt, trace); + return manager.getReturnStatus("MonitorWaitedEvent"); + } + + @Override + public DebugStatus threadStateChanged(ThreadReference thread, Integer state, JdiCause cause, + JdiReason reason) { + return DebugStatus.NO_CHANGE; + } + + void onStop(Event evt, RmiTrace trace) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + if (evt != null) { + setCurrent(evt); + vm = evt.virtualMachine(); + } + VmState state = vmStates.get(vm); + state.visited.clear(); + hookState.ensureBatch(); + try (RmiTransaction tx = trace.openTx("Stopped")) { + state.recordState("Stopped"); + cmds.activate(null); + } + hookState.endBatch(); + } + + private void setCurrent(Event event) { + VirtualMachine eventVM = event.virtualMachine(); + JdiManagerImpl jdi = manager.getJdi(); + jdi.setCurrentEvent(event); + jdi.setCurrentVM(eventVM); + if (event instanceof LocatableEvent locEvt) { + jdi.setCurrentLocation(locEvt.location()); + ThreadReference eventThread = locEvt.thread(); + jdi.setCurrentThread(eventThread); + try { + jdi.setCurrentFrame(eventThread.frame(0)); + } + catch (IncompatibleThreadStateException e) { + // IGNORE + } + } + } + + void onContinue() { + VirtualMachine currentVM = manager.getJdi().getCurrentVM(); + VmState state = vmStates.get(currentVM); + hookState.ensureBatch(); + try (RmiTransaction tx = cmds.state.trace.openTx("Continue")) { + state.recordStateContinued(currentVM); + cmds.activate(null); + } + hookState.endBatch(); + } + + public void installHooks() { + manager.getJdi().addEventsListener(null, this); + } + + public void removeHooks() { + manager.getJdi().removeEventsListener(null, this); + } + + public void enableCurrentVM() { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + VmState state = new VmState(manager); + vmStates.put(vm, state); + state.recordState("VM started"); + cmds.activate(null); + } + + public void disableCurrentVM() { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + VmState state = vmStates.get(vm); + state.visited.clear(); + vmStates.remove(vm); + vm.dispose(); + } + + public void setState(VirtualMachine vm) { + VmState state = vmStates.get(vm); + state.setState(vm); + } + +} diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiManager.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiManager.java new file mode 100644 index 0000000000..eec55960f5 --- /dev/null +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiManager.java @@ -0,0 +1,368 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.dbg.jdi.rmi.jpda; + +import java.util.HashMap; +import java.util.Map; + +import com.sun.jdi.*; + +import ghidra.app.plugin.core.debug.client.tracermi.*; +import ghidra.dbg.jdi.manager.impl.DebugStatus; +import ghidra.dbg.jdi.manager.impl.JdiManagerImpl; +import ghidra.dbg.target.schema.EnumerableTargetObjectSchema; +import ghidra.dbg.target.schema.TargetObjectSchema; +import ghidra.program.model.address.*; +import ghidra.util.Msg; + +public class TraceJdiManager { + + private static final int STATIC_METHOD_SEPARATION = 3; + public static final long BLOCK_SIZE = 0x1000L; + public static final long DEFAULT_SECTION = 0x0000L; + + public static final String PREFIX_INVISIBLE = "_"; + public static final String DISPLAY_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "display"; + public static final String STATE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "state"; + public static final String MODULE_NAME_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "module_name"; + public static final String ARCH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "arch"; + public static final String DEBUGGER_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "debugger"; + public static final String OS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "os"; + public static final String ENDIAN_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "endian"; + public static final String ACCESSIBLE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "accessible"; + + private JdiManagerImpl manager; + private TraceJdiArch arch; + private TraceJdiHooks hooks; + private TraceJdiMethods methods; + private TraceJdiCommands commands; + + Map objectRegistry = new HashMap<>(); + Map reverseRegistry = new HashMap<>(); + RmiMethodRegistry remoteMethodRegistry = new RmiMethodRegistry(); + Map scopeRegistry = new HashMap<>(); + + protected final AddressSpace ram = new GenericAddressSpace("ram", 64, AddressSpace.TYPE_RAM, 0); + protected Long ramIndex = Long.valueOf(BLOCK_SIZE); + protected final AddressSpace pool = + new GenericAddressSpace("constantPool", 64, AddressSpace.TYPE_RAM, 0); + protected Long poolIndex = Long.valueOf(0x0L); + public AddressRangeImpl defaultRange; + + private Map addressRangeByMethod = new HashMap<>(); + private Map methodsByKey = new HashMap<>(); + private Map addressRangeByClass = new HashMap<>(); + private Map cpAddressRangeByClass = new HashMap<>(); + + private Map returnStatusMap = new HashMap<>(); + TargetObjectSchema rootSchema; + + public TraceJdiManager(JdiManagerImpl manager, Map env) { + this(manager); + commands.ghidraTraceConnect(env.get("GHIDRA_TRACE_RMI_ADDR")); + commands.ghidraTraceStart(env.get("OPT_TARGET_CLASS")); + } + + // NB: Needed for testing + public TraceJdiManager(JdiManagerImpl manager) { + this.manager = manager; + Address start = ram.getAddress(DEFAULT_SECTION); + defaultRange = new AddressRangeImpl(start, start.add(BLOCK_SIZE - 1)); + rootSchema = RmiClient.loadSchema("jdi_schema.xml", "Debugger"); + + arch = new TraceJdiArch(); + commands = new TraceJdiCommands(this); // Must precede methods/hooks + methods = new TraceJdiMethods(this); + hooks = new TraceJdiHooks(this); + hooks.installHooks(); + } + + public JdiManagerImpl getJdi() { + return manager; + } + + public TraceJdiArch getArch() { + return arch; + } + + public TraceJdiCommands getCommands() { + return commands; + } + + public TraceJdiMethods getMethods() { + return methods; + } + + public TraceJdiHooks getHooks() { + return hooks; + } + + public RmiClient getClient() { + return commands.state.client; + } + + public void registerRemoteMethod(TraceJdiMethods methods, java.lang.reflect.Method m, + String name) { + String action = name; + String display = name; + String description = name; + RmiMethodRegistry.TraceMethod annot = m.getAnnotation(RmiMethodRegistry.TraceMethod.class); + if (annot == null) { + return; + } + action = annot.action(); + if (annot.display() != null) { + display = annot.display(); + } + if (annot.description() != null) { + description = annot.description(); + } + int pcount = m.getParameterCount(); + if (pcount < 1) { + return; + } + TargetObjectSchema schema = EnumerableTargetObjectSchema.ANY; + RmiRemoteMethod method = new RmiRemoteMethod(rootSchema.getContext(), name, action, display, + description, schema, methods, m); + remoteMethodRegistry.putMethod(name, method); + } + + public AddressSpace getAddressSpace(String name) { + switch (name) { + case "ram": + return ram; + case "constantPool": + return pool; + default: + return null; + } + } + + public AddressRange putAddressRange(ReferenceType cls, AddressSet set) { + if (set.isEmpty()) { + addressRangeByClass.put(cls, defaultRange); + return defaultRange; + } + AddressRange range = new AddressRangeImpl(set.getMinAddress(), set.getMaxAddress()); + addressRangeByClass.put(cls, range); + return range; + } + + public AddressRange getAddressRange(ReferenceType cls) { + if (cls == null) { + return defaultRange; + } + return addressRangeByClass.get(cls); + } + + public ReferenceType getReferenceTypeForAddress(Address address) { + for (ReferenceType ref : addressRangeByClass.keySet()) { + AddressRange range = addressRangeByClass.get(ref); + if (range.contains(address)) { + return ref; + } + } + return null; + } + + public AddressRange getPoolAddressRange(ReferenceType cls, int sz) { + if (cls == null) { + return defaultRange; + } + AddressRange range = cpAddressRangeByClass.get(cls); + if (range == null) { + registerConstantPool(cls, sz); + range = cpAddressRangeByClass.get(cls); + } + return range; + } + + public void registerConstantPool(ReferenceType declaringType, int sz) { + if (!cpAddressRangeByClass.containsKey(declaringType)) { + if (manager.getCurrentVM().canGetConstantPool()) { + long length = sz == 0 ? 2 : sz; + synchronized (cpAddressRangeByClass) { + Address start = pool.getAddress(poolIndex); + AddressRangeImpl range = + new AddressRangeImpl(start, start.add(length - 1)); + if (!cpAddressRangeByClass.containsKey(declaringType)) { + cpAddressRangeByClass.put(declaringType, range); + poolIndex += length; //bytecodes.length; + } + } + } + } + } + + public ReferenceType getReferenceTypeForPoolAddress(Address address) { + for (ReferenceType ref : cpAddressRangeByClass.keySet()) { + AddressRange range = cpAddressRangeByClass.get(ref); + if (range.contains(address)) { + return ref; + } + } + return null; + } + + public AddressRange getAddressRange(Method method) { + if (method == null) { + return defaultRange; + } + AddressRange range = addressRangeByMethod.get(methodToKey(method)); + if (range == null) { + return defaultRange; + } + return range; + } + + public Method getMethodForAddress(Address address) { + for (String methodName : addressRangeByMethod.keySet()) { + AddressRange range = addressRangeByMethod.get(methodName); + if (range.contains(address)) { + return methodsByKey.get(methodName); + } + } + return null; + } + + public Address getAddressFromLocation(Location location) { + AddressRange addressRange = getAddressRange(location.method()); + if (addressRange == null) { + return getAddressSpace("ram").getAddress(-1L); + } + long codeIndex = location.codeIndex(); + return addressRange.getMinAddress().add(codeIndex < 0 ? 0 : codeIndex); + + } + + public Location getLocation(Address address) { + Method method = getMethodForAddress(address); + long codeIndex = address.subtract(getAddressRange(method).getMinAddress()); + return method.locationOfCodeIndex(codeIndex); + } + + public static String methodToKey(Method method) { + return method.toString(); + } + + public AddressRange registerAddressesForMethod(Method method) { + byte[] bytecodes = method.bytecodes(); + if (bytecodes == null) { + return null; + } + int length = bytecodes.length; + if (length <= 0) { + return null; + } + synchronized (addressRangeByMethod) { + Address start = ram.getAddress(ramIndex); + AddressRangeImpl range = + new AddressRangeImpl(start, start.add(length - 1)); + String key = methodToKey(method); + //System.err.println(Long.toHexString(ramIndex)+":"+key+":"+bytecodes.length+" "+Long.toHexString(bytecodes[0])); + if (!methodsByKey.containsKey(key)) { + methodsByKey.put(key, method); + } + if (!addressRangeByMethod.containsKey(key)) { + addressRangeByMethod.put(key, range); + ramIndex = range.getMaxAddress().getUnsignedOffset() + STATIC_METHOD_SEPARATION; + return range; + } + return addressRangeByMethod.get(key); + } + } + + public String recordPath(Object obj, String path, String key) { + if (path.endsWith("]")) { + path += "." + key; + } + else { + path += key(key); + } + objectRegistry.put(path, obj); + if (!reverseRegistry.containsKey(obj)) { + reverseRegistry.put(obj, path); + } + return path; + } + + public Object objForPath(String path) { + return objectRegistry.get(path); + } + + public String pathForObj(Object obj) { + if (!reverseRegistry.containsKey(obj)) { + if (objectRegistry.containsValue(obj)) { + Msg.error(this, "MISSING path for " + obj); + } + return null; + } + return reverseRegistry.get(obj); + } + + public boolean getScope(Object ctxt) { + Boolean scope = scopeRegistry.get(ctxt); + if (scope == null) { + scope = true; + } + return scope; + } + + public void toggleScope(Object ctxt) { + Boolean scope = scopeRegistry.get(ctxt); + if (scope == null) { + scope = true; + } + scopeRegistry.put(ctxt, !scope); + } + + private String sanitize(String name) { + if (name == null) { + return name; + } + name = name.replace("[", ""); + name = name.replace("]", ""); + return name; + } + + public String key(String key) { + return "[" + sanitize(key) + "]"; + } + + public DebugStatus getReturnStatus(String eventName) { + return returnStatusMap.get(eventName); + } + + public void setReturnStatus(String eventName, String statusString) { + DebugStatus status = DebugStatus.BREAK; + try { + status = DebugStatus.valueOf(statusString.toUpperCase()); + } + catch (IllegalArgumentException e) { + // IGNORE + } + returnStatusMap.put(eventName, status); + } + + public void bumpRamIndex() { + synchronized (addressRangeByMethod) { + if (ramIndex % BLOCK_SIZE != 0) { + ramIndex = (ramIndex / BLOCK_SIZE + 1) * BLOCK_SIZE; + } + } + } + +} diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiMethods.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiMethods.java new file mode 100644 index 0000000000..46771fdb6a --- /dev/null +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/TraceJdiMethods.java @@ -0,0 +1,1604 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.dbg.jdi.rmi.jpda; + +import java.io.IOException; +import java.util.*; + +import com.sun.jdi.*; +import com.sun.jdi.request.*; + +import ghidra.app.plugin.core.debug.client.tracermi.*; +import ghidra.dbg.target.TargetMethod; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRange; +import ghidra.rmi.trace.TraceRmi.MemoryState; +import ghidra.util.Msg; + +public class TraceJdiMethods implements RmiMethods { + + private TraceJdiManager manager; + private TraceJdiCommands cmds; + + public TraceJdiMethods(TraceJdiManager manager) { + this.manager = manager; + this.cmds = manager.getCommands(); + registerMethods(); + } + + public void registerMethods() { + Class cls = this.getClass(); + for (java.lang.reflect.Method m : cls.getMethods()) { + RmiMethodRegistry.TraceMethod annot = + m.getAnnotation(RmiMethodRegistry.TraceMethod.class); + if (annot != null) { + manager.registerRemoteMethod(this, m, m.getName()); + } + } + } + +// public void execute(String cmd) { +// +// } + +// public void refresh_available(Object obj) { +// +// } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh VM", + schema = "VirtualMachine") + public void refresh_vm(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshVM")) { + String path = obj.getPath(); + VirtualMachine vm = (VirtualMachine) getObjectFromPath(path); + cmds.putVMDetails(path, vm); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh process", + schema = "ProcessRef") + public void refresh_process(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshProcess")) { + String path = obj.getPath(); + Process proc = (Process) getObjectFromPath(path); + cmds.putProcessDetails(path, proc); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh thread groups", + schema = "ThreadGroupReferenceContainer") + public void refresh_thread_groups(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshThreadGroups")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + Object parent = getObjectFromPath(ppath); + if (parent instanceof VirtualMachine vm) { + cmds.putThreadGroupContainer(ppath, vm.topLevelThreadGroups()); + } + if (parent instanceof ThreadGroupReference group) { + cmds.putThreadGroupContainer(ppath, group.threadGroups()); + } + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh thread group", + schema = "ThreadGroupReferenceProxy") + public void refresh_thread_group_proxy(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshThreadGroup")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + Object parent = getObjectFromPath(ppath); + if (parent instanceof ThreadGroupReference group) { + cmds.putThreadGroupReference(path, group.parent()); + } + if (parent instanceof ThreadReference ref) { + cmds.putThreadGroupReference(path, ref.threadGroup()); + } + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh thread group", + schema = "ThreadGroupReference") + public void refresh_thread_group(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshThreadGroup")) { + String path = obj.getPath(); + ThreadGroupReference ref = (ThreadGroupReference) getObjectFromPath(path); + cmds.putThreadGroupReferenceDetails(path, ref); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh threads", + schema = "ThreadContainer") + public void refresh_threads(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshThreads")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + VirtualMachine vm = (VirtualMachine) getObjectFromPath(ppath); + cmds.putThreadContainer(ppath, vm.allThreads(), false); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh threads", + schema = "ThreadReferenceContainer") + public void refresh_threadrefs(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshThreads")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + Object parent = getObjectFromPath(ppath); + if (parent instanceof ThreadGroupReference group && path.endsWith(".Threads")) { + cmds.putThreadContainer(ppath, group.threads(), true); + } + if (parent instanceof ObjectReference ref && !path.endsWith(".Threads")) { + try { + cmds.putThreadContainer(ppath, ref.waitingThreads(), true); + } + catch (IncompatibleThreadStateException e) { + // IGNORE + } + } + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh Thread", + schema = "Thread") + public void refresh_thread(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshThread")) { + String path = obj.getPath(); + ThreadReference ref = (ThreadReference) getObjectFromPath(path); + cmds.putThreadReferenceDetails(path, ref); + } + } + + @RmiMethodRegistry.TraceMethod(action = "refresh", display = "Refresh Stack", schema = "Stack") + public void refresh_stack(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshStack")) { + cmds.ghidraTracePutFrames(); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh registers", + schema = "RegisterContainer") + public void refresh_registers(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshRegisters")) { + cmds.ghidraTracePutFrames(); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh modules", + schema = "ModuleReferenceContainer") + public void refresh_modules(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshModules")) { + cmds.putModuleReferenceContainer(); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh module", + schema = "ModuleReference") + public void refresh_module(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshModule")) { + String path = obj.getPath(); + ModuleReference ref = (ModuleReference) getObjectFromPath(path); + cmds.putModuleReferenceDetails(path, ref); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh monitor info", + schema = "MonitorInfoContainer") + public void refresh_monitors(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshMonitorInfo")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + ThreadReference ref = (ThreadReference) getObjectFromPath(ppath); + cmds.putMonitorInfoContainer(path, ref.ownedMonitorsAndFrames()); + } + catch (IncompatibleThreadStateException e) { + // IGNORE + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh monitor info", + schema = "MonitorInfo") + public void refresh_monitor_info(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshMonitorInfo")) { + String path = obj.getPath(); + MonitorInfo mi = (MonitorInfo) getObjectFromPath(path); + cmds.putMonitorInfoDetails(path, mi); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh fields", + schema = "FieldContainer") + public void refresh_fields(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshFields")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + Object parent = getObjectFromPath(ppath); + if (parent instanceof ReferenceType refType) { + cmds.putFieldContainer(path, refType); + } + else if (parent instanceof ObjectReference ref) { + cmds.putVariableContainer(path, ref); + } + } + } + +// @RmiMethodRegistry.method(action = "refresh", display = "Refresh Field", schema = "Field") +// public void refresh_field(RmiTraceObject obj) { +// try (RmiTransaction tx = cmds.state.trace.openTx("RefreshField")) { +// String path = obj.getPath(); +// Field field = (Field) getObjectFromPath(path); +// cmds.putFieldDetails(path, field); +// } +// } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh objects", + schema = "ObjectReferenceContainer") + public void refresh_objects(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshObjects")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + Object parent = getObjectFromPath(ppath); + if (parent instanceof ReferenceType refType) { + cmds.putObjectContainer(path, refType.instances(cmds.MAX_REFS)); + } + if (parent instanceof ThreadReference thread) { + try { + if (path.endsWith("OwnedMonitors")) { + cmds.putObjectContainer(path, thread.ownedMonitors()); + } + } + catch (IncompatibleThreadStateException e) { + // IGNORE + } + } + if (parent instanceof ObjectReference ref && path.endsWith("ReferringObjects")) { + cmds.putObjectContainer(path, ref.referringObjects(cmds.MAX_REFS)); + } + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh object", + schema = "ObjectReferenceProxy") + public void refresh_object_proxy(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshObject")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + Object parent = getObjectFromPath(ppath); + if (parent instanceof ThreadReference thread && + path.endsWith("CurrentContendedMonitor")) { + try { + cmds.putObjectReference(path, thread.currentContendedMonitor()); + } + catch (IncompatibleThreadStateException e) { + // IGNORE + } + } + if (parent instanceof StackFrame frame) { + cmds.putObjectReference(path, frame.thisObject()); + } + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh object", + schema = "ObjectReference") + public void refresh_object(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshInstance")) { + String path = obj.getPath(); + ObjectReference method = (ObjectReference) getObjectFromPath(path); + cmds.putObjectReferenceDetails(path, method); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh methods", + schema = "MethodContainer") + public void refresh_methods(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshMethods")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + ReferenceType refType = (ReferenceType) getObjectFromPath(ppath); + cmds.putMethodContainer(path, refType); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh method", + schema = "Method") + public void refresh_method(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshMethod")) { + String path = obj.getPath(); + Method method = (Method) getObjectFromPath(path); + cmds.putMethodDetails(path, method, false); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh arguments", + schema = "ArgumentContainer") + public void refresh_arguments(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshArguments")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + Method method = (Method) getObjectFromPath(ppath); + cmds.putMethodTypeContainer(path, method); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "load_class", + display = "Load class", + schema = "ReferenceTypeContainer") + public void find_class(RmiTraceObject obj, + @TargetMethod.Param( + description = "Class to open", + display = "Class", + name = "find") String targetClass) { + try (RmiTransaction tx = cmds.state.trace.openTx("FindClass")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + Object parent = getObjectFromPath(ppath); + if (parent instanceof VirtualMachine vm) { + cmds.loadReferenceType(path, vm.allClasses(), targetClass); + } + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh_memory", + display = "Refresh memory", + schema = "Memory") + public void refresh_memory(RmiTraceObject obj) { + refresh_reference_types(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh_types", + display = "Refresh reference types", + schema = "ReferenceTypeContainer") + public void refresh_reference_types(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshReferenceTypes")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + Object parent = getObjectFromPath(ppath); + if (parent instanceof VirtualMachine vm) { + cmds.putReferenceTypeContainer(path, vm.allClasses()); + } + if (parent instanceof ClassLoaderReference ref) { + if (path.endsWith("DefinedClasses")) { + cmds.putReferenceTypeContainer(path, ref.definedClasses()); + } + if (path.endsWith("VisibleClasses")) { + cmds.putReferenceTypeContainer(path, ref.visibleClasses()); + } + } + if (parent instanceof ClassType ct) { + if (path.endsWith("AllInterfaces")) { + cmds.putInterfaceTypes(path, ct.allInterfaces()); + } + if (path.endsWith("Interfaces")) { + cmds.putInterfaceTypes(path, ct.interfaces()); + } + if (path.endsWith("SubClasses")) { + cmds.putClassTypes(path, ct.subclasses()); + } + } + if (parent instanceof InterfaceType it) { + if (path.endsWith("Implementors")) { + cmds.putClassTypes(path, it.implementors()); + } + if (path.endsWith("SubInterfaces")) { + cmds.putInterfaceTypes(path, it.subinterfaces()); + } + if (path.endsWith("SuperInterfaces")) { + cmds.putInterfaceTypes(path, it.superinterfaces()); + } + } + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh reference type", + schema = "ReferenceTypeProxy") + public void refresh_reference_type_proxy(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshReferenceType")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + Object parent = getObjectFromPath(ppath); + if (parent instanceof ObjectReference ref) { + cmds.putReferenceType(path, ref.referenceType(), false); + } + if (parent instanceof ClassObjectReference ref && path.endsWith("ReflectedType")) { + cmds.putReferenceType(path, ref.reflectedType(), false); + } + if (parent instanceof ClassType ct) { + cmds.putReferenceType(path, ct.superclass(), false); + } + if (parent instanceof Method method) { + cmds.putReferenceType(path, method.declaringType(), false); + } + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh reference type", + schema = "ReferenceType") + public void refresh_reference_type(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshReferenceType")) { + String path = obj.getPath(); + ReferenceType refType = (ReferenceType) getObjectFromPath(path); + cmds.putReferenceType(path, refType, false); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "load", + display = "Load reference", + schema = "ReferenceType") + public void load_reftype(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshReferenceType")) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + String path = obj.getPath(); + String mempath = cmds.getPath(vm) + ".Classes"; + ReferenceType refType = (ReferenceType) getObjectFromPath(path); + cmds.putReferenceType(mempath, refType, true); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh variables", + schema = "VariableContainer") + public void refresh_variables(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshVariables")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + Object parent = getObjectFromPath(ppath); + try { + if (parent instanceof Method method) { + if (path.endsWith("Arguments")) { + cmds.putLocalVariableContainer(path, method.arguments()); + } + if (path.endsWith("Variables")) { + cmds.putLocalVariableContainer(path, method.variables()); + } + } + if (parent instanceof StackFrame frame) { + Map map = frame.getValues(frame.visibleVariables()); + cmds.putLocalVariableContainer(path, map); + } + } + catch (AbsentInformationException e) { + // IGNORE + } + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh variable", + schema = "Variable") + public void refresh_variable(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshVariable")) { + String path = obj.getPath(); + Object object = getObjectFromPath(path); + if (object instanceof LocalVariable var) { + cmds.putLocalVariableDetails(path, var); + } + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh locations", + schema = "LocationContainer") + public void refresh_locations(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshLocations")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + Object parent = getObjectFromPath(ppath); + if (parent instanceof Method) { + Method method = (Method) parent; + cmds.putLocationContainer(path, method); + } + if (parent instanceof ReferenceType) { + ReferenceType ref = (ReferenceType) parent; + cmds.putLocationContainer(path, ref); + } + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh location", + schema = "Location") + public void refresh_location(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshLocation")) { + String path = obj.getPath(); + Location loc = (Location) getObjectFromPath(path); + cmds.putLocationDetails(path, loc); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh breakpoints", + schema = "BreakpointContainer") + public void refresh_breakpoints(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshBreakpoints")) { + cmds.putBreakpoints(); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh events", + schema = "EventContainer") + public void refresh_events(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshEvents")) { + cmds.putEvents(); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh values", + schema = "ValueContainer") + public void refresh_values(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshValues")) { + String path = obj.getPath(); + String ppath = cmds.getParentPath(path); + Object parent = getObjectFromPath(ppath); + if (parent instanceof ArrayReference arr) { + cmds.putValueContainer(path, arr.getValues()); + } + } + } + + @RmiMethodRegistry.TraceMethod( + action = "refresh", + display = "Refresh value", + schema = "Value") + public void refresh_value(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshLocation")) { + String path = obj.getPath(); + Value val = (Value) getObjectFromPath(path); + cmds.putValueDetailsByType(path, val); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "set", + display = "Set value", + schema = "Variable") + public void set_value_lvar(RmiTraceObject obj, String value) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshLocation")) { + String path = obj.getPath(); + LocalVariable lvar = (LocalVariable) getObjectFromPath(path); + cmds.modifyValue(lvar, value); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "set", + display = "Set value", + schema = "Field") + public void set_value_field(RmiTraceObject obj, String value) { + try (RmiTransaction tx = cmds.state.trace.openTx("RefreshLocation")) { + String path = obj.getPath(); + Field field = (Field) getObjectFromPath(path); + cmds.modifyValue(field, value); + } + } + + @RmiMethodRegistry.TraceMethod(action = "activate", display = "Activate", schema = "ANY") + public void activate(RmiTraceObject obj) { + try (RmiTransaction tx = cmds.state.trace.openTx("Activate")) { + String path = obj.getPath(); + cmds.activate(path); + } + } + + @RmiMethodRegistry.TraceMethod(action = "kill", display = "Kill", schema = "VirtualMachine") + public void kill(RmiTraceObject obj) { + try { + manager.getJdi().sendInterruptNow(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + + @RmiMethodRegistry.TraceMethod(action = "resume", display = "Resume", schema = "VirtualMachine") + public void resume_vm(RmiTraceObject obj) { + VirtualMachine vm = (VirtualMachine) getObjectFromPath(obj.getPath()); + vm.resume(); + manager.getHooks().setState(vm); + } + + @RmiMethodRegistry.TraceMethod(action = "resume", display = "Resume", schema = "Thread") + public void resume(RmiTraceObject obj) { + ThreadReference thread = (ThreadReference) getObjectFromPath(obj.getPath()); + thread.resume(); + manager.getHooks().setState(thread.virtualMachine()); + } + + @RmiMethodRegistry.TraceMethod( + action = "suspend", + display = "Suspend", + schema = "VirtualMachine") + public void suspend(RmiTraceObject obj) { + Object object = getObjectFromPath(obj.getPath()); + if (object instanceof ThreadReference thread) { + thread.suspend(); + manager.getHooks().setState(thread.virtualMachine()); + } + else { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + vm.suspend(); + manager.getHooks().setState(vm); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "interrupt", + display = "Interrupt", + schema = "VirtualMachine") + public void interrupt(RmiTraceObject obj) { + suspend(obj); + } + + // NB: For the VirtualMachine, the step methods add requests for break-on-step for all threads. + // These requests will remain pending until the VM is resumed. + @RmiMethodRegistry.TraceMethod( + action = "step_into", + display = "Step into", + schema = "VirtualMachine") + public void step_vm_into(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + List threads = getThreadsFromValue(obj); + for (ThreadReference thread : threads) { + try { + StepRequest stepReq = vm.eventRequestManager() + .createStepRequest(thread, StepRequest.STEP_MIN, + StepRequest.STEP_INTO); + stepReq.enable(); + } + catch (DuplicateRequestException dre) { + // IGNORE + } + } + vm.resume(); + } + + @RmiMethodRegistry.TraceMethod( + action = "step_over", + display = "Step over", + schema = "VirtualMachine") + public void step_vm_over(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + List threads = getThreadsFromValue(obj); + for (ThreadReference thread : threads) { + try { + StepRequest stepReq = vm.eventRequestManager() + .createStepRequest(thread, StepRequest.STEP_MIN, + StepRequest.STEP_OVER); + stepReq.enable(); + } + catch (DuplicateRequestException dre) { + // IGNORE + } + } + vm.resume(); + } + + @RmiMethodRegistry.TraceMethod( + action = "step_out", + display = "Step out", + schema = "VirtualMachine") + public void step_vm_out(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + List threads = getThreadsFromValue(obj); + for (ThreadReference thread : threads) { + try { + StepRequest stepReq = vm.eventRequestManager() + .createStepRequest(thread, StepRequest.STEP_MIN, + StepRequest.STEP_OUT); + stepReq.enable(); + } + catch (DuplicateRequestException dre) { + // IGNORE + } + } + vm.resume(); + } + + @RmiMethodRegistry.TraceMethod(action = "step_into", display = "Step into", schema = "Thread") + public void step_into(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + ThreadReference thread = (ThreadReference) getObjectFromPath(obj.getPath()); + StepRequest stepReq = vm.eventRequestManager() + .createStepRequest(thread, StepRequest.STEP_MIN, + StepRequest.STEP_INTO); + stepReq.enable(); + vm.resume(); + } + + @RmiMethodRegistry.TraceMethod(action = "step_over", display = "Step over", schema = "Thread") + public void step_over(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + ThreadReference thread = (ThreadReference) getObjectFromPath(obj.getPath()); + StepRequest stepReq = vm.eventRequestManager() + .createStepRequest(thread, StepRequest.STEP_OVER, + StepRequest.STEP_INTO); + stepReq.enable(); + vm.resume(); + } + + @RmiMethodRegistry.TraceMethod(action = "step_out", display = "Step out", schema = "Thread") + public void step_out(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + ThreadReference thread = (ThreadReference) getObjectFromPath(obj.getPath()); + StepRequest stepReq = vm.eventRequestManager() + .createStepRequest(thread, StepRequest.STEP_OUT, + StepRequest.STEP_INTO); + stepReq.enable(); + vm.resume(); + } + +// public void step_advance(Object obj) {} +// public void step_return(Object obj) {} + + @RmiMethodRegistry.TraceMethod( + action = "thread_interrupt", + display = "Thread Interrupt", + schema = "Thread") + public void thread_interrupt(RmiTraceObject obj) { + Object object = getObjectFromPath(obj.getPath()); + if (object instanceof ThreadReference thread) { + thread.interrupt(); + manager.getHooks().setState(thread.virtualMachine()); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "pop_stack", + display = "Pop stack", + schema = "StackFrame") + public void pop_stack(RmiTraceObject obj) { + StackFrame frame = (StackFrame) getObjectFromPath(obj.getPath()); + ThreadReference thread = frame.thread(); + try { + thread.popFrames(frame); + } + catch (IncompatibleThreadStateException e) { + Msg.out("Incompatible thread state for pop"); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "break_location", + display = "Break on execute", + schema = "Location") + public void break_location(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof Location loc) { + BreakpointRequest brkReq = vm.eventRequestManager() + .createBreakpointRequest(loc); + brkReq.enable(); + cmds.putBreakpoints(); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "break_field_access", + display = "Break on access", + schema = "Field") + public void break_access(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof Field field) { + AccessWatchpointRequest brkReq = vm.eventRequestManager() + .createAccessWatchpointRequest(field); + brkReq.enable(); + cmds.putBreakpoints(); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "break_field_modified", + display = "Break on modify", + schema = "Field") + public void break_modify(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof Field field) { + ModificationWatchpointRequest brkReq = vm.eventRequestManager() + .createModificationWatchpointRequest(field); + brkReq.enable(); + cmds.putBreakpoints(); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "break_exception", + display = "Break on exception", + schema = "ReferenceType") + public void break_exception(RmiTraceObject obj, + @TargetMethod.Param( + description = "Caught exceptions will be notified", + display = "NotifyCaught", + name = "notifyC") Boolean notifyCaught, + @TargetMethod.Param( + description = "Uncaught exceptions will be notified", + display = "NotifyUncaught", + name = "notifyU") Boolean notifyUncaught) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof ReferenceType reftype) { + ExceptionRequest excReq = vm.eventRequestManager() + .createExceptionRequest(reftype, notifyCaught, notifyUncaught); + excReq.enable(); + cmds.putEvents(); + } + } + + private void break_started(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + ThreadStartRequest brkReq = vm.eventRequestManager() + .createThreadStartRequest(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof ThreadReference ref) { + brkReq.putProperty("Thread", ref); + brkReq.addThreadFilter(ref); + } + brkReq.enable(); + cmds.putEvents(); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_started", + display = "Break on thread start", + schema = "EventContainer") + public void break_started_container(RmiTraceObject obj) { + break_started(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_started", + display = "Break on thread start", + schema = "Thread") + public void break_started_thread(RmiTraceObject obj) { + break_started(obj); + } + + private void break_death(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + ThreadDeathRequest brkReq = vm.eventRequestManager() + .createThreadDeathRequest(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof ThreadReference ref) { + brkReq.putProperty("Thread", ref); + brkReq.addThreadFilter(ref); + } + brkReq.enable(); + cmds.putEvents(); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_death", + display = "Break on thread exit", + schema = "EventContainer") + public void break_death_container(RmiTraceObject obj) { + break_death(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_death", + display = "Break on thread exit", + schema = "Thread") + public void break_death_thread(RmiTraceObject obj) { + break_death(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_vm_death", + display = "Break on VM death", + schema = "VirtualMachine") + public void break_vm_death(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + VMDeathRequest brkReq = vm.eventRequestManager() + .createVMDeathRequest(); + brkReq.enable(); + cmds.putEvents(); + } + + private void break_enter(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + MethodEntryRequest brkReq = vm.eventRequestManager() + .createMethodEntryRequest(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof ReferenceType reftype) { + brkReq.putProperty("Class", reftype); + brkReq.addClassFilter(reftype); + } + if (ctxt instanceof ObjectReference ref) { + brkReq.putProperty("Instance", ref); + brkReq.addInstanceFilter(ref); + } + if (ctxt instanceof ThreadReference ref) { + brkReq.putProperty("Thread", ref); + brkReq.addThreadFilter(ref); + } + brkReq.enable(); + cmds.putEvents(); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_enter", + display = "Break on method enter", + schema = "EventContainer") + public void break_enter_container(RmiTraceObject obj) { + break_enter(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_enter", + display = "Break on method enter", + schema = "ReferenceType") + public void break_enter_reftype(RmiTraceObject obj) { + break_enter(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_enter_instance", + display = "Break on method enter", + schema = "ObjectReference") + public void break_enter_instance(RmiTraceObject obj) { + break_enter(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_enter_thread", + display = "Break on method enter", + schema = "Thread") + public void break_enter_thread(RmiTraceObject obj) { + break_enter(obj); + } + + private void break_exit(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + MethodExitRequest brkReq = vm.eventRequestManager() + .createMethodExitRequest(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof ReferenceType reftype) { + brkReq.putProperty("Class", reftype); + brkReq.addClassFilter(reftype); + } + if (ctxt instanceof ObjectReference ref) { + brkReq.putProperty("Instance", ref); + brkReq.addInstanceFilter(ref); + } + if (ctxt instanceof ThreadReference ref) { + brkReq.putProperty("Thread", ref); + brkReq.addThreadFilter(ref); + } + brkReq.enable(); + cmds.putEvents(); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_exit", + display = "Break on method exit", + schema = "EventContainer") + public void break_exit_container(RmiTraceObject obj) { + break_exit(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_exit", + display = "Break on method exit", + schema = "ReferenceType") + public void break_exit_reftype(RmiTraceObject obj) { + break_exit(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_exit", + display = "Break on method exit", + schema = "ObjectReference") + public void break_exit_instance(RmiTraceObject obj) { + break_exit(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_exit", + display = "Break on method exit", + schema = "Thread") + public void break_exit_thread(RmiTraceObject obj) { + break_exit(obj); + } + + private void break_load(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + ClassPrepareRequest brkReq = vm.eventRequestManager() + .createClassPrepareRequest(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof ReferenceType reftype) { + brkReq.putProperty("Class", reftype); + brkReq.addClassFilter(reftype); + } + brkReq.enable(); + cmds.putEvents(); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_load", + display = "Break on class load", + schema = "EventContainer") + public void break_load_container(RmiTraceObject obj) { + break_load(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_load", + display = "Break on class load", + schema = "ReferenceType") + public void break_load_reftype(RmiTraceObject obj) { + break_load(obj); + } + + private void break_unload(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + ClassUnloadRequest brkReq = vm.eventRequestManager() + .createClassUnloadRequest(); + brkReq.enable(); + cmds.putEvents(); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_unload", + display = "Break on class unload", + schema = "EventContainer") + public void break_unload_container(RmiTraceObject obj) { + break_unload(obj); + } + + private void break_mon_enter_contention(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + MonitorContendedEnterRequest brkReq = vm.eventRequestManager() + .createMonitorContendedEnterRequest(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof ReferenceType reftype) { + brkReq.putProperty("Class", reftype); + brkReq.addClassFilter(reftype); + } + if (ctxt instanceof ObjectReference ref) { + brkReq.putProperty("Instance", ref); + brkReq.addInstanceFilter(ref); + } + if (ctxt instanceof ThreadReference ref) { + brkReq.putProperty("Thread", ref); + brkReq.addThreadFilter(ref); + } + brkReq.enable(); + cmds.putEvents(); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_enter_contention", + display = "Break on monitor contended enter", + schema = "EventContainer") + public void break_mon_enter_contention_container(RmiTraceObject obj) { + break_mon_enter_contention(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_enter_contention", + display = "Break on monitor contended enter", + schema = "ReferenceType") + public void break_mon_enter_contention_reftype(RmiTraceObject obj) { + break_mon_enter_contention(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_enter_contention", + display = "Break on monitor contended enter", + schema = "ObjectReference") + public void break_mon_enter_contention_instance(RmiTraceObject obj) { + break_mon_enter_contention(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_enter_contention", + display = "Break on monitor contended enter", + schema = "Thread") + public void break_mon_enter_contention_thread(RmiTraceObject obj) { + break_mon_enter_contention(obj); + } + + private void break_mon_entered_contention(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + MonitorContendedEnteredRequest brkReq = vm.eventRequestManager() + .createMonitorContendedEnteredRequest(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof ReferenceType reftype) { + brkReq.putProperty("Class", reftype); + brkReq.addClassFilter(reftype); + } + if (ctxt instanceof ObjectReference ref) { + brkReq.putProperty("Instance", ref); + brkReq.addInstanceFilter(ref); + } + if (ctxt instanceof ThreadReference ref) { + brkReq.putProperty("Thread", ref); + brkReq.addThreadFilter(ref); + } + brkReq.enable(); + cmds.putEvents(); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_entered_contention", + display = "Break on monitor contented entered", + schema = "EventContainer") + public void break_mon_entered_contention_container(RmiTraceObject obj) { + break_mon_entered_contention(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_entered_contention", + display = "Break on monitor contented entered", + schema = "ReferenceType") + public void break_mon_entered_contention_reftype(RmiTraceObject obj) { + break_mon_entered_contention(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_entered_contention", + display = "Break on monitor contented entered", + schema = "ObjectReference") + public void break_mon_entered_contention_instance(RmiTraceObject obj) { + break_mon_entered_contention(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_entered_contention", + display = "Break on monitor contented entered", + schema = "Thread") + public void break_mon_entered_contention_thread(RmiTraceObject obj) { + break_mon_entered_contention(obj); + } + + private void break_mon_wait(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + MonitorWaitRequest brkReq = vm.eventRequestManager() + .createMonitorWaitRequest(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof ReferenceType reftype) { + brkReq.putProperty("Class", reftype); + brkReq.addClassFilter(reftype); + } + if (ctxt instanceof ObjectReference ref) { + brkReq.putProperty("Instance", ref); + brkReq.addInstanceFilter(ref); + } + if (ctxt instanceof ThreadReference ref) { + brkReq.putProperty("Thread", ref); + brkReq.addThreadFilter(ref); + } + brkReq.enable(); + cmds.putEvents(); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_wait", + display = "Break on monitor wait", + schema = "EventContainer") + public void break_mon_wait_container(RmiTraceObject obj) { + break_mon_wait(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_wait", + display = "Break on monitor wait", + schema = "ReferenceType") + public void break_mon_wait_reftype(RmiTraceObject obj) { + break_mon_wait(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_wait", + display = "Break on monitor wait", + schema = "ObjectReference") + public void break_mon_wait_instance(RmiTraceObject obj) { + break_mon_wait(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_wait", + display = "Break on monitor wait", + schema = "Thread") + public void break_mon_wait_thread(RmiTraceObject obj) { + break_mon_wait(obj); + } + + private void break_mon_waited(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + MonitorWaitedRequest brkReq = vm.eventRequestManager() + .createMonitorWaitedRequest(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof ReferenceType reftype) { + brkReq.putProperty("Class", reftype); + brkReq.addClassFilter(reftype); + } + if (ctxt instanceof ObjectReference ref) { + brkReq.putProperty("Instance", ref); + brkReq.addInstanceFilter(ref); + } + if (ctxt instanceof ThreadReference ref) { + brkReq.putProperty("Thread", ref); + brkReq.addThreadFilter(ref); + } + brkReq.enable(); + cmds.putEvents(); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_waited", + display = "Break on monitor waited", + schema = "EventContainer") + public void break_mon_waited_container(RmiTraceObject obj) { + break_mon_waited(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_waited", + display = "Break on monitor waited", + schema = "ReferenceType") + public void break_mon_waited_reftype(RmiTraceObject obj) { + break_mon_waited(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_waited", + display = "Break on monitor waited", + schema = "ObjectReference") + public void break_mon_waited_instance(RmiTraceObject obj) { + break_mon_waited(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "break_mon_waited", + display = "Break on monitor waited", + schema = "Thread") + public void break_mon_waited_thread(RmiTraceObject obj) { + break_mon_waited(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "add_count_filter", + display = "Add count filter", + schema = "Event") + public void add_count_filter(RmiTraceObject obj, + @TargetMethod.Param( + description = "Count", + display = "MaxCount", + name = "count") Integer count) { + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof EventRequest req) { + req.disable(); + req.addCountFilter(count); + cmds.setValue(obj.getPath(), "Count", count); + req.enable(); + cmds.putEvents(); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "set_class_filter", + display = "Set class filter", + schema = "Event") + public void set_class_filter(RmiTraceObject obj, + @TargetMethod.Param( + description = "Filter Pattern", + display = "Filter", + name = "filter") String filter, + @TargetMethod.Param( + description = "Exclude", + display = "Exclude", + name = "exclude") String exclude) { + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof MethodEntryRequest req) { + req.disable(); + if (exclude.equals("true")) { + req.addClassExclusionFilter(filter); + cmds.setValue(obj.getPath(), "Exclude", filter); + } + else { + req.addClassFilter(filter); + cmds.setValue(obj.getPath(), "Include", filter); + } + req.enable(); + } + if (ctxt instanceof MethodExitRequest req) { + req.disable(); + if (exclude.equals("true")) { + req.addClassExclusionFilter(filter); + cmds.setValue(obj.getPath(), "Exclude", filter); + } + else { + req.addClassFilter(filter); + cmds.setValue(obj.getPath(), "Include", filter); + } + req.enable(); + } + if (ctxt instanceof ClassPrepareRequest req) { + req.disable(); + if (exclude.equals("true")) { + req.addClassExclusionFilter(filter); + cmds.setValue(obj.getPath(), "Exclude", filter); + } + else { + req.addClassFilter(filter); + cmds.setValue(obj.getPath(), "Include", filter); + } + req.enable(); + } + if (ctxt instanceof ClassUnloadRequest req) { + req.disable(); + if (exclude.equals("true")) { + req.addClassExclusionFilter(filter); + cmds.setValue(obj.getPath(), "Exclude", filter); + } + else { + req.addClassFilter(filter); + cmds.setValue(obj.getPath(), "Include", filter); + } + req.enable(); + } + if (ctxt instanceof MonitorContendedEnterRequest req) { + req.disable(); + if (exclude.equals("true")) { + req.addClassExclusionFilter(filter); + cmds.setValue(obj.getPath(), "Exclude", filter); + } + else { + req.addClassFilter(filter); + cmds.setValue(obj.getPath(), "Include", filter); + } + req.enable(); + } + if (ctxt instanceof MonitorContendedEnteredRequest req) { + req.disable(); + if (exclude.equals("true")) { + req.addClassExclusionFilter(filter); + cmds.setValue(obj.getPath(), "Exclude", filter); + } + else { + req.addClassFilter(filter); + cmds.setValue(obj.getPath(), "Include", filter); + } + req.enable(); + } + if (ctxt instanceof MonitorWaitRequest req) { + req.disable(); + if (exclude.equals("true")) { + req.addClassExclusionFilter(filter); + cmds.setValue(obj.getPath(), "Exclude", filter); + } + else { + req.addClassFilter(filter); + cmds.setValue(obj.getPath(), "Include", filter); + } + req.enable(); + } + if (ctxt instanceof MonitorWaitedRequest req) { + req.disable(); + if (exclude.equals("true")) { + req.addClassExclusionFilter(filter); + cmds.setValue(obj.getPath(), "Exclude", filter); + } + else { + req.addClassFilter(filter); + cmds.setValue(obj.getPath(), "Include", filter); + } + req.enable(); + } + cmds.putEvents(); + } + + @RmiMethodRegistry.TraceMethod( + action = "set_source_filter", + display = "Set source filter", + schema = "Event") + public void set_source_filter(RmiTraceObject obj, + @TargetMethod.Param( + description = "Source Name Pattern", + display = "SourceName", + name = "srcname") String srcname) { + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof ClassPrepareRequest req) { + req.disable(); + req.addSourceNameFilter(srcname); + cmds.setValue(obj.getPath(), "SourceMatches", srcname); + req.enable(); + } + cmds.putEvents(); + } + + @RmiMethodRegistry.TraceMethod( + action = "set_platform_filter", + display = "Set platform filter", + schema = "Event") + public void set_platform_filter(RmiTraceObject obj) { + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof ThreadStartRequest req) { + req.disable(); + req.addPlatformThreadsOnlyFilter(); + cmds.setValue(obj.getPath(), "PlatformOnly", true); + req.enable(); + } + if (ctxt instanceof ThreadDeathRequest req) { + req.disable(); + req.addPlatformThreadsOnlyFilter(); + cmds.setValue(obj.getPath(), "PlatformOnly", true); + req.enable(); + } + cmds.putEvents(); + } + + @RmiMethodRegistry.TraceMethod( + action = "toggle_breakpoint", + display = "Toggle breakpoint", + schema = "BreakpointSpec") + public void toggle_breakpoint(RmiTraceObject obj) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof Field field) { + ModificationWatchpointRequest brkReq = vm.eventRequestManager() + .createModificationWatchpointRequest(field); + brkReq.enable(); + } + if (ctxt instanceof EventRequest req) { + if (req.isEnabled()) { + req.disable(); + } + else { + req.enable(); + } + } + cmds.putBreakpoints(); + } + + @RmiMethodRegistry.TraceMethod( + action = "toggle_event", + display = "Toggle event", + schema = "Event") + public void toggle_event(RmiTraceObject obj) { + Object ctxt = getObjectFromPath(obj.getPath()); + if (ctxt instanceof EventRequest req) { + if (req.isEnabled()) { + req.disable(); + } + else { + req.enable(); + } + cmds.putEvents(); + } + } + + @RmiMethodRegistry.TraceMethod( + action = "toggle_scope", + display = "Toggle scope", + schema = "MethodContainer") + public void toggle_scope_methods(RmiTraceObject obj) { + String ppath = cmds.getParentPath(obj.getPath()); + Object parent = getObjectFromPath(ppath); + manager.toggleScope(parent); + refresh_methods(obj); + } + + @RmiMethodRegistry.TraceMethod( + action = "toggle_scope", + display = "Toggle scope", + schema = "FieldContainer") + public void toggle_scope_fields(RmiTraceObject obj) { + String ppath = cmds.getParentPath(obj.getPath()); + Object parent = getObjectFromPath(ppath); + manager.toggleScope(parent); + if (obj.getPath().endsWith("Fields")) { + refresh_fields(obj); + } + if (obj.getPath().endsWith("Variables")) { + refresh_fields(obj); + } + } + + @RmiMethodRegistry.TraceMethod(action = "read_mem", display = "", schema = "VirtualMachine") + public long read_mem(RmiTraceObject obj, AddressRange range) { + VirtualMachine vm = manager.getJdi().getCurrentVM(); + MemoryMapper mapper = cmds.state.trace.memoryMapper; + Address start = mapper.mapBack(range.getMinAddress()); + try (RmiTransaction tx = cmds.state.trace.openTx("ReadMemory")) { + cmds.putMem(start, range.getLength(), true); + cmds.putMemState(start, range.getLength(), MemoryState.MS_KNOWN, true); + } + catch (Exception e) { + cmds.putMemState(start, range.getLength(), MemoryState.MS_ERROR, true); + } + return range.getLength(); + } + + private List getThreadsFromValue(RmiTraceObject obj) { + Object object = getObjectFromPath(obj.getPath()); + if (object instanceof VirtualMachine vm) { + return vm.allThreads(); + } + List threads = new ArrayList<>(); + if (object instanceof ThreadReference thread) { + threads.add(thread); + } + else { + threads.add(manager.getJdi().getCurrentThread()); + } + return threads; + } + + private Object getObjectFromPath(String path) { + return manager.objForPath(path); + } + +} diff --git a/Ghidra/Debug/Debugger-jpda/src/main/resources/ghidra/app/plugin/core/debug/client/tracermi/jdi_schema.xml b/Ghidra/Debug/Debugger-jpda/src/main/resources/ghidra/app/plugin/core/debug/client/tracermi/jdi_schema.xml new file mode 100644 index 0000000000..14b0cee484 --- /dev/null +++ b/Ghidra/Debug/Debugger-jpda/src/main/resources/ghidra/app/plugin/core/debug/client/tracermi/jdi_schema.xml @@ -0,0 +1,787 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ghidra/Debug/Debugger-jpda/src/test/java/ghidra/dbg/jdi/model/JdiModelTest.java b/Ghidra/Debug/Debugger-jpda/src/test/java/ghidra/dbg/jdi/model/JdiModelTest.java index 2b52c00da7..3b9e6ffe91 100644 --- a/Ghidra/Debug/Debugger-jpda/src/test/java/ghidra/dbg/jdi/model/JdiModelTest.java +++ b/Ghidra/Debug/Debugger-jpda/src/test/java/ghidra/dbg/jdi/model/JdiModelTest.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -66,7 +66,7 @@ public class JdiModelTest implements DebuggerModelTestUtils { waitOn(launcher.launch(parameters)); TargetObject vm = - Unique.assertOne(waitOn(model.fetchObjectElements("VirtualMachines")).values()); + Unique.assertOne(waitOn(model.fetchObjectElements("VMs")).values()); waitOn(vm.as(TargetKillable.class).kill()); } } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/TraceRmiLauncherServicePlugin.html b/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/TraceRmiLauncherServicePlugin.html index c42a4598fc..7ca467ef03 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/TraceRmiLauncherServicePlugin.html +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/TraceRmiLauncherServicePlugin.html @@ -733,94 +733,93 @@ python3 -m pip install --no-index -f Debugger-rmi-trace\pypkg\dist -f Debugger-a Alternatively, if you are trying to quit, but typed ".exit", just type "quit()" to terminate the session.

-

dbgeng-ext

+

dbgeng-ext

-

This launcher extends the base dbgeng launcher adding extra options (a la IDebugClient's CreateProcess2).

+

This launcher extends the base dbgeng launcher adding extra options (a la IDebugClient's + CreateProcess2).

+

Options

-

Options

+
    +
  • Dir: This is the starting directory for the process.
  • -
      -
    • Dir: This is the starting directory for the process.
    • +
    • Env: This is a composite string containg Environment Variable entries delineated + by '/0' separators. For example, you could redefine USERNAME and USERPROFILE with the entry + 'USERNAME=SomeUser/0USERPROFILE=C:\Users\SomeUser'.
    • -
    • Env: This is a composite string containg Environment Variable entries - delineated by '/0' separators. For example, you could redefine USERNAME and USERPROFILE - with the entry 'USERNAME=SomeUser/0USERPROFILE=C:\Users\SomeUser'.
    • +
    • CreateFlags: Flags used when creating the process, typically either + DEBUG_PROCESS(1) or DEBUG_ONLY_THIS_PROCESS(2) if you do not wish to follow spawned + processes. Other possible values are defined by processes.h's + CreateProcessCreationFlags.
    • -
    • CreateFlags: Flags used when creating the process, typically either DEBUG_PROCESS(1) or - DEBUG_ONLY_THIS_PROCESS(2) if you do not wish to follow spawned processes. Other possible values - are defined by processes.h's CreateProcessCreationFlags.
    • +
    • CreateFlags (Engine): Engine-specific flags used when creating the process + (defined in dbgeng.h). Typically, these are set to 0.
    • -
    • CreateFlags (Engine): Engine-specific flags used when creating the process (defined in dbgeng.h). - Typically, these are set to 0.
    • +
    • VerifierFlags (Engine): Flags used by the Application Verifier. Typically unused, + but, if desired, CreateEngineFlags must include + DEBUG_ECREATE_PROCESS_USE_VERIFIER_FLAGS(2).
    • +
    -
  • VerifierFlags (Engine): Flags used by the Application Verifier. Typically unused, but, if desired, - CreateEngineFlags must include DEBUG_ECREATE_PROCESS_USE_VERIFIER_FLAGS(2).
  • -
+

dbgeng-attach

- -

dbgeng-attach

+

This launcher allows the user to attach to a local running process. Options are the same as + those for the base dbgeng, except for ProcessId and AttachFlags

-

This launcher allows the user to attach to a local running process. Options are the same as those for the base dbgeng, except for ProcessId and AttachFlags

+

Options

+
    +
  • ProcessId: The pid of the process you wish to attach to.
  • -

    Options

    +
  • AttachFlags: Flags used when attaching to the target process, typically + DEBUG_ATTACH_PROCESS(0). Other possible values are defined in dbgeng.h and determine whether + the attach should be invasive or not and the status of the process after attaching.
  • +
-
    -
  • ProcessId: The pid of the process you wish to attach to.
  • +

    dbgeng-remote

    -
  • AttachFlags: Flags used when attaching to the target process, typically DEBUG_ATTACH_PROCESS(0). Other possible values - are defined in dbgeng.h and determine whether the attach should be invasive or not - and the status of the process after attaching.
  • +

    This launcher extends the base dbgeng launcher adding an option for connecting through a + remote process server.

    -
+

Options

- -

dbgeng-remote

- -

This launcher extends the base dbgeng launcher adding an option for connecting through a remote process server. -

- - -

Options

- -
    -
  • Connection: This is the connection string specifying the transport options for - communicating with the remote server. A typical example might be 'tcp:port=12345,server=192.168.0.2'' - for a process server launched on the machine at 192.168.0.2 using: +
      +
    • + Connection: This is the connection string specifying the transport options for + communicating with the remote server. A typical example might be + 'tcp:port=12345,server=192.168.0.2'' for a process server launched on the machine at + 192.168.0.2 using:
       dbgsrv -t tcp:port=12345
       
      -
    • -
    +
  • +
- -

dbgeng-kernel

+

dbgeng-kernel

-

This version of the dbgeng should be used for kernel-debugging of a remote machine. Options are the - same as the base dbgeng, except for the connection-string arguments. For remote - debugging, the target machine should be booted with the appropriate options, set using BCDEDIT or the - equivalent, such as: -

-
  • +

    This version of the dbgeng should be used for kernel-debugging of a remote machine. Options + are the same as the base dbgeng, except for the connection-string arguments. For remote + debugging, the target machine should be booted with the appropriate options, set using BCDEDIT + or the equivalent, such as:

    + +
      +
    •  bcdedit /debug ON
       bdcedit /dbgsettings NET HOSTIP:IP PORT:54321 KEY:1.1.1.1
       
      -
    -

    - where IP= the address of the machine runing Ghidra. -

    +
  • +
-

Options

+

where IP= the address of the machine runing Ghidra.

-
    -
  • Arguments: This is the connection string specifying the transport options for - communicating with the remote target. A typical example might be 'net:port=54321,key=1.1.1.1'.' -
  • -
+

Options

+ +
    +
  • Arguments: This is the connection string specifying the transport options for + communicating with the remote target. A typical example might be + 'net:port=54321,key=1.1.1.1'.'
  • +
-

TTD (Time-Travel Debugging)

This is a nascent extension to our launcher for the Windows Debugger. The launcher itself @@ -854,6 +853,88 @@ bdcedit /dbgsettings NET HOSTIP:IP PORT:54321 KEY:1.1.1.1 contain dbgmodel.dll and the scripts that implement TTD. These are most easily obtained by installing WinDbg Preview or later.

+

Stock Java Launchers

+ +

The following launchers based on the Java Debugger are included out of the box:

+ +

java launch

+ +

This launcher uses the native Java Debug Interface (JDI) to launch the current + .class file.

+ +

Setup

+ +

You must have Java installed on the local system. No additional setup is required.

+ +

Options

+ +
    +
  • Arguments: These are the command-line arguments to pass into the target.
  • + +
  • Arch: The architecture (currently, either "JVM" or "Dalvik").
  • + +
  • Suspend: Should the target be suspended on launch.
  • + +
  • Include virtual threads: As described.
  • + +
  • JShell cmd: If desired, the path to the jshell binary that will host + execution.
  • +
+ +

java attach port

+ +

This launcher uses the native Java Debug Interface (JDI) to attach to a running java program + launched with an open Java Debug Wire Port (JDWP), e.g.:

+ +
    +
  • +
    +java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=localhost:54321 Target.class
    +
    +
  • +
+ +

Setup

+ +

Identical to that for the java launcher.

+ +

Options

+ +
    +
  • Arch: The architecture (currently, either "JVM" or "Dalvik").
  • + +
  • Host: The host IP where the target is running.
  • + +
  • Port: The open JDWP port used by the target.
  • + +
  • Timeout: How long to wait for a connection attempt.
  • + +
  • JShell cmd: If desired, the path to the jshell binary that will host + execution.
  • +
+ +

java attach PID

+ +

This launcher uses the native Java Debug Interface (JDI) to attach to a running java program + launched with a Java Debug Wire Port (JDWP) identified by process id.

+ +

Setup

+ +

Identical to that for the java launcher.

+ +

Options

+ +
    +
  • Arch: The architecture (currently, either "JVM" or "Dalvik").
  • + +
  • Pid: The target process's ID.
  • + +
  • Timeout: How long to wait for a connection attempt.
  • + +
  • JShell cmd: If desired, the path to the jshell binary that will host + execution.
  • +
+

Development and Diagnostic Launchers

We currently provide one launcher for Trace RMI API exploration and development:

diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/DefaultMemoryMapper.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/DefaultMemoryMapper.java new file mode 100644 index 0000000000..569e1f5f5e --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/DefaultMemoryMapper.java @@ -0,0 +1,52 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import ghidra.program.model.address.*; +import ghidra.program.model.lang.*; +import ghidra.program.util.DefaultLanguageService; + +public class DefaultMemoryMapper implements MemoryMapper { + + private final AddressFactory factory; + + public DefaultMemoryMapper(LanguageID id) { + LanguageService langServ = DefaultLanguageService.getLanguageService(); + try { + Language lang = langServ.getLanguage(id); + this.factory = lang.getAddressFactory(); + } + catch (LanguageNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + public Address map(Address address) { + return address; + } + + @Override + public Address mapBack(Address address) { + return address; + } + + @Override + public Address genAddr(String space, long offset) { + return factory.getAddressSpace(space).getAddress(offset); + } + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/DefaultRegisterMapper.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/DefaultRegisterMapper.java new file mode 100644 index 0000000000..4f8b18905e --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/DefaultRegisterMapper.java @@ -0,0 +1,48 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import ghidra.program.model.lang.LanguageID; +import ghidra.program.model.lang.RegisterValue; + +public class DefaultRegisterMapper implements RegisterMapper { + + public DefaultRegisterMapper(LanguageID id) { + // Nothing so far + } + + @Override + public String mapName(String name) { + return name; + } + + @Override + public String mapNameBack(String name) { + return name; + } + + @Override + public RegisterValue mapValue(String name, RegisterValue rv) { + return rv; + } + + @Override + public RegisterValue mapValueBack(String name, RegisterValue rv) { + return rv; + } + + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/MemoryMapper.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/MemoryMapper.java new file mode 100644 index 0000000000..0f344c7661 --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/MemoryMapper.java @@ -0,0 +1,30 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import com.sun.jdi.VirtualMachine; + +import ghidra.program.model.address.Address; + +public interface MemoryMapper { + + public Address map(Address address); + + public Address mapBack(Address address); + + public Address genAddr(String space, long offset); + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/ProtobufSocket.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/ProtobufSocket.java new file mode 100644 index 0000000000..6372ca0c9b --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/ProtobufSocket.java @@ -0,0 +1,80 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; + +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.InvalidProtocolBufferException; + +import ghidra.util.Msg; + +public class ProtobufSocket { + public interface Decoder { + T decode(ByteBuffer buf) throws InvalidProtocolBufferException; + } + + private final ByteBuffer lenSend = ByteBuffer.allocate(4); + private final ByteBuffer lenRecv = ByteBuffer.allocate(4); + private final SocketChannel channel; + private final Decoder decoder; + + public ProtobufSocket(SocketChannel channel, Decoder decoder) { + this.channel = channel; + this.decoder = decoder; + } + + public void send(T msg) throws IOException { + synchronized (lenSend) { + lenSend.clear(); + lenSend.putInt(msg.getSerializedSize()); + lenSend.flip(); + channel.write(lenSend); + for (ByteBuffer buf : msg.toByteString().asReadOnlyByteBufferList()) { + channel.write(buf); + } + } + } + + public T recv() throws IOException { + synchronized (lenRecv) { + lenRecv.clear(); + while (lenRecv.hasRemaining()) { + channel.read(lenRecv); + } + lenRecv.flip(); + int len = lenRecv.getInt(); + // This is just for testing, so littering on the heap is okay. + ByteBuffer buf = ByteBuffer.allocate(len); + while (buf.hasRemaining()) { + channel.read(buf); + } + buf.flip(); + return decoder.decode(buf); + } + } + + public void close() { + try { + channel.close(); + } + catch (IOException e) { + Msg.error(this, "Unable to close ProtobufSocket"); + } + } +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RegisterMapper.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RegisterMapper.java new file mode 100644 index 0000000000..478bddfc80 --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RegisterMapper.java @@ -0,0 +1,30 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import ghidra.program.model.lang.RegisterValue; + +public interface RegisterMapper { + + public String mapName(String name); + + public String mapNameBack(String name); + + public RegisterValue mapValue(String name, RegisterValue rv); + + public RegisterValue mapValueBack(String name, RegisterValue rv); + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiBatch.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiBatch.java new file mode 100644 index 0000000000..65a8c393b5 --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiBatch.java @@ -0,0 +1,44 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import java.util.HashSet; +import java.util.Set; + +public class RmiBatch { + + + private int refCount = 0; + private Set futures = new HashSet<>(); + + public void inc() { + refCount++; + } + + public int dec() { + return --refCount; + } + + public void append(Object f) { + futures.add(f); + } + + public Object results() { + return null; + } + + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiClient.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiClient.java new file mode 100644 index 0000000000..37af45ff1a --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiClient.java @@ -0,0 +1,731 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Parameter; +import java.nio.channels.SocketChannel; +import java.util.*; + +import org.jdom.JDOMException; + +import com.google.protobuf.ByteString; + +import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler; +import ghidra.dbg.target.schema.*; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.*; +import ghidra.rmi.trace.TraceRmi; +import ghidra.rmi.trace.TraceRmi.*; +import ghidra.rmi.trace.TraceRmi.Language; +import ghidra.rmi.trace.TraceRmi.Value.Builder; +import ghidra.trace.model.Lifespan; +import ghidra.util.Msg; + +public class RmiClient { + private final ProtobufSocket socket; + private final String description; + private int nextTraceId = 0; + private RmiBatch currentBatch = null; + + Map traces = new HashMap<>(); + private SchemaContext schemaContext; + private RmiMethodHandlerThread handler; + private static RmiMethodRegistry methodRegistry; + private Deque requests = new LinkedList<>(); + + public static TargetObjectSchema loadSchema(String resourceName, String rootName) { + XmlSchemaContext schemaContext; + + try { + InputStream resourceAsStream = RmiClient.class.getResourceAsStream(resourceName); + schemaContext = XmlSchemaContext + .deserialize(resourceAsStream); + return schemaContext.getSchema(schemaContext.name(rootName)); + } + catch (JDOMException | IOException e) { + throw new AssertionError(e); + } + } + +// public static TargetObjectSchema getSchema(String name) { +// try { +// return SCHEMA_CTX.getSchema(new SchemaName(name)); +// } catch (NullPointerException e) { +// System.err.println("Possibly non-existent schema: "+name); +// return SCHEMA_CTX.getSchema(new SchemaName("OBJECT")); +// } +// } + + public static enum TraceRmiResolution { + RES_ADJUST("adjust", Resolution.CR_ADJUST), // + RES_DENY("deny", Resolution.CR_DENY), // + RES_TRUNCATE("truncate", Resolution.CR_TRUNCATE), // + ; + + TraceRmiResolution(String val, TraceRmi.Resolution description) { + this.val = val; + this.description = description; + } + + public final String val; + public final Resolution description; + } + + public static enum TraceRmiValueKinds { + ATTRIBUTES("attributes", ValueKinds.VK_ATTRIBUTES), // + ELEMENTS("elements", ValueKinds.VK_ELEMENTS), // + BOTH("both", ValueKinds.VK_BOTH), // + ; + + TraceRmiValueKinds(String val, TraceRmi.ValueKinds description) { + this.val = val; + this.description = description; + } + + public final String val; + public final ValueKinds description; + } + + public RmiClient(SocketChannel channel, String description) { + this.socket = new ProtobufSocket<>(channel, RootMessage::parseFrom); + this.description = description; + this.handler = new RmiMethodHandlerThread(this, socket); + handler.start(); + } + + public ProtobufSocket getSocket() { + return socket; + } + + public String getDescription() { + return description; + } + + public void close() { + handler.close(); + socket.close(); + } + + private void send(RootMessage msg) { + try { + requests.push(msg); + socket.send(msg); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + public RmiTrace createTrace(String path, LanguageID language, CompilerSpecID compiler) { + if (compiler == null) { + compiler = new CompilerSpecID("default"); + } + RmiTrace trace = new RmiTrace(this, nextTraceId); + traces.put(nextTraceId, trace); + send(RootMessage.newBuilder() + .setRequestCreateTrace(RequestCreateTrace.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(nextTraceId++)) + .setLanguage(Language.newBuilder() + .setId(language.getIdAsString())) + .setCompiler(Compiler.newBuilder() + .setId(compiler.getIdAsString())) + .setPath(FilePath.newBuilder() + .setPath(path))) + .build()); + return trace; + } + + public void closeTrace(int id) { + send(RootMessage.newBuilder() + .setRequestCloseTrace(RequestCloseTrace.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(id))) + .build()); + } + + public void saveTrace(int id) { + send(RootMessage.newBuilder() + .setRequestSaveTrace(RequestSaveTrace.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(id))) + .build()); + } + + public void startTx(int traceId, String desc, boolean undoable, int txId) { + send(RootMessage.newBuilder() + .setRequestStartTx(RequestStartTx.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setTxid(TxId.newBuilder().setId(txId)) + .setDescription(desc) + .setUndoable(undoable)) + .build()); + } + + public void endTx(int traceId, int txId, boolean abort) { + send(RootMessage.newBuilder() + .setRequestEndTx(RequestEndTx.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setTxid(TxId.newBuilder().setId(txId)) + .setAbort(abort)) + .build()); + } + + public void snapshot(int traceId, String desc, String datetime, long snap) { + send(RootMessage.newBuilder() + .setRequestSnapshot(RequestSnapshot.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setSnap(Snap.newBuilder() + .setSnap(snap)) + .setDatetime(datetime) + .setDescription(desc)) + .build()); + } + + public void createOverlaySpace(int traceId, String base, String name) { + send(RootMessage.newBuilder() + .setRequestCreateOverlay(RequestCreateOverlaySpace.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setBaseSpace(base) + .setName(name)) + .build()); + } + + public void putBytes(int traceId, long snap, Address start, byte[] data) { + send(RootMessage.newBuilder() + .setRequestPutBytes(RequestPutBytes.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setSnap(Snap.newBuilder().setSnap(snap)) + .setStart(Addr.newBuilder() + .setSpace(start.getAddressSpace().getName()) + .setOffset(start.getOffset())) + .setData(ByteString.copyFrom(data))) + .build()); + } + + public void setMemoryState(int traceId, long snap, AddressRange range, MemoryState state) { + send(RootMessage.newBuilder() + .setRequestSetMemoryState(RequestSetMemoryState.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setSnap(Snap.newBuilder().setSnap(snap)) + .setRange(AddrRange.newBuilder() + .setSpace(range.getAddressSpace().getName()) + .setOffset(range.getMinAddress().getOffset()) + .setExtend(range.getLength() - 1)) + .setState(state)) + .build()); + } + + public void deleteBytes(int traceId, long snap, AddressRange range) { + send(RootMessage.newBuilder() + .setRequestDeleteBytes(RequestDeleteBytes.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setSnap(Snap.newBuilder().setSnap(snap)) + .setRange(AddrRange.newBuilder() + .setSpace(range.getAddressSpace().getName()) + .setOffset(range.getMinAddress().getOffset()) + .setExtend(range.getLength()))) + .build()); + } + + public void putRegisters(int traceId, long snap, String ppath, RegisterValue[] values) { + RequestPutRegisterValue.Builder builder = RequestPutRegisterValue.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setSnap(Snap.newBuilder().setSnap(snap)) + .setSpace(ppath); + for (int i = 0; i < values.length; i++) { + RegisterValue rv = values[i]; + ByteString val = ByteString.copyFrom(rv.toBytes()); + rv.getUnsignedValue(); + builder.addValues(i, RegVal.newBuilder() + .setName(rv.getRegister().getName()) + .setValue(val)); + } + send(RootMessage.newBuilder() + .setRequestPutRegisterValue(builder) + .build()); + } + + public void deleteRegisters(int traceId, long snap, String ppath, String[] names) { + RequestDeleteRegisterValue.Builder builder = RequestDeleteRegisterValue.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setSnap(Snap.newBuilder().setSnap(snap)) + .setSpace(ppath); + for (int i = 0; i < names.length; i++) { + String name = names[i]; + builder.setNames(i, name); + } + send(RootMessage.newBuilder() + .setRequestDeleteRegisterValue(builder) + .build()); + } + + public void createRootObject(int traceId, SchemaContext schemContext, String schema) { + this.schemaContext = schemContext; + String xmlCtx = XmlSchemaContext.serialize(schemContext); + send(RootMessage.newBuilder() + .setRequestCreateRootObject(RequestCreateRootObject.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setSchemaContext(xmlCtx) + .setRootSchema(schema)) + .build()); + } + + public void createObject(int traceId, String path) { + //System.err.println("createObject:"+path); + send(RootMessage.newBuilder() + .setRequestCreateObject(RequestCreateObject.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setPath(ObjPath.newBuilder().setPath(path))) + .build()); + } + + public void insertObject(int traceId, String path, Lifespan span, Resolution r) { + send(RootMessage.newBuilder() + .setRequestInsertObject(RequestInsertObject.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setObject(ObjSpec.newBuilder() + .setPath(ObjPath.newBuilder() + .setPath(path))) + .setSpan(Span.newBuilder() + .setMin(span.lmin()) + .setMax(span.lmax())) + .setResolution(r)) + .build()); + } + + public void insertObject(int traceId, ObjSpec object, Lifespan span, Resolution r) { + send(RootMessage.newBuilder() + .setRequestInsertObject(RequestInsertObject.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setObject(object) + .setSpan(Span.newBuilder() + .setMin(span.lmin()) + .setMax(span.lmax())) + .setResolution(r)) + .build()); + } + + public void removeObject(int traceId, ObjSpec object, Lifespan span, boolean tree) { + send(RootMessage.newBuilder() + .setRequestRemoveObject(RequestRemoveObject.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setObject(object) + .setSpan(Span.newBuilder() + .setMin(span.lmin()) + .setMax(span.lmax())) + .setTree(tree)) + .build()); + } + + public void setValue(int traceId, String ppath, Lifespan span, String key, Object value, + String resolution) { + Resolution r = resolution == null ? Resolution.CR_ADJUST + : TraceRmiResolution.valueOf(resolution).description; + send(RootMessage.newBuilder() + .setRequestSetValue(RequestSetValue.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setValue(ValSpec.newBuilder() + .setSpan(Span.newBuilder() + .setMin(span.lmin()) + .setMax(span.lmax())) + .setParent(ObjSpec.newBuilder() + .setPath(ObjPath.newBuilder() + .setPath(ppath))) + .setKey(key) + .setValue(buildValue(value))) + .setResolution(r)) + .build()); + } + + public void retainValues(int traceId, String ppath, Lifespan span, ValueKinds kinds, + Set keys) { + RequestRetainValues.Builder builder = RequestRetainValues.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setObject(ObjSpec.newBuilder() + .setPath(ObjPath.newBuilder() + .setPath(ppath))) + .setSpan(Span.newBuilder() + .setMin(span.lmin()) + .setMax(span.lmax())) + .setKinds(kinds) + .addAllKeys(keys); + send(RootMessage.newBuilder() + .setRequestRetainValues(builder) + .build()); + } + + public void getObject(int traceId, String path) { + RequestGetObject.Builder builder = RequestGetObject.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setObject(ObjSpec.newBuilder() + .setPath(ObjPath.newBuilder() + .setPath(path))); + send(RootMessage.newBuilder() + .setRequestGetObject(builder) + .build()); + } + + public void getValues(int traceId, Lifespan span, String pattern) { + send(RootMessage.newBuilder() + .setRequestGetValues(RequestGetValues.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setSpan(Span.newBuilder() + .setMin(span.lmin()) + .setMax(span.lmax())) + .setPattern(ObjPath.newBuilder().setPath(pattern))) + .build()); + } + + public void getValuesIntersecting(int traceId, Lifespan span, AddressRange range, + String key) { + send(RootMessage.newBuilder() + .setRequestGetValuesIntersecting(RequestGetValuesIntersecting.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setBox(Box.newBuilder() + .setSpan(Span.newBuilder() + .setMin(span.lmin()) + .setMax(span.lmax())) + .setRange(AddrRange.newBuilder() + .setSpace(range.getAddressSpace().getName()) + .setOffset(range.getMinAddress().getOffset()) + .setExtend(range.getLength()))) + .setKey(key)) + .build()); + } + + public RmiTraceObject proxyObjectId(int traceId, Long id) { + return RmiTraceObject.fromId(traces.get(traceId), id); + } + + public RmiTraceObject proxyObjectPath(int traceId, String path) { + return RmiTraceObject.fromPath(traces.get(traceId), path); + } + + public RmiTraceObject proxyObjectPath(int traceId, Long id, String path) { + return new RmiTraceObject(traces.get(traceId), id, path); + } + + @SuppressWarnings("unchecked") + private Builder buildValue(Object value) { + Builder builder = Value.newBuilder(); + if (value instanceof String str) { + return builder.setStringValue(str); + } + if (value instanceof Boolean bval) { + return builder.setBoolValue(bval); + } + if (value instanceof Short sval) { + return builder.setShortValue(sval); + } + if (value instanceof Integer ival) { + return builder.setIntValue(ival); + } + if (value instanceof Long lval) { + return builder.setLongValue(lval); + } + if (value instanceof ByteString bstr) { + return builder.setBytesValue(bstr); + } + if (value instanceof Byte b) { + return builder.setByteValue(b); + } + if (value instanceof Character c) { + return builder.setCharValue(c); + } + if (value instanceof Address address) { + Addr.Builder addr = Addr.newBuilder() + .setSpace(address.getAddressSpace().getName()) + .setOffset(address.getOffset()); + return builder.setAddressValue(addr); + } + if (value instanceof AddressRange range) { + AddrRange.Builder rng = AddrRange.newBuilder() + .setSpace(range.getAddressSpace().getName()) + .setOffset(range.getMinAddress().getOffset()) + .setExtend(range.getLength() - 1); + return builder.setRangeValue(rng); + } + if (value instanceof RmiTraceObject obj) { + return builder.setChildSpec(ObjSpec.newBuilder() + .setPath(ObjPath.newBuilder() + .setPath(obj.getPath()))); + } + if (value instanceof List list) { + if (list.get(0) instanceof String) { + StringArr.Builder b = StringArr.newBuilder().addAllArr((List) list); + return builder.setStringArrValue(b.build()); + } + if (list.get(0) instanceof Boolean) { + BoolArr.Builder b = BoolArr.newBuilder().addAllArr((List) list); + return builder.setBoolArrValue(b.build()); + } + if (list.get(0) instanceof Short) { + ShortArr.Builder b = ShortArr.newBuilder().addAllArr((List) list); + return builder.setShortArrValue(b.build()); + } + if (list.get(0) instanceof Integer) { + IntArr.Builder b = IntArr.newBuilder().addAllArr((List) list); + return builder.setIntArrValue(b.build()); + } + if (list.get(0) instanceof Long) { + LongArr.Builder b = LongArr.newBuilder().addAllArr((List) list); + return builder.setLongArrValue(b.build()); + } + } + + throw new RuntimeException("Unhandled type for buildValue: " + value); + } + + public void activate(int traceId, String path) { + send(RootMessage.newBuilder() + .setRequestActivate(RequestActivate.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setObject(ObjSpec.newBuilder() + .setPath(ObjPath.newBuilder() + .setPath(path)))) + .build()); + } + + public void disassemble(int traceId, long snap, Address start) { + send(RootMessage.newBuilder() + .setRequestDisassemble(RequestDisassemble.newBuilder() + .setOid(DomObjId.newBuilder() + .setId(traceId)) + .setSnap(Snap.newBuilder().setSnap(snap)) + .setStart(Addr.newBuilder() + .setSpace(start.getAddressSpace().getName()) + .setOffset(start.getOffset()))) + .build()); + } + + public void negotiate(String desc) { + RequestNegotiate.Builder builder = RequestNegotiate.newBuilder() + .setVersion(TraceRmiHandler.VERSION) + .setDescription(desc); + int i = 0; + for (RmiRemoteMethod m : methodRegistry.getMap().values()) { + Method method = buildMethod(m); + builder.addMethods(i++, method); + } + send(RootMessage.newBuilder() + .setRequestNegotiate(builder) + .build()); + } + + private Method buildMethod(RmiRemoteMethod method) { + Method.Builder builder = Method.newBuilder() + .setName(method.getName()) + .setDescription(method.getDescription()) + .setAction(method.getAction()) + .setDisplay(method.getDisplay()); + int i = 0; + for (RmiRemoteMethodParameter p : method.getParameters()) { + MethodParameter param = buildParameter(p); + builder.addParameters(i++, param); + } + return builder.build(); + } + + private MethodParameter buildParameter(RmiRemoteMethodParameter param) { + return MethodParameter.newBuilder() + .setName(param.getName()) + .setDisplay(param.getDisplay()) + .setDescription(param.getDescription()) + .setType(param.getType()) + .setDefaultValue(param.getDefaultValue()) + .setRequired(param.isRequired()) + .build(); + } + + public void handleInvokeMethod(int traceId, XRequestInvokeMethod req) { + RmiRemoteMethod rm = getMethod(req.getName()); + Object[] arglist = new Object[req.getArgumentsCount()]; + java.lang.reflect.Method m = rm.getMethod(); + Map argmap = new HashMap<>(); + for (int i = 0; i < req.getArgumentsCount(); i++) { + MethodArgument arg = req.getArguments(i); + argmap.put(arg.getName(), arg); + } + int i = 0; + for (Parameter p : m.getParameters()) { + MethodArgument arg = argmap.get(p.getName()); + if (arg != null) { + Object obj = argToObject(traceId, arg); + arglist[i++] = obj; + } + } + try { + Object ret = m.invoke(rm.getContainer(), arglist); + if (ret != null) { + socket.send(RootMessage.newBuilder() + .setXreplyInvokeMethod(XReplyInvokeMethod.newBuilder() + .setReturnValue(buildValue(ret))) + .build()); + } + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException + | IOException e) { + String message = e.getMessage(); + if (message != null) { + Msg.error(this, message); + try { + socket.send(RootMessage.newBuilder() + .setXreplyInvokeMethod( + XReplyInvokeMethod.newBuilder().setError(message)) + .build()); + } + catch (IOException e1) { + Msg.error(this, e1.getMessage()); + } + } + } + + } + + private Object argToObject(int traceId, MethodArgument arg) { + if (arg == null) { + throw new RuntimeException("Null argument passed to argToObject"); + } + Value value = arg.getValue(); + if (value.hasStringValue()) { + return value.getStringValue(); + } + if (value.hasStringArrValue()) { + return value.getStringArrValue(); + } + if (value.hasBoolValue()) { + return value.getBoolValue(); + } + if (value.hasBoolArrValue()) { + return value.getBoolArrValue(); + } + if (value.hasCharValue()) { + return value.getCharValue(); + } + if (value.hasCharArrValue()) { + return value.getCharArrValue(); + } + if (value.hasShortValue()) { + return value.getShortValue(); + } + if (value.hasShortArrValue()) { + return value.getShortArrValue(); + } + if (value.hasIntValue()) { + return value.getIntValue(); + } + if (value.hasIntArrValue()) { + return value.getIntArrValue(); + } + if (value.hasLongValue()) { + return value.getLongValue(); + } + if (value.hasLongArrValue()) { + return value.getLongArrValue(); + } + if (value.hasAddressValue()) { + return decodeAddr(traceId, value.getAddressValue()); + } + if (value.hasRangeValue()) { + return decodeRange(traceId, value.getRangeValue()); + } + if (value.hasByteValue()) { + return value.getByteValue(); + } + if (value.hasBytesValue()) { + return value.getBytesValue(); + } + if (value.hasNullValue()) { + return value.getNullValue(); + } + ObjDesc desc = value.getChildDesc(); + String path = desc.getPath().getPath(); + return proxyObjectPath(traceId, path); + } + + private Address decodeAddr(int id, Addr addr) { + RmiTrace trace = traces.get(id); + return trace.memoryMapper.genAddr(addr.getSpace(), addr.getOffset()); + } + + private AddressRange decodeRange(int id, AddrRange rng) { + RmiTrace trace = traces.get(id); + Address start = trace.memoryMapper.genAddr(rng.getSpace(), rng.getOffset()); + return new AddressRangeImpl(start, start.add(rng.getExtend())); + } + + public void setRegistry(RmiMethodRegistry methodRegistry) { + RmiClient.methodRegistry = methodRegistry; + } + + public RmiRemoteMethod getMethod(String name) { + return methodRegistry.getMap().get(name); + } + + public Object startBatch() { + if (currentBatch == null) { + currentBatch = new RmiBatch(); + } + currentBatch.inc(); + return currentBatch; + } + + public Object endBatch() { + RmiBatch cb = null; + if (0 == currentBatch.dec()) { + cb = currentBatch; + currentBatch = null; + } + if (cb != null) { + return cb.results(); + } + return null; + } + + public TargetObjectSchema getSchema(String schema) { + return schemaContext.getSchema(new SchemaName(schema)); + } + + public RootMessage getRequestsPoll() { + return requests.poll(); + } + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiMethodHandlerThread.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiMethodHandlerThread.java new file mode 100644 index 0000000000..f932496598 --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiMethodHandlerThread.java @@ -0,0 +1,75 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import ghidra.rmi.trace.TraceRmi.*; +import ghidra.util.Msg; + +public class RmiMethodHandlerThread extends Thread { + + private RmiClient client; + private ProtobufSocket socket; + private boolean terminated = false; + + public RmiMethodHandlerThread(RmiClient client, ProtobufSocket socket) { + this.client = client; + this.socket = socket; + } + + @Override + public void run() { + while (!terminated) { + try { + RootMessage msg = socket.recv(); + if (msg.hasXrequestInvokeMethod()) { + try { + XRequestInvokeMethod req = msg.getXrequestInvokeMethod(); + int id = req.getOid().getId(); + RmiTrace trace = client.traces.get(id); + trace.handleInvokeMethod(req); + } + catch (Exception e) { + e.printStackTrace(); + } + continue; + } + RootMessage request = client.getRequestsPoll(); + if (msg.hasError()) { + Msg.error(this, msg); + } + else if (msg.hasReplyCreateObject()) { + ReplyCreateObject reply = msg.getReplyCreateObject(); + RmiTrace trace = client.traces.get(request.getRequestCreateObject().getOid().getId()); + trace.handleCreateObject(reply); + } + else if (msg.hasReplyCreateTrace()) { + ReplyCreateTrace reply = msg.getReplyCreateTrace(); + RmiTrace trace = client.traces.get(request.getRequestCreateTrace().getOid().getId()); + trace.handleCreateTrace(reply); + } + } + catch (Exception e) { + Msg.error(this, e.getMessage()); + } + } + Msg.info(this, "Handler exiting"); + } + + public void close() { + terminated = true; + } + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiMethodRegistry.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiMethodRegistry.java new file mode 100644 index 0000000000..6205f5ff8f --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiMethodRegistry.java @@ -0,0 +1,50 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import java.lang.annotation.*; +import java.util.HashMap; +import java.util.Map; + +public class RmiMethodRegistry { + + /** + * An annotation for marking remote methods. + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public static @interface TraceMethod { + String action(); + String display() default ""; + String description() default ""; + String schema() default "ANY"; + } + + Map map = new HashMap<>(); + + public RmiRemoteMethod getMethod(String key) { + return map.get(key); + } + + public void putMethod(String key, RmiRemoteMethod value) { + map.put(key, value); + } + + public Map getMap() { + return map; + } + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiMethods.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiMethods.java new file mode 100644 index 0000000000..0e35c2b887 --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiMethods.java @@ -0,0 +1,22 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +public interface RmiMethods { + + // This is a marker class - we need an instance of the methods container to invoke its methods + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiRemoteMethod.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiRemoteMethod.java new file mode 100644 index 0000000000..30d6210266 --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiRemoteMethod.java @@ -0,0 +1,129 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +import ghidra.dbg.target.TargetMethod; +import ghidra.dbg.target.schema.*; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRange; +import ghidra.rmi.trace.TraceRmi.Value; + +public class RmiRemoteMethod { + + private final SchemaContext schemaContext; + private String name; + private String action; + private String display; + private String description; + private RmiRemoteMethodParameter[] params; + private TargetObjectSchema schema; + private RmiMethods instance; + private Method m; + + public RmiRemoteMethod(SchemaContext schemaContext, String name, String action, String display, String description, TargetObjectSchema schema, RmiMethods instance, Method m) { + this.schemaContext = schemaContext; + this.name = name; + this.action = action; + this.display = display; + this.description = description; + this.params = new RmiRemoteMethodParameter[m.getParameterCount()]; + this.schema = schema; + this.instance = instance; + this.m = m; + + int i = 0; + for (Parameter p : m.getParameters()) { + TargetObjectSchema pschema = getSchemaFromParameter(p); + String pname = p.getName(); // NB: don't change this unless yuou resolve the ordering issues + String pdesc = pname; + String pdisp = pname; + if (i == 0) { + RmiMethodRegistry.TraceMethod annot = m.getAnnotation(RmiMethodRegistry.TraceMethod.class); + if (annot != null) { + pschema = schemaContext.getSchema(new SchemaName(annot.schema())); + } + pdisp = "Object"; + } + Value pdef = null; + TargetMethod.Param pannot = p.getAnnotation(TargetMethod.Param.class); + if (pannot != null) { + pdesc = pannot.description(); + pdisp = pannot.display(); + } + boolean required = i != 0; + params[i++] = new RmiRemoteMethodParameter(pname, pschema, required, pdef, pdisp, pdesc); + } + } + + private TargetObjectSchema getSchemaFromParameter(Parameter p) { + if (p.getAnnotatedType().getType().equals(String.class)) { + return EnumerableTargetObjectSchema.STRING; + } + if (p.getAnnotatedType().getType().equals(Boolean.class)) { + return EnumerableTargetObjectSchema.BOOL; + } + if (p.getAnnotatedType().getType().equals(Integer.class)) { + return EnumerableTargetObjectSchema.INT; + } + if (p.getAnnotatedType().getType().equals(Long.class)) { + return EnumerableTargetObjectSchema.LONG; + } + if (p.getAnnotatedType().getType().equals(Address.class)) { + return EnumerableTargetObjectSchema.ADDRESS; + } + if (p.getAnnotatedType().getType().equals(AddressRange.class)) { + return EnumerableTargetObjectSchema.RANGE; + } + return EnumerableTargetObjectSchema.ANY; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getAction() { + return action; + } + + public String getDisplay() { + return display; + } + + public RmiRemoteMethodParameter[] getParameters() { + return params; + } + + public Method getMethod() { + return m; + } + + public TargetObjectSchema getSchema() { + return schema; + } + + public RmiMethods getContainer() { + return instance; + } + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiRemoteMethodParameter.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiRemoteMethodParameter.java new file mode 100644 index 0000000000..0994c27b47 --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiRemoteMethodParameter.java @@ -0,0 +1,71 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import ghidra.dbg.target.schema.TargetObjectSchema; +import ghidra.rmi.trace.TraceRmi.*; + +public class RmiRemoteMethodParameter { + + private final String name; + private final TargetObjectSchema schema; + private final boolean required; + private final Value defaultValue; + private final String display; + private final String description; + + public RmiRemoteMethodParameter(String name, TargetObjectSchema schema, boolean required, + Value defaultValue, String display, String description) { + this.name = name; + this.schema = schema; + this.required = required; + this.defaultValue = defaultValue; + this.display = display; + this.description = description; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getDisplay() { + return display; + } + + public ValueType getType() { + String schemaName = schema.getName().toString(); +// if (schemaName.equals("ANY")) { +// return ValueType.newBuilder().setName("OBJECT").build(); +// } + return ValueType.newBuilder().setName(schemaName).build(); + } + + public Value getDefaultValue() { + if (defaultValue != null) { + return defaultValue; + } + return Value.newBuilder().setNullValue(Null.newBuilder()).build(); + } + + public boolean isRequired() { + return required; + } + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiTrace.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiTrace.java new file mode 100644 index 0000000000..e364af9617 --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiTrace.java @@ -0,0 +1,216 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.lang.RegisterValue; +import ghidra.rmi.trace.TraceRmi.*; +import ghidra.trace.model.Lifespan; +import ghidra.util.LockHold; +import ghidra.util.Msg; + +public class RmiTrace { + + final RmiClient client; + private final int id; + + private int nextTx = 0; + private Object txLock = new Object(); + private ReadWriteLock snLock = new ReentrantReadWriteLock(); + + private Set overlays = new HashSet<>(); + private long currentSnap = -1; + private boolean closed = false; + + public MemoryMapper memoryMapper; + public RegisterMapper registerMapper; + + public RmiTrace(RmiClient client, int id) { + this.client = client; + this.id = id; + } + + public void close() { + if (closed) { + return; + } + client.closeTrace(id); + } + + public void save() { + client.saveTrace(id); + } + + public RmiTransaction startTx(String description, boolean undoable) { + int txid; + synchronized(txLock) { + txid = nextTx++; + } + client.startTx(id, description, undoable, txid); + return new RmiTransaction(this, txid); + } + + public RmiTransaction openTx(String description) { + return startTx(description, false); + } + + public void endTx(int txid, boolean abort) { + client.endTx(id, txid, abort); + } + + public long nextSnap() { + try (LockHold hold = LockHold.lock(snLock.writeLock())) { + return ++currentSnap; + } + } + + public long snapshot(String description, String datatime, Long snap) { + if (snap == null) { + snap = nextSnap(); + } + client.snapshot(id, description, datatime, snap.intValue()); + return snap.longValue(); + } + + public long getSnap() { + try (LockHold hold = LockHold.lock(snLock.readLock())) { + return currentSnap; + } + } + + public void setSnap(long snap) { + try (LockHold hold = LockHold.lock(snLock.writeLock())) { + this.currentSnap = snap; + } + } + + public long snapOrCurrent(Long snap) { + try (LockHold hold = LockHold.lock(snLock.readLock())) { + return snap == null ? this.currentSnap : snap.longValue(); + } + } + + public void createOverlaySpace(String base, String name) { + if (overlays.contains(name)) { + return; + } + client.createOverlaySpace(id, base, name); + } + + public void createOverlaySpace(Address repl, Address orig) { + createOverlaySpace(repl.getAddressSpace().getName(), orig.getAddressSpace().getName()); + } + + public void putBytes(Address addr, byte[] data, Long snap) { + client.putBytes(id, snapOrCurrent(snap), addr, data); + } + + public void setMemoryState(AddressRange range, MemoryState state, Long snap) { + client.setMemoryState(id, snapOrCurrent(snap), range, state); + } + + public void deleteBytes(AddressRange range, Long snap) { + client.deleteBytes(id, snapOrCurrent(snap), range); + } + + public void putRegisters(String ppath, RegisterValue[] values, Long snap) { + client.putRegisters(id, snapOrCurrent(snap), ppath, values); + } + + public void deleteRegisters(String ppath, String[] names, Long snap) { + client.deleteRegisters(id, snapOrCurrent(snap), ppath, names); + } + + public void createRootObject(SchemaContext schemaContext, String schema) { + client.createRootObject(id, schemaContext, schema); + } + + public void createObject(String path) { + client.createObject(id, path); + } + + public void handleCreateObject(ReplyCreateObject reply) { + RmiTraceObject obj = new RmiTraceObject(this, reply.getObject()); + try (RmiTransaction tx = startTx("CreateObject", false); LockHold hold = LockHold.lock(snLock.readLock())) { + obj.insert(currentSnap, null); + } + } + + public void handleCreateTrace(ReplyCreateTrace reply) { + } + + public void insertObject(String path) { + Lifespan span = getLifespan(); + client.insertObject(id, path, span, Resolution.CR_ADJUST); + } + + private Lifespan getLifespan() { + try (LockHold hold = LockHold.lock(snLock.readLock())) { + return Lifespan.nowOn(currentSnap); + } + } + + public void setValue(String ppath, String key, Object value) { + Lifespan span = getLifespan(); + client.setValue(id, ppath, span, key, value, null); + } + + public void retainValues(String ppath, Set keys, ValueKinds kinds) { + Lifespan span = getLifespan(); + client.retainValues(id, ppath, span, kinds, keys); + } + + public void activate(String path) { + if (path == null) { + Msg.error(this, "Attempt to activate null"); + } + client.activate(id, path); + } + + public void disassemble(Address start, Long snap) { + client.disassemble(id, snapOrCurrent(snap), start); + } + + public void handleInvokeMethod(XRequestInvokeMethod req) { + try (RmiTransaction tx = startTx("InvokeMethod", false)) { + client.handleInvokeMethod(id, req); + } + } + + public RmiTraceObject proxyObjectId(Long objectId) { + return client.proxyObjectId(id, objectId); + } + + public RmiTraceObject proxyObjectPath(String path) { + return client.proxyObjectPath(id, path); + } + + public RmiTraceObject proxyObjectPath(Long objectId, String path) { + return client.proxyObjectPath(id, objectId, path); + } + + public int getId() { + return id; + } + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiTraceObject.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiTraceObject.java new file mode 100644 index 0000000000..cd9b247c48 --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiTraceObject.java @@ -0,0 +1,79 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import java.util.Set; + +import ghidra.rmi.trace.TraceRmi.*; +import ghidra.trace.model.Lifespan; + +public class RmiTraceObject { + + private RmiTrace trace; + private ObjSpec spec; + private String path; + + public RmiTraceObject(RmiTrace trace, ObjSpec spec) { + this.trace = trace; + this.spec = spec; + this.path = spec.getPath().getPath(); + } + + public RmiTraceObject(RmiTrace trace, Long id, String path) { + this.trace = trace; + this.path = path; + } + + public static RmiTraceObject fromId(RmiTrace trace, long id) { + return new RmiTraceObject(trace, id, null); + } + + public static RmiTraceObject fromPath(RmiTrace trace, String path) { + return new RmiTraceObject(trace, null, path); + } + + public void insert(long snap, Resolution resolution) { + if (resolution == null) { + resolution = Resolution.CR_ADJUST; + } + Lifespan span = Lifespan.nowOn(snap); + trace.client.insertObject(trace.getId(), spec, span, resolution); + } + + public void remove(long snap, boolean tree) { + Lifespan span = Lifespan.nowOn(snap); + trace.client.removeObject(trace.getId(), spec, span, tree); + } + + public void setValue(String key, Object value, long snap, String resolution) { + Lifespan span = Lifespan.nowOn(snap); + trace.client.setValue(trace.getId(), path, span, key, value, resolution); + } + + public void retainValues(Set keys, long snap, ValueKinds kinds) { + Lifespan span = Lifespan.nowOn(snap); + trace.client.retainValues(trace.getId(), path, span, kinds, keys); + } + + public void activate() { + trace.client.activate(trace.getId(), path); + } + + public String getPath() { + return path; + } + +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiTransaction.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiTransaction.java new file mode 100644 index 0000000000..4bb6075ab2 --- /dev/null +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiTransaction.java @@ -0,0 +1,64 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.client.tracermi; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import ghidra.util.LockHold; + +public class RmiTransaction implements AutoCloseable { + + private RmiTrace trace; + private int id; + private ReadWriteLock lock = new ReentrantReadWriteLock(); + private boolean closed = false; + + public RmiTransaction(RmiTrace trace, int id) { + this.trace = trace; + this.id = id; + } + + public void commit() { + try (LockHold hold = LockHold.lock(lock.writeLock())) { + if (closed) { + return; + } + closed = true; + } + trace.endTx(id, false); + } + + public void abort() { + try (LockHold hold = LockHold.lock(lock.writeLock())) { + if (closed) { + return; + } + closed = true; + } + trace.endTx(id, true); + } + + @Override + public void close() { + commit(); + } + + public RmiTransaction startTx(String description, boolean b) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/LaunchFailureDialog.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/LaunchFailureDialog.java index bb36376626..abeeba6287 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/LaunchFailureDialog.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/LaunchFailureDialog.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -94,6 +94,13 @@ public class LaunchFailureDialog extends OptionDialog { result.trace() != null; } + protected static String shorten(String title) { + if (title.length() > 80) { + return title.substring(0, 77) + "..."; + } + return title; + } + protected static String htmlContent(TerminalSession session) { String content = session.content().trim(); List lines = Arrays.asList(content.split("\n")); @@ -108,7 +115,7 @@ public class LaunchFailureDialog extends OptionDialog {
Title: %s
%s
%s
""".formatted( - session.title(), + HTMLUtilities.escapeHTML(shorten(session.title())), note, content); } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java index 5bb0ab1133..f074f847de 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java @@ -244,6 +244,9 @@ public class TraceRmiHandler implements TraceRmiConnection { this.plugin = plugin; plugin.addHandler(this); this.socket = socket; + if (socket == null) { + throw new RuntimeException("Socket cannot be null"); + } this.in = socket.getInputStream(); this.out = socket.getOutputStream(); diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java index 673c3a444b..b39121f04d 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java @@ -391,8 +391,9 @@ public class TraceRmiTarget extends AbstractTarget { boolean allowSuitableObject) { Map result = new HashMap<>(); for (RemoteMethod m : methods) { - result.put(m.name(), createEntry(m, context, allowContextObject, allowCoordsObject, - allowSuitableObject)); + ActionEntry entry = createEntry(m, context, allowContextObject, allowCoordsObject, + allowSuitableObject); + result.put(m.name(), entry); } return result; }