diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/UnixShellScriptTraceRmiLaunchOffer.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/UnixShellScriptTraceRmiLaunchOffer.java index bb9a379cd0..052b3d9dfa 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/UnixShellScriptTraceRmiLaunchOffer.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/UnixShellScriptTraceRmiLaunchOffer.java @@ -37,10 +37,13 @@ public class UnixShellScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLa /** * 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. - * @throws FileNotFoundException + * @param script the script file that implements this offer + * @return the offer + * @throws FileNotFoundException if the script file does not exist */ public static UnixShellScriptTraceRmiLaunchOffer create(TraceRmiLauncherServicePlugin plugin, Program program, File script) throws FileNotFoundException { 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 52c9f9ae63..a3c3b92877 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 @@ -225,6 +225,7 @@ public class TraceRmiHandler implements TraceRmiConnection { *
* Note it is common for this to be constructed by a TCP client.
*
+ * @param plugin the Trace RMI plugin
* @param socket the socket to the back-end debugger
* @throws IOException if there is an issue with the I/O streams
*/
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java
index fbcafb5079..6c654c0789 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java
@@ -513,7 +513,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
return null;
}
}
-
+
protected static State computeState(LogicalBreakpoint breakpoint, Program programOrView) {
if (programOrView instanceof TraceProgramView view) {
return breakpoint.computeStateForTrace(view.getTrace());
@@ -809,7 +809,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
BreakpointsDecompilerMarginProvider decompilerMarginProvider;
private MarginProviderSupplier functionGraphMarginSupplier =
- new DefaultMarginProviderSupplier();
+ new DefaultMarginProviderSupplier();
public DebuggerBreakpointMarkerPlugin(PluginTool tool) {
super(tool);
@@ -973,7 +973,9 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
protected void removeMarkers(Program program) {
synchronized (markersByProgram) {
BreakpointMarkerSets oldSets = markersByProgram.remove(program);
- oldSets.dispose();
+ if (oldSets != null) {
+ oldSets.dispose();
+ }
}
}
@@ -1069,7 +1071,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
this.functionGraphMarginService.addMarkerProviderSupplier(functionGraphMarginSupplier);
}
}
-
+
protected void createActions() {
actionSetSoftwareBreakpoint =
new SetBreakpointAction(Set.of(TraceBreakpointKind.SW_EXECUTE));
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java
index 5a71334840..d5b14e6a51 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java
@@ -434,8 +434,8 @@ public class DebuggerListingProvider extends CodeViewerProvider {
* is synchronized exactly with the other providers. "Main" on the other hand, does not
* necessarily have that property, but it is still not a snapshot. It is the main
* listing presented by this plugin, and so it has certain unique features. Calling
- * {@link DebuggerListingPlugin#getConnectedProvider()} will return the main dynamic listing,
- * despite it not really being "connected."
+ * {@link DebuggerListingPlugin#getProvider()} will return the main dynamic listing, despite it
+ * not really being "connected."
*
* @return true if this is the main listing for the plugin.
*/
@@ -649,8 +649,12 @@ public class DebuggerListingProvider extends CodeViewerProvider {
@Override
protected void doSetProgram(Program newProgram) {
+ // E.g., The "Navigate Previous" could cause a change in trace
if (newProgram != null && current.getView() != null && newProgram != current.getView()) {
- throw new AssertionError();
+ if (!(newProgram instanceof TraceProgramView view)) {
+ throw new IllegalArgumentException("Dynamic Listings require trace views");
+ }
+ traceManager.activateTrace(view.getTrace());
}
if (getProgram() == newProgram) {
return;
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java
index d1ecc34dfb..a9c12df03b 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java
@@ -426,6 +426,9 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
current = coordinates;
addNewListeners();
doSetProgram(current.getView());
+ // NB. Also avoid a stale location being reported to the history service.
+ setLocation(null);
+ setSelection(null, false);
goToTrait.goToCoordinates(coordinates);
trackingTrait.goToCoordinates(coordinates);
readsMemTrait.goToCoordinates(coordinates);
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java
index d936c3cf24..ed97bd8ef5 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java
@@ -75,7 +75,10 @@ import ghidra.util.task.*;
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsProduced = {
+ TraceOpenedPluginEvent.class,
TraceActivatedPluginEvent.class,
+ TraceInactiveCoordinatesPluginEvent.class,
+ TraceClosedPluginEvent.class,
},
eventsConsumed = {
TraceActivatedPluginEvent.class,
@@ -284,6 +287,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
private DebuggerPlatformService platformService;
// @AutoServiceConsumed via method
private DebuggerControlService controlService;
+ @AutoServiceConsumed
+ private NavigationHistoryService navigationHistoryService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@@ -959,6 +964,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
protected void doTraceClosed(Trace trace) {
+ if (navigationHistoryService != null) {
+ navigationHistoryService.clear(trace.getProgramView());
+ }
synchronized (listenersByTrace) {
trace.release(this);
lastCoordsByTrace.remove(trace);
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NavigationHistoryPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NavigationHistoryPlugin.java
index e35c40c081..8d38294c49 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NavigationHistoryPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NavigationHistoryPlugin.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.
@@ -348,7 +348,8 @@ public class NavigationHistoryPlugin extends Plugin
notifyHistoryChange();
}
- private void clear(Program program) {
+ @Override
+ public void clear(Program program) {
for (HistoryList historyList : historyListMap.values()) {
clear(historyList, program);
}
@@ -657,7 +658,7 @@ public class NavigationHistoryPlugin extends Plugin
ProgramLocation location =
validateProgramLocation(activeNavigatable, memento.getProgramLocation());
if (location != null) {
- // prefer the active natigatable's location
+ // prefer the active navigatable's location
return location;
}
}
@@ -683,11 +684,13 @@ public class NavigationHistoryPlugin extends Plugin
Program program = location.getProgram();
if (program.isClosed()) {
- Msg.showError(this, null, "Closed Program",
- "The Navigation History Plugin is using a closed program.\n" + "Program: " +
- program.getName() + "Navigatable: " +
- navigatable.getClass().getSimpleName() + "\n" + "Location: " +
- location.getClass().getSimpleName() + " @ " + location.getAddress());
+ Msg.showError(this, null, "Closed Program", """
+ The Navigation History Plugin is using a closed program.
+ Program: %s
+ Navigatable: %s
+ Location: %s @ %s"""
+ .formatted(program.getName(), navigatable.getClass().getSimpleName(),
+ location.getClass().getSimpleName(), location.getAddress()));
return true;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/NavigationHistoryService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/NavigationHistoryService.java
index 969c93af07..d25c072f6c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/NavigationHistoryService.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/NavigationHistoryService.java
@@ -21,65 +21,71 @@ import ghidra.app.nav.LocationMemento;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.navigation.NavigationHistoryPlugin;
import ghidra.framework.plugintool.ServiceInfo;
+import ghidra.program.model.listing.Program;
/**
- * The ToolStateHistoryService maintains a stack of locations that the user
- * has visited via a navigation plugin.
- * It provides methods querying and manipulating this list.
+ * The NavigationHistoryService maintains a stack of locations that the user has visited via a
+ * navigation plugin. It provides methods querying and manipulating this list.
*/
-@ServiceInfo(defaultProvider = NavigationHistoryPlugin.class, description = "Maintains a history of tool states")
+@ServiceInfo(
+ defaultProvider = NavigationHistoryPlugin.class,
+ description = "Maintains a history of tool states")
public interface NavigationHistoryService {
/**
- * Positions the current location to the next location in the history list.
- * If there is no "next" location, the history list remains unchanged.
+ * Positions the current location to the next location in the history list. If there is no
+ * "next" location, the history list remains unchanged.
+ *
* @param navigatable the navigatable to be navigated
*/
public void next(Navigatable navigatable);
/**
- * Positions the "current" location to the previous location in the history list.
- * If there is no "previous" location, the history list remains unchanged.
+ * Positions the "current" location to the previous location in the history list. If there is no
+ * "previous" location, the history list remains unchanged.
+ *
* @param navigatable the navigatable to be navigated
*/
public void previous(Navigatable navigatable);
- /**
- * Navigates to the given location in the "next" list. If the location is not in the list, then
+ /**
+ * Navigates to the given location in the "next" list. If the location is not in the list, then
* nothing will happen.
*
* @param navigatable the navigatable to be navigated
- * @param location The location within the "next" list to which to go
+ * @param location The location within the "next" list to which to go
*/
public void next(Navigatable navigatable, LocationMemento location);
- /**
- * Navigates to the given location in the "previous" list. If the location is not in
- * the list, then nothing will happen
+ /**
+ * Navigates to the given location in the "previous" list. If the location is not in the list,
+ * then nothing will happen
*
* @param navigatable the navigatable to be navigated
- * @param location The location within the "previous" list to which to go.
+ * @param location The location within the "previous" list to which to go.
*/
public void previous(Navigatable navigatable, LocationMemento location);
/**
- * Positions the "current" location to the next location which is in a different function
- * from current one or previous non-code location.
- * If we are not inside any function, performs like "next".
+ * Positions the "current" location to the next location which is in a different function from
+ * current one or previous non-code location. If we are not inside any function, performs like
+ * "next".
+ *
* @param navigatable the navigatable to be navigated
*/
public void nextFunction(Navigatable navigatable);
/**
- * Positions the "previous" location to the next location which is in a different function
- * from current one or previous non-code location.
- * If we are not inside any function, performs like "next".
+ * Positions the "previous" location to the next location which is in a different function from
+ * current one or previous non-code location. If we are not inside any function, performs like
+ * "next".
+ *
* @param navigatable the navigatable to be navigated
*/
public void previousFunction(Navigatable navigatable);
/**
- * Returns the LocationMemento objects in the "previous" list
+ * Returns the {@link LocationMemento} objects in the "previous" list
*
* @param navigatable the navigatable to be navigated
* @return the LocationMemento objects in the "previous" list
@@ -87,7 +93,7 @@ public interface NavigationHistoryService {
public List Note this returns the the original passed-in actions and not the proxy actions that the
+ *
+ * Note this returns the the original passed-in actions and not the proxy actions that the
* window uses.
*
* @return the set of actions for this window
diff --git a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ReentryGuard.java b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ReentryGuard.java
new file mode 100644
index 0000000000..b301ef1fd8
--- /dev/null
+++ b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ReentryGuard.java
@@ -0,0 +1,185 @@
+/* ###
+ * 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 generic.concurrent;
+
+/**
+ * A means of detecting and handling reentrant conditions.
+ *
+ *
+ * One example where this has been applied deals with updating actions upon changes in context. If,
+ * in the course of determining which actions are enabled, one of the {@code isEnabled} methods
+ * displays an error dialog, the Swing thread reenters its main loop while that dialog is showing,
+ * but before {@code isEnabled} has returned. This can cause all sorts of unexpected behaviors.
+ * Namely, a timer could fire, context could change again, etc., and the list of actions being
+ * updated may also change. At worst, this could result in many exceptions being thrown, because a
+ * data structure has been modified concurrently. At best, if the loop is allowed to finish, there's
+ * a lot of wasted time updating actions that will never be displayed.
+ *
+ *
+ * In that example, the loop that updates the actions would be the "guarded block." Any point at
+ * which the list of actions is modified might result in "reentrant access" and should be checked.
+ *
+ *
+ * This class provides a primitive for instrumenting, detecting, and properly reacting to such
+ * conditions. For example, if the modification should not be allowed at all, the guard can throw an
+ * exception at the moment of reentrant access. Alternatively, if the modification should be
+ * allowed, the guard would simply set a flag, then the guarded block can check that flag and
+ * terminate early.
+ *
+ *
+ * This implementation is not thread safe. It is designed to check for reentrant access,
+ * not concurrent access. The client must ensure that only one thread enters the guarded block or
+ * calls {@link #checkAccess()} at a time. Otherwise, the behavior is undefined.
+ *
+ *
+ * This should always be used in a {@code try-with-resources} block. This will ensure that the
+ * guard is notified of exit from the guarded block, even in exceptional circumstances.
+ *
+ *
+ * NOTE: Re-entering the guarded portion is itself a violation.
+ *
+ * @return a closeable for notifying the guard of exit from the guarded block, or null if
+ * reentering the guarded block
+ */
+ public Guarded enter() {
+ if (inGuarded) {
+ violation = violated(true, violation);
+ return null;
+ }
+ inGuarded = true;
+ violation = null;
+ return guarded;
+ }
+
+ /**
+ * Notify the guard of access to some resource used by the guarded block
+ *
+ *
+ * If the access turns out to be reentrant, i.e., the thread's current stack includes a frame in
+ * the guarded block, this will call {@link #violated(boolean, Object)} and record the result.
+ * It can be inspected later via {@link #getViolation()}.
+ */
+ public void checkAccess() {
+ if (inGuarded) {
+ violation = violated(false, violation);
+ }
+ }
+
+ /**
+ * Record a violation
+ *
+ *
+ * This method is called if {@link #checkAccess()} or {@link #enter()} is called while already
+ * inside a guarded block. Its return value is stored for later inspection by
+ * {@link #getViolation()}. It's possible multiple violations occur within one execution of the
+ * guarded block. The previous return value of this method is provided, if that is the case. To
+ * record only the first violation, this method should just {@code return previous} when it is
+ * non-null. To record only the last violation, this method should disregard {@code previous}.
+ * To record all violations, {@code T} will need to be a collection, and this method will need
+ * to create and/or append to the collection.
+ *
+ * @param nested true if the violation is a nested call to {@link #enter()}; false if the
+ * violation is a call to {@link #checkAccess()}
+ * @param previous the previous return value of this method, on the occasion of multiple
+ * violations
+ * @return the record of the violation
+ */
+ protected abstract T violated(boolean nested, T previous);
+
+ /**
+ * Retrieve a violation, if applicable
+ *
+ *
+ * Calling this method outside of a guarded block has undefined behavior.
+ *
+ * @return the violation; or null to indicate no violation
+ */
+ public T getViolation() {
+ return violation;
+ }
+
+ /**
+ * Check if there is a violation
+ *
+ *
+ * This is equivalent to checking if {@link #getViolation()} returns non-null.
+ *
+ * @return true if there is a violation.
+ */
+ public boolean isViolated() {
+ return violation != null;
+ }
+}
+ * public class ActionManager {
+ * private final ReentryGuard<Throwable> reentryGuard = new ReentryGuard<>() {
+ * @Override
+ * public Throwable violated(boolean nested, Throwable previous) {
+ * if (previous != null) {
+ * return previous;
+ * }
+ * return new Throwable(); // record the stack of the violation
+ * }
+ * };
+ * private final List<Action> actions;
+ *
+ * public void addAction(Action action) {
+ * // Notify the guard we've committed some reentrant behavior.
+ * // Would need to add this to anything that modifies the action list.
+ * reentryGuard.checkAccess();
+ * actions.add(action);
+ * }
+ *
+ * public void updateActions(Context ctx) {
+ * try (Guarded guarded = reentryGuard.enter()) {
+ * // There is no need to create a copy, since we'll bail before the next iteration
+ * for (Action action : actions) {
+ * boolean enabled = action.isEnabledForContext(ctx);
+ * if (reentryGuard.getViolation() != null) {
+ * break; // Actions has been modified. Bail.
+ * // NOTE: This leaves the update incomplete.
+ * // Something has to call updateActions again.
+ * }
+ * actions.setEnabled(enabled);
+ * }
+ * }
+ * }
+ * }
+ *
+ *
+ * @param