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 super JdiModelTargetThread> 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.
+ *
+ *
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.:
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;
}