Merge remote-tracking branch 'origin/GP-2581_Dan_followWatch--SQUASHED'

This commit is contained in:
Ryan Kurtz 2022-10-04 01:46:58 -04:00
commit de9ec734ae
60 changed files with 1632 additions and 552 deletions

View file

@ -7,4 +7,4 @@ DebuggerPlatformOpinion
DebuggerProgramLaunchOpinion
DebuggerRegisterColumnFactory
DisassemblyInject
LocationTrackingSpec
LocationTrackingSpecFactory

View file

@ -110,8 +110,8 @@
<LI>Track Program Counter (by stack) - navigates this listing to the current program counter
as recorded from the debugger's stack trace. This option is not recommended for regular use,
especially for emulation, since the emulator does not provide stack trace records. It is
recommended for debugger connection developers to verify the stack records are being property
especially for emulation, since the emulator does not provide stack trace records. Its use is
recommended for debugger connection developers to verify the stack records are being properly
interpreted by the GUI.</LI>
<LI>Track Program Counter (by register) - navigates this listing to the current program
@ -119,10 +119,14 @@
Program Counter" option is recommended, since the stack may record the program counter in
some cases where the registers do not. For example, when unwinding a stack frame, the
debugger may report the frame's program counter without reporting the frame's registers. This
option may be desired if/when stack frames are recorded incorrectly.</LI>
option may be desired if stack frames are recorded incorrectly.</LI>
<LI>Track Stack Pointer - navigates this listing to the current stack pointer. Again,
<LI>Track Stack Pointer - navigates this listing to the current stack pointer. Note that
platforms may vary in how they define the stack pointer.</LI>
<LI>Track address of watch - navigates this listing to the address of the expression's value.
This option is replicated for each watch in the <A href=
"help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html">Watches</A> window.</LI>
</UL>
<H3><A name="go_to"></A>Go To (G)</H3>

View file

@ -73,38 +73,9 @@
<H3><A name="track_location"></A>Track Location</H3>
<P>This action is always available on all memory windows. It configures automatic navigation
for the window. When location tracking is enabled, the window is automatically navigated to an
address computed from the trace's or target's machine state. <B>NOTE:</B> This feature is
disabled when the edit toggle is on. The address is also highlighted in green. The computed
address is affected by the tool's current "coordinates," that is the selected thread, frame,
and point in time. The options are pluggable, but currently consist of:</P>
<UL>
<LI>Do Not Track - disables automatic navigation.</LI>
<LI>Track Program Counter - (default) navigates this listing to the current program counter.
If the stack contains a record of the program counter, it is preferred to the recorded
register value. Note that debuggers may vary in how they report the program counter, and its
meaning may vary among platforms. While there may be some nuances to the register value, the
value recorded in the stack should indicate the next instruction to be executed.</LI>
<LI>Track Program Counter (by stack) - navigates this listing to the current program counter
as recorded from the debugger's stack trace. This option is not recommended for regular use,
especially for emulation, since the emulator does not provide stack trace records. It is
recommended for debugger connection developers to verify the stack records are being property
interpreted by the GUI.</LI>
<LI>Track Program Counter (by register) - navigates this listing to the current program
counter as recorded from the registers. While suitable for regular use, the default "Track
Program Counter" option is recommended, since the stack may record the program counter in
some cases where the registers do not. For example, when unwinding a stack frame, the
debugger may report the frame's program counter without reporting the frame's registers. This
option may be desired if/when stack frames are recorded incorrectly.</LI>
<LI>Track Stack Pointer - navigates this listing to the current stack pointer. Again,
platforms may vary in how they define the stack pointer.</LI>
</UL>
<P>This action is equivalent to the same action in the <A href=
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html#track_location">Dynamic
Listing</A> window. <B>NOTE:</B> This feature is disabled when the edit toggle is on.</P>
<H3><A name="go_to"></A>Go To (G)</H3>

View file

@ -863,6 +863,7 @@ public interface DebuggerResources {
String NAME_PC_BY_STACK = "Track Program Counter (by Stack)";
String NAME_SP = "Track Stack Pointer";
String NAME_NONE = "Do Not Track";
String NAME_PREFIX_WATCH = "Track address of watch: ";
// TODO: Separate icons for Program Counter and Stack Pointer
Icon ICON_PC = ICON_REGISTER_MARKER;

View file

@ -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.gui.action;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import ghidra.framework.plugintool.PluginTool;
/**
* The factory for the basic location tracking specs: NONE, PC, SP
*/
public class BasicLocationTrackingSpecFactory implements LocationTrackingSpecFactory {
public static final List<LocationTrackingSpec> ALL = List.of(
NoneLocationTrackingSpec.INSTANCE,
PCLocationTrackingSpec.INSTANCE,
PCByRegisterLocationTrackingSpec.INSTANCE,
PCByStackLocationTrackingSpec.INSTANCE,
SPLocationTrackingSpec.INSTANCE);
public static final Map<String, LocationTrackingSpec> BY_CONFIG_NAME = ALL.stream()
.collect(Collectors.toUnmodifiableMap(
LocationTrackingSpec::getConfigName,
Function.identity()));
@Override
public List<LocationTrackingSpec> getSuggested(PluginTool tool) {
return ALL;
}
@Override
public LocationTrackingSpec parseSpec(String name) {
return BY_CONFIG_NAME.get(name);
}
}

View file

@ -21,14 +21,10 @@ import ghidra.framework.plugintool.Plugin;
public interface DebuggerTrackLocationAction extends TrackLocationAction {
// TODO: Update the action when new specs enter the class path?
static MultiStateActionBuilder<LocationTrackingSpec> builder(Plugin owner) {
MultiStateActionBuilder<LocationTrackingSpec> builder = TrackLocationAction.builder(owner);
builder.toolBarGroup(owner.getName());
builder.useCheckboxForIcons(true);
for (LocationTrackingSpec spec : LocationTrackingSpec.allSpecs().values()) {
builder.addState(spec.getMenuName(), spec.getMenuIcon(), spec);
}
return builder;
}
}

View file

@ -18,8 +18,8 @@ package ghidra.app.plugin.core.debug.gui.action;
import java.awt.Color;
import java.lang.invoke.MethodHandles;
import java.math.BigInteger;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import docking.ActionContext;
import docking.ComponentProvider;
@ -36,12 +36,12 @@ import ghidra.app.plugin.core.debug.gui.colors.MultiSelectionBlendedLayoutBackgr
import ghidra.app.plugin.core.debug.gui.listing.DebuggerTrackedRegisterListingBackgroundColorModel;
import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.AsyncUtils;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.SaveState;
import ghidra.framework.options.annotation.AutoOptionConsumed;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
@ -49,6 +49,7 @@ import ghidra.trace.model.Trace.TraceStackChangeType;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.Msg;
public class DebuggerTrackLocationTrait {
protected static final AutoConfigState.ClassHandler<DebuggerTrackLocationTrait> CONFIG_STATE_HANDLER =
@ -67,7 +68,7 @@ public class DebuggerTrackLocationTrait {
// Should only happen during transitional times, if at all.
return;
}
if (!spec.affectedByRegisterChange(space, range, current)) {
if (!tracker.affectedByBytesChange(space, range, current)) {
return;
}
doTrack();
@ -78,7 +79,7 @@ public class DebuggerTrackLocationTrait {
// Should only happen during transitional times, if at all.
return;
}
if (!spec.affectedByStackChange(stack, current)) {
if (!tracker.affectedByStackChange(stack, current)) {
return;
}
doTrack();
@ -134,11 +135,11 @@ public class DebuggerTrackLocationTrait {
protected MultiStateDockingAction<LocationTrackingSpec> action;
private final LocationTrackingSpec defaultSpec =
LocationTrackingSpec.fromConfigName(PCLocationTrackingSpec.CONFIG_NAME);
private final LocationTrackingSpec defaultSpec = PCLocationTrackingSpec.INSTANCE;
@AutoConfigStateField(codec = TrackingSpecConfigFieldCodec.class)
protected LocationTrackingSpec spec = defaultSpec;
protected LocationTracker tracker = spec.getTracker();
protected final PluginTool tool;
protected final Plugin plugin;
@ -209,10 +210,9 @@ public class DebuggerTrackLocationTrait {
}
public MultiStateDockingAction<LocationTrackingSpec> installAction() {
// TODO: Add "other" option, and present most-recent in menu, too
// TODO: "other" as in arbitrary expression?
// Only those applicable to the current thread's registers, though.
// TODO: Only those Sleigh expressions applicable to the current thread's registers?
action = DebuggerTrackLocationAction.builder(plugin)
.stateGenerator(this::getStates)
.onAction(this::clickedSpecButton)
.onActionStateChanged(this::clickedSpecMenu)
.buildAndInstallLocal(provider);
@ -220,6 +220,21 @@ public class DebuggerTrackLocationTrait {
return action;
}
public List<ActionState<LocationTrackingSpec>> getStates() {
Map<String, ActionState<LocationTrackingSpec>> states = new TreeMap<>();
for (LocationTrackingSpec spec : LocationTrackingSpecFactory
.allSuggested(tool)
.values()) {
states.put(spec.getConfigName(),
new ActionState<>(spec.getMenuName(), spec.getMenuIcon(), spec));
}
ActionState<LocationTrackingSpec> current = action.getCurrentState();
if (current != null) {
states.put(current.getUserData().getConfigName(), current);
}
return List.copyOf(states.values());
}
protected void clickedSpecButton(ActionContext ctx) {
doTrack();
}
@ -232,12 +247,13 @@ public class DebuggerTrackLocationTrait {
protected void doSetSpec(LocationTrackingSpec spec) {
if (this.spec != spec) {
this.spec = spec;
this.tracker = spec.getTracker();
specChanged(spec);
}
doTrack();
}
protected ProgramLocation computeTrackedLocation() {
protected CompletableFuture<ProgramLocation> computeTrackedLocation() {
// Change of register values (for current frame)
// Change of stack pc (for current frame)
// Change of current view (if not caused by goTo)
@ -248,16 +264,22 @@ public class DebuggerTrackLocationTrait {
DebuggerCoordinates cur = current;
TraceThread thread = cur.getThread();
if (thread == null || spec == null) {
return null;
return AsyncUtils.nil();
}
// NB: view's snap may be forked for emulation
Address address = spec.computeTraceAddress(tool, cur);
return address == null ? null : new ProgramLocation(cur.getView(), address);
return tracker.computeTraceAddress(tool, cur).thenApply(address -> {
return address == null ? null : new ProgramLocation(cur.getView(), address);
});
}
protected void doTrack() {
trackedLocation = computeTrackedLocation();
locationTracked();
computeTrackedLocation().thenAccept(loc -> {
trackedLocation = loc;
locationTracked();
}).exceptionally(ex -> {
Msg.error(this, "Error while computing location: " + ex);
return null;
});
}
protected void addNewListeners() {
@ -296,7 +318,7 @@ public class DebuggerTrackLocationTrait {
public void readConfigState(SaveState saveState) {
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
tracker = spec.getTracker();
action.setCurrentActionStateByUserData(spec);
}

View file

@ -0,0 +1,78 @@
/* ###
* 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.gui.action;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.util.TraceAddressSpace;
/**
* The actual tracking logic for a location tracking spec
*
* <p>
* In simple cases, the spec can implement this interface and return itself in
* {@link LocationTrackingSpec#getTracker()}. If the tracker needs some state, then the spec should
* create a separate tracker.
*/
public interface LocationTracker {
/**
* Compute the trace address to "goto"
*
* <p>
* If the coordinates indicate emulation, i.e., the schedule is non-empty, the trace manager
* will already have performed the emulation and stored the results in a "scratch" snap. In
* general, the location should be computed using that snap, i.e.,
* {@link DebuggerCoordinates#getViewSnap()} rather than {@link DebuggerCoordinates#getSnap()}.
* The address returned must be in the host platform's language, i.e., please use
* {@link TracePlatform#mapGuestToHost(Address)}.
*
* @param tool the tool containing the provider
* @param coordinates the trace, thread, snap, etc., of the tool
* @return the address to navigate to
*/
CompletableFuture<Address> computeTraceAddress(PluginTool tool,
DebuggerCoordinates coordinates);
// TODO: Is there a way to generalize these so that other dependencies need not
// have their own bespoke methods?
/**
* Check if the address should be recomputed given the indicated value change
*
* @param space the space (address space, thread, frame) where the change occurred
* @param range the range (time and space) where the change occurred
* @param coordinates the provider's current coordinates
* @return true if re-computation and "goto" is warranted
*/
boolean affectedByBytesChange(TraceAddressSpace space,
TraceAddressSnapRange range, DebuggerCoordinates coordinates);
/**
* Check if the address should be recomputed given the indicated stack change
*
* @param stack the stack that changed (usually it's PC / return offset)
* @param coordinates the provider's current coordinates
* @return true if re-computation and "goto" is warranted
*/
boolean affectedByStackChange(TraceStack stack, DebuggerCoordinates coordinates);
}

View file

@ -15,26 +15,14 @@
*/
package ghidra.app.plugin.core.debug.gui.action;
import java.util.Map;
import java.util.TreeMap;
import javax.swing.Icon;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.AutoConfigState.ConfigFieldCodec;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.classfinder.ExtensionPoint;
/**
* A specification for automatic navigation of the dynamic listing
@ -45,32 +33,18 @@ import ghidra.util.classfinder.ExtensionPoint;
* want 3 different common expressions readily available in the drop-down list. It might make sense
* to generate a tracking specification from each Watch.
*/
public interface LocationTrackingSpec extends ExtensionPoint {
enum Private {
INSTANCE;
private final Map<String, LocationTrackingSpec> specsByName = new TreeMap<>();
private final ChangeListener classListener = this::classesChanged;
private Private() {
ClassSearcher.addChangeListener(classListener);
}
private synchronized void classesChanged(ChangeEvent evt) {
MiscellaneousUtils.collectUniqueInstances(LocationTrackingSpec.class, specsByName,
LocationTrackingSpec::getConfigName);
}
}
Private PRIVATE = Private.INSTANCE;
public interface LocationTrackingSpec {
/**
* Codec for saving/restoring the tracking specification
*/
public static class TrackingSpecConfigFieldCodec
implements ConfigFieldCodec<LocationTrackingSpec> {
@Override
public LocationTrackingSpec read(SaveState state, String name,
LocationTrackingSpec current) {
String specName = state.getString(name, null);
return fromConfigName(specName);
return LocationTrackingSpecFactory.fromConfigName(specName);
}
@Override
@ -112,29 +86,6 @@ public interface LocationTrackingSpec extends ExtensionPoint {
return true;
}
/**
* Get the specification for the given configuration name
*
* @param name the name
* @return the spec, or null
*/
static LocationTrackingSpec fromConfigName(String name) {
synchronized (PRIVATE) {
return PRIVATE.specsByName.get(name);
}
}
/**
* Get a copy of all the known specifications
*
* @return the specifications by configuration name
*/
static Map<String, LocationTrackingSpec> allSpecs() {
synchronized (PRIVATE) {
return new TreeMap<>(PRIVATE.specsByName);
}
}
/**
* Get the configuration name
*
@ -171,42 +122,13 @@ public interface LocationTrackingSpec extends ExtensionPoint {
String computeTitle(DebuggerCoordinates coordinates);
/**
* Compute the trace address to "goto"
* Get (or create) the actual location tracking logic
*
* <p>
* If the coordinates indicate emulation, i.e., the schedule is non-empty, the trace manager
* will already have performed the emulation and stored the results in a "scratch" snap. In
* general, the location should be computed using that snap, i.e.,
* {@link DebuggerCoordinates#getViewSnap()} rather than {@link DebuggerCoordinates#getSnap()}.
* The address returned must be in the host platform's language, i.e., please use
* {@link TracePlatform#mapGuestToHost(Address)}.
* Having a separate object from the spec gives implementations the option of keeping state on a
* per-window basis.
*
* @param tool the tool containing the provider
* @param coordinates the trace, thread, snap, etc., of the tool
* @return the address to navigate to
* @return the tracker
*/
Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates);
// TODO: Is there a way to generalize these so that other dependencies need not
// have their own bespoke methods?
/**
* Check if the address should be recomputed given the indicated register value change
*
* @param space the space (address space, thread, frame) where the change occurred
* @param range the range (time and space) where the change occurred
* @param coordinates the provider's current coordinates
* @return true if re-computation and "goto" is warranted
*/
boolean affectedByRegisterChange(TraceAddressSpace space,
TraceAddressSnapRange range, DebuggerCoordinates coordinates);
/**
* Check if the address should be recomputed given the indicated stack change
*
* @param stack the stack that changed (usually it's PC / return offset)
* @param coordinates the provider's current coordinates
* @return true if re-computation and "goto" is warranted
*/
boolean affectedByStackChange(TraceStack stack, DebuggerCoordinates coordinates);
LocationTracker getTracker();
}

View file

@ -0,0 +1,77 @@
/* ###
* 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.gui.action;
import java.util.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.classfinder.ExtensionPoint;
/**
* A discoverable factory of tracking specifications
*/
public interface LocationTrackingSpecFactory extends ExtensionPoint {
/**
* Get the specification for the given configuration name
*
* @param name the name
* @return the spec, or null
*/
static LocationTrackingSpec fromConfigName(String name) {
for (LocationTrackingSpecFactory factory : ClassSearcher
.getInstances(LocationTrackingSpecFactory.class)) {
LocationTrackingSpec spec = factory.parseSpec(name);
if (spec != null) {
return spec;
}
}
return null;
}
/**
* Get a copy of all the known specifications
*
* @return the specifications by configuration name
*/
static Map<String, LocationTrackingSpec> allSuggested(PluginTool tool) {
Map<String, LocationTrackingSpec> all = new TreeMap<>();
for (LocationTrackingSpecFactory factory : ClassSearcher
.getInstances(LocationTrackingSpecFactory.class)) {
for (LocationTrackingSpec spec : factory.getSuggested(tool)) {
all.put(spec.getConfigName(), spec);
}
}
return all;
}
/**
* Get all the specifications currently suggested by this factory
*
* @param tool the plugin tool or context
* @return the list of suggested specifications
*/
List<LocationTrackingSpec> getSuggested(PluginTool tool);
/**
* Attempt to parse the given configuration name as as specification
*
* @param name the configuration name, usually including a prefix unique to each factory
* @return the specification, or null if this factory cannot parse it
*/
LocationTrackingSpec parseSpec(String name);
}

View file

@ -15,17 +15,22 @@
*/
package ghidra.app.plugin.core.debug.gui.action;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
import ghidra.async.AsyncUtils;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.util.TraceAddressSpace;
public class NoneLocationTrackingSpec implements LocationTrackingSpec {
public enum NoneLocationTrackingSpec implements LocationTrackingSpec, LocationTracker {
INSTANCE;
public static final String CONFIG_NAME = "TRACK_NONE";
@Override
@ -49,12 +54,18 @@ public class NoneLocationTrackingSpec implements LocationTrackingSpec {
}
@Override
public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
return null;
public LocationTracker getTracker() {
return this;
}
@Override
public boolean affectedByRegisterChange(TraceAddressSpace space,
public CompletableFuture<Address> computeTraceAddress(PluginTool tool,
DebuggerCoordinates coordinates) {
return AsyncUtils.nil();
}
@Override
public boolean affectedByBytesChange(TraceAddressSpace space,
TraceAddressSnapRange range, DebuggerCoordinates coordinates) {
return false;
}

View file

@ -23,7 +23,9 @@ import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Register;
import ghidra.trace.model.guest.TracePlatform;
public class PCByRegisterLocationTrackingSpec implements RegisterLocationTrackingSpec {
public enum PCByRegisterLocationTrackingSpec implements RegisterLocationTrackingSpec {
INSTANCE;
public static final String CONFIG_NAME = "TRACK_PC_BY_REGISTER";
@Override

View file

@ -15,6 +15,8 @@
*/
package ghidra.app.plugin.core.debug.gui.action;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
@ -28,7 +30,9 @@ import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
public class PCByStackLocationTrackingSpec implements LocationTrackingSpec {
public enum PCByStackLocationTrackingSpec implements LocationTrackingSpec, LocationTracker {
INSTANCE;
public static final String CONFIG_NAME = "TRACK_PC_BY_STACK";
@Override
@ -52,7 +56,11 @@ public class PCByStackLocationTrackingSpec implements LocationTrackingSpec {
}
@Override
public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
public LocationTracker getTracker() {
return this;
}
public Address doComputeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
Trace trace = coordinates.getTrace();
TraceThread thread = coordinates.getThread();
long snap = coordinates.getSnap();
@ -68,6 +76,12 @@ public class PCByStackLocationTrackingSpec implements LocationTrackingSpec {
return frame.getProgramCounter(snap);
}
@Override
public CompletableFuture<Address> computeTraceAddress(PluginTool tool,
DebuggerCoordinates coordinates) {
return CompletableFuture.supplyAsync(() -> doComputeTraceAddress(tool, coordinates));
}
// Note it does no good to override affectByRegChange. It must do what we'd avoid anyway.
@Override
public boolean affectedByStackChange(TraceStack stack, DebuggerCoordinates coordinates) {
@ -89,7 +103,7 @@ public class PCByStackLocationTrackingSpec implements LocationTrackingSpec {
}
@Override
public boolean affectedByRegisterChange(TraceAddressSpace space, TraceAddressSnapRange range,
public boolean affectedByBytesChange(TraceAddressSpace space, TraceAddressSnapRange range,
DebuggerCoordinates coordinates) {
return false;
}

View file

@ -15,6 +15,8 @@
*/
package ghidra.app.plugin.core.debug.gui.action;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
@ -25,13 +27,15 @@ import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.util.TraceAddressSpace;
public class PCLocationTrackingSpec implements LocationTrackingSpec {
public enum PCLocationTrackingSpec implements LocationTrackingSpec, LocationTracker {
INSTANCE;
public static final String CONFIG_NAME = "TRACK_PC";
private static final PCByRegisterLocationTrackingSpec BY_REG =
new PCByRegisterLocationTrackingSpec();
PCByRegisterLocationTrackingSpec.INSTANCE;
private static final PCByStackLocationTrackingSpec BY_STACK =
new PCByStackLocationTrackingSpec();
PCByStackLocationTrackingSpec.INSTANCE;
@Override
public String getConfigName() {
@ -54,14 +58,22 @@ public class PCLocationTrackingSpec implements LocationTrackingSpec {
}
@Override
public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
if (coordinates.getTime().isSnapOnly()) {
Address pc = BY_STACK.computeTraceAddress(tool, coordinates);
if (pc != null) {
return pc;
public LocationTracker getTracker() {
return this;
}
@Override
public CompletableFuture<Address> computeTraceAddress(PluginTool tool,
DebuggerCoordinates coordinates) {
return CompletableFuture.supplyAsync(() -> {
if (coordinates.getTime().isSnapOnly()) {
Address pc = BY_STACK.doComputeTraceAddress(tool, coordinates);
if (pc != null) {
return pc;
}
}
}
return BY_REG.computeTraceAddress(tool, coordinates);
return BY_REG.doComputeTraceAddress(tool, coordinates);
});
}
// Note it does no good to override affectByRegChange. It must do what we'd avoid anyway.
@ -71,8 +83,8 @@ public class PCLocationTrackingSpec implements LocationTrackingSpec {
}
@Override
public boolean affectedByRegisterChange(TraceAddressSpace space, TraceAddressSnapRange range,
public boolean affectedByBytesChange(TraceAddressSpace space, TraceAddressSnapRange range,
DebuggerCoordinates coordinates) {
return BY_REG.affectedByRegisterChange(space, range, coordinates);
return BY_REG.affectedByBytesChange(space, range, coordinates);
}
}

View file

@ -15,6 +15,8 @@
*/
package ghidra.app.plugin.core.debug.gui.action;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
@ -29,8 +31,7 @@ import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
// TODO: Use this, or allow arbitrary expressions
public interface RegisterLocationTrackingSpec extends LocationTrackingSpec {
public interface RegisterLocationTrackingSpec extends LocationTrackingSpec, LocationTracker {
Register computeRegister(DebuggerCoordinates coordinates);
AddressSpace computeDefaultAddressSpace(DebuggerCoordinates coordinates);
@ -45,7 +46,11 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec {
}
@Override
default Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
default LocationTracker getTracker() {
return this;
}
default Address doComputeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
Trace trace = coordinates.getTrace();
TracePlatform platform = coordinates.getPlatform();
TraceThread thread = coordinates.getThread();
@ -81,7 +86,13 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec {
}
@Override
default boolean affectedByRegisterChange(TraceAddressSpace space,
default CompletableFuture<Address> computeTraceAddress(PluginTool tool,
DebuggerCoordinates coordinates) {
return CompletableFuture.supplyAsync(() -> doComputeTraceAddress(tool, coordinates));
}
@Override
default boolean affectedByBytesChange(TraceAddressSpace space,
TraceAddressSnapRange range, DebuggerCoordinates coordinates) {
if (!LocationTrackingSpec.changeIsCurrent(space, range, coordinates)) {
return false;

View file

@ -23,7 +23,9 @@ import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Register;
import ghidra.trace.model.guest.TracePlatform;
public class SPLocationTrackingSpec implements RegisterLocationTrackingSpec {
public enum SPLocationTrackingSpec implements RegisterLocationTrackingSpec {
INSTANCE;
public static final String CONFIG_NAME = "TRACK_SP";
@Override

View file

@ -0,0 +1,151 @@
/* ###
* 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.gui.action;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
import ghidra.app.plugin.core.debug.gui.watch.WatchRow;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.async.AsyncUtils;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.util.TraceAddressSpace;
/**
* A tracking specification for the address of a given Sleigh expression
*/
public class WatchLocationTrackingSpec implements LocationTrackingSpec {
public static final String CONFIG_PREFIX = "TRACK_WATCH_";
private final String expression;
/**
* Derive a tracking specification from the given watch
*
* @param watch the watch who address to follow
* @return the tracking specification
*/
public static WatchLocationTrackingSpec fromWatch(WatchRow watch) {
return new WatchLocationTrackingSpec(watch.getExpression());
}
/**
* Create a tracking specification from the given expression
*
* @param expression the Sleigh expression whose address to follow
*/
public WatchLocationTrackingSpec(String expression) {
this.expression = expression;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof WatchLocationTrackingSpec that)) {
return false;
}
return this.expression.equals(that.expression);
}
@Override
public String getConfigName() {
return CONFIG_PREFIX + expression;
}
@Override
public String getMenuName() {
return TrackLocationAction.NAME_PREFIX_WATCH + expression;
}
@Override
public Icon getMenuIcon() {
return DebuggerResources.ICON_REGISTER_MARKER;
}
@Override
public String computeTitle(DebuggerCoordinates coordinates) {
return "&(" + expression + ")";
}
/**
* The tracking logic for a watch (Sleigh expression)
*/
class WatchLocationTracker implements LocationTracker {
private AddressSetView reads;
private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
private PcodeExecutor<WatchValue> asyncExec = null;
private PcodeExpression compiled;
@Override
public CompletableFuture<Address> computeTraceAddress(PluginTool tool,
DebuggerCoordinates coordinates) {
if (!Objects.equals(current, coordinates) || asyncExec == null) {
current = coordinates;
asyncExec = current.getPlatform() == null ? null
: DebuggerPcodeUtils.buildWatchExecutor(tool, coordinates);
}
else {
asyncExec.getState().clear();
}
if (current.getTrace() == null) {
return AsyncUtils.nil();
}
return CompletableFuture.supplyAsync(() -> {
Language language = current.getPlatform().getLanguage();
if (!(language instanceof SleighLanguage slang)) {
return null;
}
if (compiled == null || compiled.getLanguage() != language) {
compiled = SleighProgramCompiler.compileExpression(slang, expression);
}
WatchValue value = compiled.evaluate(asyncExec);
return value == null ? null : value.address();
});
}
@Override
public boolean affectedByBytesChange(TraceAddressSpace space, TraceAddressSnapRange range,
DebuggerCoordinates coordinates) {
return LocationTrackingSpec.changeIsCurrent(space, range, coordinates) &&
(reads == null || reads.intersects(range.getX1(), range.getX2()));
}
@Override
public boolean affectedByStackChange(TraceStack stack, DebuggerCoordinates coordinates) {
return false;
}
}
@Override
public LocationTracker getTracker() {
return new WatchLocationTracker();
}
}

View file

@ -0,0 +1,53 @@
/* ###
* 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.gui.action;
import java.util.List;
import java.util.stream.Collectors;
import ghidra.app.services.DebuggerWatchesService;
import ghidra.framework.plugintool.PluginTool;
/**
* The factory for tracking specifications based on watches
*
* <p>
* This will generate an "address-of-watch" tracking specification for each watch currently in the
* watches service, i.e., configured in the Watches window.
*/
public class WatchLocationTrackingSpecFactory implements LocationTrackingSpecFactory {
@Override
public List<LocationTrackingSpec> getSuggested(PluginTool tool) {
DebuggerWatchesService watchesService = tool.getService(DebuggerWatchesService.class);
if (watchesService == null) {
return List.of();
}
return watchesService.getWatches()
.stream()
.map(WatchLocationTrackingSpec::fromWatch)
.collect(Collectors.toList());
}
@Override
public LocationTrackingSpec parseSpec(String name) {
if (!name.startsWith(WatchLocationTrackingSpec.CONFIG_PREFIX)) {
return null;
}
String expression = name.substring(WatchLocationTrackingSpec.CONFIG_PREFIX.length());
return new WatchLocationTrackingSpec(expression);
}
}

View file

@ -471,8 +471,7 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
provider.readConfigState(providerState); // Yes, config
}
else {
provider.setTrackingSpec(
LocationTrackingSpec.fromConfigName(NoneLocationTrackingSpec.CONFIG_NAME));
provider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
}
}
}

View file

@ -243,8 +243,7 @@ public class DebuggerMemoryBytesPlugin
provider.readConfigState(providerState); // Yes, config
}
else {
provider.setTrackingSpec(
LocationTrackingSpec.fromConfigName(NoneLocationTrackingSpec.CONFIG_NAME));
provider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
}
}
}

View file

@ -33,6 +33,9 @@ import ghidra.framework.plugintool.util.PluginStatus;
eventsConsumed = {
TraceActivatedPluginEvent.class,
},
servicesProvided = {
DebuggerWatchesService.class,
},
servicesRequired = {
DebuggerModelService.class,
DebuggerTraceManagerService.class,
@ -44,11 +47,13 @@ public class DebuggerWatchesPlugin extends AbstractDebuggerPlugin {
public DebuggerWatchesPlugin(PluginTool tool) {
super(tool);
provider = new DebuggerWatchesProvider(this);
registerServiceProvided(DebuggerWatchesService.class, provider);
}
@Override
protected void init() {
provider = new DebuggerWatchesProvider(this);
super.init();
}

View file

@ -21,6 +21,7 @@ import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
@ -47,6 +48,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegisterActionContext;
import ghidra.app.plugin.core.debug.gui.register.RegisterRow;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.services.*;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
@ -59,9 +61,13 @@ import ghidra.framework.options.annotation.AutoOptionDefined;
import ghidra.framework.options.annotation.HelpInfo;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.pcode.exec.DebuggerPcodeUtils;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
import ghidra.pcode.exec.PcodeExecutor;
import ghidra.pcode.exec.trace.TraceSleighUtils;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Symbol;
@ -71,17 +77,20 @@ import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
import ghidra.trace.model.Trace.TraceMemoryStateChangeType;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.*;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
import ghidra.util.table.column.AbstractGColumnRenderer;
public class DebuggerWatchesProvider extends ComponentProviderAdapter {
public class DebuggerWatchesProvider extends ComponentProviderAdapter
implements DebuggerWatchesService {
private static final String KEY_ROW_COUNT = "rowCount";
private static final String PREFIX_ROW = "row";
@ -304,7 +313,13 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
final DebuggerWatchesPlugin plugin;
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
DebuggerCoordinates previous = DebuggerCoordinates.NOWHERE;
private Trace currentTrace; // Copy for transition
SleighLanguage language;
PcodeExecutor<WatchValue> asyncWatchExecutor; // name is reminder to use asynchronously
PcodeExecutor<byte[]> prevValueExecutor;
// TODO: We could do better, but the tests can't sync if we do multi-threaded evaluation
ExecutorService workQueue = Executors.newSingleThreadExecutor();
@AutoServiceConsumed
private DebuggerListingService listingService; // For goto and selection
@ -790,14 +805,24 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
watchRow.setDataType(regRow.getDataType());
}
@Override
public WatchRow addWatch(String expression) {
WatchRow row = new WatchRow(this, "");
row.setCoordinates(current);
watchTableModel.add(row);
row.setExpression(expression);
return row;
}
@Override
public void removeWatch(WatchRow row) {
watchTableModel.delete(row);
}
@Override
public synchronized List<WatchRow> getWatches() {
return List.copyOf(watchTableModel.getModelData());
}
@Override
public JComponent getComponent() {
return mainPanel;
@ -831,44 +856,37 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
current = coordinates;
return;
}
previous = current;
current = coordinates;
doSetTrace(current.getTrace());
setRowsContext(coordinates);
if (current.isAliveAndReadsPresent()) {
readTarget();
TracePlatform platform = current.getPlatform();
Language lang = platform == null ? null : platform.getLanguage();
if (lang instanceof SleighLanguage slang) {
language = slang;
}
else {
language = null;
}
asyncWatchExecutor = current.getPlatform() == null ? null
: DebuggerPcodeUtils.buildWatchExecutor(tool, current);
prevValueExecutor = current.getPlatform() == null || previous.getPlatform() == null ? null
: TraceSleighUtils.buildByteExecutor(previous.getPlatform(),
previous.getViewSnap(), previous.getThread(), previous.getFrame());
reevaluate();
Swing.runIfSwingOrRunLater(() -> watchTableModel.fireTableDataChanged());
}
public synchronized void setRowsContext(DebuggerCoordinates coordinates) {
for (WatchRow row : watchTableModel.getModelData()) {
row.setCoordinates(coordinates);
}
}
public synchronized void readTarget() {
for (WatchRow row : watchTableModel.getModelData()) {
row.doTargetReads();
}
}
public synchronized void doCheckDepsAndReevaluate() {
asyncWatchExecutor.getState().clear();
for (WatchRow row : watchTableModel.getModelData()) {
AddressSetView reads = row.getReads();
if (reads == null || reads.intersects(changed)) {
row.doTargetReads();
row.reevaluate();
}
}
changed.clear();
Swing.runIfSwingOrRunLater(() -> {
watchTableModel.fireTableDataChanged();
contextChanged();
});
}
public void reevaluate() {
@ -914,4 +932,14 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
public void goToTime(TraceSchedule time) {
traceManager.activateTime(time);
}
public void waitEvaluate(int timeoutMs) {
try {
CompletableFuture.runAsync(() -> {
}, workQueue).get(timeoutMs, TimeUnit.MILLISECONDS);
}
catch (ExecutionException | InterruptedException | TimeoutException e) {
throw new AssertionError(e);
}
}
}

View file

@ -19,8 +19,6 @@ import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.Range;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
@ -28,17 +26,16 @@ import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.services.DebuggerStateEditingService;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.async.AsyncUtils;
import ghidra.docking.settings.Settings;
import ghidra.docking.settings.SettingsImpl;
import ghidra.framework.options.SaveState;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.trace.*;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeEncodeException;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.ByteMemBufferImpl;
@ -49,8 +46,8 @@ import ghidra.trace.model.*;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.symbol.TraceLabelSymbol;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.*;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
public class WatchRow {
public static final int TRUNCATE_BYTES_LENGTH = 64;
@ -59,12 +56,6 @@ public class WatchRow {
private static final String KEY_SETTINGS = "settings";
private final DebuggerWatchesProvider provider;
private Trace trace;
private DebuggerCoordinates coordinates;
private SleighLanguage language;
private PcodeExecutor<Pair<byte[], TraceMemoryState>> executorWithState;
private ReadDepsPcodeExecutor executorWithAddress;
private PcodeExecutor<byte[]> asyncExecutor; // name is reminder to use asynchronously
private String expression;
private String typePath;
@ -101,14 +92,14 @@ public class WatchRow {
protected void recompile() {
compiled = null;
error = null;
if (provider.language == null) {
return;
}
if (expression == null || expression.length() == 0) {
return;
}
if (language == null) {
return;
}
try {
compiled = SleighProgramCompiler.compileExpression(language, expression);
compiled = SleighProgramCompiler.compileExpression(provider.language, expression);
}
catch (Exception e) {
error = e;
@ -116,203 +107,73 @@ public class WatchRow {
}
}
protected void doTargetReads() {
if (compiled != null && asyncExecutor != null) {
CompletableFuture<byte[]> asyncEvaluation =
CompletableFuture.supplyAsync(() -> compiled.evaluate(asyncExecutor));
asyncEvaluation.exceptionally(ex -> {
error = ex;
Swing.runIfSwingOrRunLater(() -> {
provider.watchTableModel.notifyUpdated(this);
});
return null;
});
// NB. Re-evaluation triggered by database changes, or called separately
}
}
protected void reevaluate() {
blank();
if (trace == null || compiled == null) {
SleighLanguage language = provider.language;
PcodeExecutor<WatchValue> executor = provider.asyncWatchExecutor;
PcodeExecutor<byte[]> prevExec = provider.prevValueExecutor;
if (executor == null) {
provider.contextChanged();
return;
}
try {
Pair<byte[], TraceMemoryState> valueWithState = compiled.evaluate(executorWithState);
Pair<byte[], Address> valueWithAddress = compiled.evaluate(executorWithAddress);
CompletableFuture.runAsync(() -> {
if (compiled == null || compiled.getLanguage() != language) {
recompile();
}
if (compiled == null) {
provider.contextChanged();
return;
}
WatchValue fullValue = compiled.evaluate(executor);
prevValue = prevExec == null ? null : compiled.evaluate(prevExec);
TracePlatform platform = provider.current.getPlatform();
value = valueWithState.getLeft();
value = fullValue.bytes();
error = null;
state = valueWithState.getRight();
state = fullValue.state();
// TODO: Optional column for guest address?
address = platform.mapGuestToHost(valueWithAddress.getRight());
address = platform.mapGuestToHost(fullValue.address());
symbol = computeSymbol();
reads = platform.mapGuestToHost(executorWithAddress.getReads());
// reads piece uses trace access to translate to host/overlay already
reads = fullValue.reads();
valueObj = parseAsDataTypeObj();
valueString = parseAsDataTypeStr();
}
catch (Exception e) {
}, provider.workQueue).exceptionally(e -> {
error = e;
}
provider.contextChanged();
return null;
}).thenRunAsync(() -> {
provider.watchTableModel.fireTableDataChanged();
provider.contextChanged();
}, AsyncUtils.SWING_EXECUTOR);
}
protected String parseAsDataTypeStr() {
if (dataType == null || value == null) {
return "";
}
MemBuffer buffer = new ByteMemBufferImpl(address, value, language.isBigEndian());
MemBuffer buffer = new ByteMemBufferImpl(address, value, provider.language.isBigEndian());
return dataType.getRepresentation(buffer, settings, value.length);
}
// TODO: DataType settings
protected Object parseAsDataTypeObj() {
if (dataType == null || value == null) {
return null;
}
MemBuffer buffer = new ByteMemBufferImpl(address, value, language.isBigEndian());
MemBuffer buffer = new ByteMemBufferImpl(address, value, provider.language.isBigEndian());
return dataType.getValue(buffer, SettingsImpl.NO_SETTINGS, value.length);
}
public static class ReadDepsTraceBytesPcodeExecutorStatePiece
extends DirectBytesTracePcodeExecutorStatePiece {
private AddressSet reads = new AddressSet();
public ReadDepsTraceBytesPcodeExecutorStatePiece(TracePlatform platform, long snap,
TraceThread thread, int frame) {
super(DirectBytesTracePcodeExecutorState.getDefaultThreadAccess(platform, snap, thread,
frame));
}
@Override
public byte[] getVar(AddressSpace space, long offset, int size, boolean quantize,
Reason reason) {
byte[] data = super.getVar(space, offset, size, quantize, reason);
if (space.isMemorySpace()) {
offset = quantizeOffset(space, offset);
}
if (space.isMemorySpace() || space.isRegisterSpace()) {
try {
reads.add(new AddressRangeImpl(space.getAddress(offset), data.length));
}
catch (AddressOverflowException | AddressOutOfBoundsException e) {
throw new AssertionError(e);
}
}
return data;
}
@Override
protected void setInSpace(AddressSpace space, long offset, int size, byte[] val) {
throw new UnsupportedOperationException("Expression cannot write to trace");
}
public void reset() {
reads = new AddressSet();
}
public AddressSet getReads() {
return new AddressSet(reads);
}
}
public static class ReadDepsPcodeExecutor
extends PcodeExecutor<Pair<byte[], Address>> {
private ReadDepsTraceBytesPcodeExecutorStatePiece depsPiece;
public ReadDepsPcodeExecutor(ReadDepsTraceBytesPcodeExecutorStatePiece depsState,
SleighLanguage language, PairedPcodeArithmetic<byte[], Address> arithmetic,
PcodeExecutorState<Pair<byte[], Address>> state) {
super(language, arithmetic, state, Reason.INSPECT);
this.depsPiece = depsState;
}
@Override
public PcodeFrame execute(PcodeProgram program,
PcodeUseropLibrary<Pair<byte[], Address>> library) {
depsPiece.reset();
return super.execute(program, library);
}
public AddressSet getReads() {
return depsPiece.getReads();
}
}
/**
* Build an executor that can compute three things simultaneously
*
* <p>
* This computes the concrete value, its address, and the set of physical addresses involved in
* the computation. The resulting pair gives the value and its address. To get the addresses
* involved, invoke {@link ReadDepsPcodeExecutor#getReads()} after evaluation.
*
* @param coordinates the coordinates providing context for the evaluation
* @return an executor for evaluating the watch
*/
protected static ReadDepsPcodeExecutor buildAddressDepsExecutor(
DebuggerCoordinates coordinates) {
TracePlatform platform = coordinates.getPlatform();
ReadDepsTraceBytesPcodeExecutorStatePiece piece =
new ReadDepsTraceBytesPcodeExecutorStatePiece(platform, coordinates.getViewSnap(),
coordinates.getThread(), coordinates.getFrame());
Language language = platform.getLanguage();
if (!(language instanceof SleighLanguage slang)) {
throw new IllegalArgumentException("Watch expressions require a Sleigh language");
}
PcodeExecutorState<Pair<byte[], Address>> paired = new DefaultPcodeExecutorState<>(piece)
.paired(new AddressOfPcodeExecutorStatePiece(language));
PairedPcodeArithmetic<byte[], Address> arithmetic = new PairedPcodeArithmetic<>(
BytesPcodeArithmetic.forLanguage(language), AddressOfPcodeArithmetic.INSTANCE);
return new ReadDepsPcodeExecutor(piece, slang, arithmetic, paired);
}
public void setCoordinates(DebuggerCoordinates coordinates) {
// NB. Caller has already verified coordinates actually changed
prevValue = value;
trace = coordinates.getTrace();
this.coordinates = coordinates;
updateType();
if (trace == null) {
blank();
return;
}
Language newLanguage = trace.getBaseLanguage();
if (language != newLanguage) {
if (!(newLanguage instanceof SleighLanguage)) {
error = new RuntimeException("Not a sleigh-based language");
return;
}
language = (SleighLanguage) newLanguage;
recompile();
}
if (coordinates.isAliveAndReadsPresent()) {
asyncExecutor =
DebuggerPcodeUtils.executorForCoordinates(provider.getTool(), coordinates);
}
executorWithState = TraceSleighUtils.buildByteWithStateExecutor(trace,
coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame());
executorWithAddress = buildAddressDepsExecutor(coordinates);
}
public void setExpression(String expression) {
if (!Objects.equals(this.expression, expression)) {
prevValue = null;
// NB. Allow fall-through so user can re-evaluate via nop edit.
}
this.expression = expression;
blank();
recompile();
if (error != null) {
provider.contextChanged();
return;
}
if (asyncExecutor != null) {
doTargetReads();
}
this.compiled = null;
reevaluate();
provider.contextChanged();
}
public String getExpression() {
@ -325,6 +186,7 @@ public class WatchRow {
return;
}
// Try from the trace first
Trace trace = provider.current.getTrace();
if (trace != null) {
dataType = trace.getDataTypeManager().getDataType(typePath);
if (dataType != null) {
@ -378,7 +240,7 @@ public class WatchRow {
return settings;
}
public void settingsChanged() {
protected void settingsChanged() {
if (dataType != null) {
savedSettings.write(dataType.getSettingsDefinitions(), dataType.getDefaultSettings());
}
@ -406,12 +268,12 @@ public class WatchRow {
}
public String getRawValueString() {
if (value == null) {
if (value == null || provider.language == null) {
return "??";
}
if (address == null || !address.getAddressSpace().isMemorySpace()) {
BigInteger asBigInt =
Utils.bytesToBigInteger(value, value.length, language.isBigEndian(), false);
BigInteger asBigInt = Utils.bytesToBigInteger(value, value.length,
provider.language.isBigEndian(), false);
return "0x" + asBigInt.toString(16);
}
if (value.length > TRUNCATE_BYTES_LENGTH) {
@ -437,6 +299,10 @@ public class WatchRow {
return state;
}
public byte[] getValue() {
return value;
}
public String getValueString() {
return valueString;
}
@ -456,7 +322,7 @@ public class WatchRow {
if (editingService == null) {
return false;
}
StateEditor editor = editingService.createStateEditor(coordinates);
StateEditor editor = editingService.createStateEditor(provider.current);
return editor.isVariableEditable(address, getValueLength());
}
@ -488,7 +354,7 @@ public class WatchRow {
val = new BigInteger(intString, 10);
}
setRawValueBytes(
Utils.bigIntegerToBytes(val, value.length, trace.getBaseLanguage().isBigEndian()));
Utils.bigIntegerToBytes(val, value.length, provider.language.isBigEndian()));
}
public void setRawValueBytes(byte[] bytes) {
@ -507,7 +373,7 @@ public class WatchRow {
if (editingService == null) {
throw new AssertionError("No editing service");
}
StateEditor editor = editingService.createStateEditor(coordinates);
StateEditor editor = editingService.createStateEditor(provider.current);
editor.setVariable(address, bytes).exceptionally(ex -> {
Msg.showError(this, null, "Write Failed",
"Could not modify watch value (on target)", ex);
@ -523,7 +389,7 @@ public class WatchRow {
}
try {
byte[] encoded = dataType.encodeRepresentation(valueString,
new ByteMemBufferImpl(address, value, language.isBigEndian()),
new ByteMemBufferImpl(address, value, provider.language.isBigEndian()),
SettingsImpl.NO_SETTINGS, value.length);
setRawValueBytes(encoded);
}
@ -550,8 +416,10 @@ public class WatchRow {
if (address == null || !address.isMemoryAddress()) {
return null;
}
DebuggerCoordinates current = provider.current;
Trace trace = current.getTrace();
Collection<? extends TraceLabelSymbol> labels =
trace.getSymbolManager().labels().getAt(coordinates.getSnap(), null, address, false);
trace.getSymbolManager().labels().getAt(current.getSnap(), null, address, false);
if (!labels.isEmpty()) {
return labels.iterator().next();
}
@ -560,7 +428,7 @@ public class WatchRow {
return null;
}
TraceLocation dloc =
new DefaultTraceLocation(trace, null, Range.singleton(coordinates.getSnap()), address);
new DefaultTraceLocation(trace, null, Range.singleton(current.getSnap()), address);
ProgramLocation sloc = provider.mappingService.getOpenMappedLocation(dloc);
if (sloc == null) {
return null;

View file

@ -65,7 +65,7 @@ public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAcc
@Override
public boolean isLive() {
return recorder != null && recorder.isRecording() && recorder.getSnap() == snap;
return InternalPcodeDebuggerDataAccess.super.isLive();
}
@Override

View file

@ -60,7 +60,7 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist
@Override
public boolean isLive() {
return recorder != null && recorder.isRecording() && recorder.getSnap() == snap;
return InternalPcodeDebuggerDataAccess.super.isLive();
}
@Override
@ -75,7 +75,7 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist
@Override
public CompletableFuture<Boolean> readFromTargetRegisters(AddressSetView guestView) {
if (!isLive()) {
if (guestView.isEmpty() || !isLive()) {
return CompletableFuture.completedFuture(false);
}
Set<Register> toRead = new HashSet<>();

View file

@ -19,10 +19,25 @@ import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.lifecycle.Internal;
import ghidra.pcode.exec.trace.data.InternalPcodeTraceDataAccess;
import ghidra.trace.model.TraceTimeViewport;
@Internal
public interface InternalPcodeDebuggerDataAccess extends InternalPcodeTraceDataAccess {
PluginTool getTool();
TraceRecorder getRecorder();
default boolean isLive() {
TraceRecorder recorder = getRecorder();
if (recorder == null || !recorder.isRecording()) {
return false;
}
TraceTimeViewport viewport = getViewport();
for (long s : viewport.getReversedSnaps()) {
if (recorder.getSnap() == s) {
return true;
}
}
return false;
}
}

View file

@ -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.services;
import java.util.Collection;
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin;
import ghidra.app.plugin.core.debug.gui.watch.WatchRow;
import ghidra.framework.plugintool.ServiceInfo;
/**
* A service interface for controlling the Watches window
*/
@ServiceInfo(
defaultProvider = DebuggerWatchesPlugin.class,
description = "Service for managing watches")
public interface DebuggerWatchesService {
/**
* Add a watch
*
* @param expression the Sleigh expression
* @return the new row
*/
WatchRow addWatch(String expression);
/**
* Remove a watch
*
* @param watch the row to remove
*/
void removeWatch(WatchRow watch);
/**
* Get the current watches
*
* @return the unmodifiable collection of watches
*/
Collection<WatchRow> getWatches();
}

View file

@ -21,10 +21,18 @@ import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerA
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.emu.ThreadPcodeExecutorState;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.trace.*;
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.lang.Language;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryState;
/**
* Utilities for evaluating or executing Sleigh/p-code in the Debugger
@ -39,13 +47,12 @@ public enum DebuggerPcodeUtils {
* If a thread is included, the executor state will have access to both the memory and registers
* in the context of that thread. Otherwise, only memory access is permitted.
*
* @param tool the plugin tool. TODO: This shouldn't be required
* @param tool the plugin tool
* @param coordinates the coordinates
* @return the state
*/
public static PcodeExecutorState<byte[]> executorStateForCoordinates(PluginTool tool,
DebuggerCoordinates coordinates) {
// TODO: Make platform part of coordinates
Trace trace = coordinates.getTrace();
if (trace == null) {
throw new IllegalArgumentException("Coordinates have no trace");
@ -57,7 +64,7 @@ public enum DebuggerPcodeUtils {
"Given trace or platform does not use a Sleigh language");
}
DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(tool,
coordinates.getRecorder(), platform, coordinates.getSnap());
coordinates.getRecorder(), platform, coordinates.getViewSnap());
PcodeExecutorState<byte[]> shared =
new RWTargetMemoryPcodeExecutorState(access.getDataForSharedState(), Mode.RW);
if (coordinates.getThread() == null) {
@ -87,4 +94,261 @@ public enum DebuggerPcodeUtils {
return new PcodeExecutor<>(slang, BytesPcodeArithmetic.forLanguage(slang), state,
Reason.INSPECT);
}
/**
* The value of a watch expression including its state, address, and addresses read
*/
public record WatchValue(byte[] bytes, TraceMemoryState state, Address address,
AddressSetView reads) {
}
/**
* A p-code arithmetic on watch values
*
* <p>
* This is just a composition of four arithmetics. Using Pair<A,Pair<B,Pair<C,D>> would be
* unwieldy.
*/
public enum WatchValuePcodeArithmetic implements PcodeArithmetic<WatchValue> {
BIG_ENDIAN(BytesPcodeArithmetic.BIG_ENDIAN),
LITTLE_ENDIAN(BytesPcodeArithmetic.LITTLE_ENDIAN);
public static WatchValuePcodeArithmetic forEndian(boolean isBigEndian) {
return isBigEndian ? BIG_ENDIAN : LITTLE_ENDIAN;
}
public static WatchValuePcodeArithmetic forLanguage(Language language) {
return forEndian(language.isBigEndian());
}
private static final TraceMemoryStatePcodeArithmetic STATE =
TraceMemoryStatePcodeArithmetic.INSTANCE;
private static final AddressOfPcodeArithmetic ADDRESS =
AddressOfPcodeArithmetic.INSTANCE;
private static final AddressesReadPcodeArithmetic READS =
AddressesReadPcodeArithmetic.INSTANCE;
private final BytesPcodeArithmetic bytes;
private WatchValuePcodeArithmetic(BytesPcodeArithmetic bytes) {
this.bytes = bytes;
}
@Override
public Endian getEndian() {
return bytes.getEndian();
}
@Override
public WatchValue unaryOp(int opcode, int sizeout, int sizein1, WatchValue in1) {
return new WatchValue(
bytes.unaryOp(opcode, sizeout, sizein1, in1.bytes),
STATE.unaryOp(opcode, sizeout, sizein1, in1.state),
ADDRESS.unaryOp(opcode, sizeout, sizein1, in1.address),
READS.unaryOp(opcode, sizeout, sizein1, in1.reads));
}
@Override
public WatchValue binaryOp(int opcode, int sizeout, int sizein1, WatchValue in1,
int sizein2, WatchValue in2) {
return new WatchValue(
bytes.binaryOp(opcode, sizeout, sizein1, in1.bytes, sizein2, in2.bytes),
STATE.binaryOp(opcode, sizeout, sizein1, in1.state, sizein2, in2.state),
ADDRESS.binaryOp(opcode, sizeout, sizein1, in1.address, sizein2, in2.address),
READS.binaryOp(opcode, sizeout, sizein1, in1.reads, sizein2, in2.reads));
}
@Override
public WatchValue modBeforeStore(int sizeout, int sizeinAddress, WatchValue inAddress,
int sizeinValue, WatchValue inValue) {
return new WatchValue(
bytes.modBeforeStore(sizeout, sizeinAddress, inAddress.bytes,
sizeinValue, inValue.bytes),
STATE.modBeforeStore(sizeout, sizeinAddress, inAddress.state,
sizeinValue, inValue.state),
ADDRESS.modBeforeStore(sizeout, sizeinAddress, inAddress.address,
sizeinValue, inValue.address),
READS.modBeforeStore(sizeout, sizeinAddress, inAddress.reads,
sizeinValue, inValue.reads));
}
@Override
public WatchValue modAfterLoad(int sizeout, int sizeinAddress, WatchValue inAddress,
int sizeinValue, WatchValue inValue) {
return new WatchValue(
bytes.modAfterLoad(sizeout, sizeinAddress, inAddress.bytes,
sizeinValue, inValue.bytes),
STATE.modAfterLoad(sizeout, sizeinAddress, inAddress.state,
sizeinValue, inValue.state),
ADDRESS.modAfterLoad(sizeout, sizeinAddress, inAddress.address,
sizeinValue, inValue.address),
READS.modAfterLoad(sizeout, sizeinAddress, inAddress.reads,
sizeinValue, inValue.reads));
}
@Override
public WatchValue fromConst(byte[] value) {
return new WatchValue(
bytes.fromConst(value),
STATE.fromConst(value),
ADDRESS.fromConst(value),
READS.fromConst(value));
}
@Override
public byte[] toConcrete(WatchValue value, Purpose purpose) {
return bytes.toConcrete(value.bytes, purpose);
}
@Override
public long sizeOf(WatchValue value) {
return bytes.sizeOf(value.bytes);
}
}
public static class WatchValuePcodeExecutorStatePiece
implements PcodeExecutorStatePiece<byte[], WatchValue> {
private final PcodeExecutorStatePiece<byte[], byte[]> bytesPiece;
private final PcodeExecutorStatePiece<byte[], TraceMemoryState> statePiece;
private final PcodeExecutorStatePiece<byte[], Address> addressPiece;
private final PcodeExecutorStatePiece<byte[], AddressSetView> readsPiece;
private final PcodeArithmetic<WatchValue> arithmetic;
public WatchValuePcodeExecutorStatePiece(
PcodeExecutorStatePiece<byte[], byte[]> bytesPiece,
PcodeExecutorStatePiece<byte[], TraceMemoryState> statePiece,
PcodeExecutorStatePiece<byte[], Address> addressPiece,
PcodeExecutorStatePiece<byte[], AddressSetView> readsPiece) {
this.bytesPiece = bytesPiece;
this.statePiece = statePiece;
this.addressPiece = addressPiece;
this.readsPiece = readsPiece;
this.arithmetic = WatchValuePcodeArithmetic.forLanguage(bytesPiece.getLanguage());
}
@Override
public Language getLanguage() {
return bytesPiece.getLanguage();
}
@Override
public PcodeArithmetic<byte[]> getAddressArithmetic() {
return bytesPiece.getAddressArithmetic();
}
@Override
public PcodeArithmetic<WatchValue> getArithmetic() {
return arithmetic;
}
@Override
public void setVar(AddressSpace space, byte[] offset, int size, boolean quantize,
WatchValue val) {
bytesPiece.setVar(space, offset, size, quantize, val.bytes);
statePiece.setVar(space, offset, size, quantize, val.state);
addressPiece.setVar(space, offset, size, quantize, val.address);
readsPiece.setVar(space, offset, size, quantize, val.reads);
}
@Override
public WatchValue getVar(AddressSpace space, byte[] offset, int size, boolean quantize,
Reason reason) {
return new WatchValue(
bytesPiece.getVar(space, offset, size, quantize, reason),
statePiece.getVar(space, offset, size, quantize, reason),
addressPiece.getVar(space, offset, size, quantize, reason),
readsPiece.getVar(space, offset, size, quantize, reason));
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return bytesPiece.getConcreteBuffer(address, purpose);
}
@Override
public void clear() {
bytesPiece.clear();
statePiece.clear();
addressPiece.clear();
readsPiece.clear();
}
}
public static class WatchValuePcodeExecutorState implements PcodeExecutorState<WatchValue> {
private WatchValuePcodeExecutorStatePiece piece;
public WatchValuePcodeExecutorState(WatchValuePcodeExecutorStatePiece piece) {
this.piece = piece;
}
@Override
public Language getLanguage() {
return piece.getLanguage();
}
@Override
public PcodeArithmetic<WatchValue> getArithmetic() {
return piece.arithmetic;
}
@Override
public void setVar(AddressSpace space, WatchValue offset, int size, boolean quantize,
WatchValue val) {
piece.setVar(space, offset.bytes, size, quantize, val);
}
@Override
public WatchValue getVar(AddressSpace space, WatchValue offset, int size, boolean quantize,
Reason reason) {
return piece.getVar(space, offset.bytes, size, quantize, reason);
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return piece.getConcreteBuffer(address, purpose);
}
@Override
public void clear() {
piece.clear();
}
}
public static WatchValuePcodeExecutorState buildWatchState(PluginTool tool,
DebuggerCoordinates coordinates) {
PcodeTraceDataAccess data = new DefaultPcodeTraceAccess(coordinates.getPlatform(),
coordinates.getViewSnap(), coordinates.getSnap())
.getDataForThreadState(coordinates.getThread(), coordinates.getFrame());
PcodeExecutorState<byte[]> bytesState = executorStateForCoordinates(tool, coordinates);
return new WatchValuePcodeExecutorState(new WatchValuePcodeExecutorStatePiece(
bytesState,
new TraceMemoryStatePcodeExecutorStatePiece(data),
new AddressOfPcodeExecutorStatePiece(data.getLanguage()),
new AddressesReadTracePcodeExecutorStatePiece(data)));
}
/**
* Build an executor that can compute watch values
*
* <p>
* This computes the concrete value, its state, its address, and the set of physical addresses
* involved in the computation. <b>CAUTION:</b> This executor's state will attempt to read live
* machine state, if applicable. Use the executor in a background thread to avoid locking the
* GUI.
*
* @param tool this plugin tool
* @param coordinates the coordinates providing context for the evaluation
* @return an executor for evaluating the watch
*/
public static PcodeExecutor<WatchValue> buildWatchExecutor(PluginTool tool,
DebuggerCoordinates coordinates) {
TracePlatform platform = coordinates.getPlatform();
Language language = platform.getLanguage();
if (!(language instanceof SleighLanguage slang)) {
throw new IllegalArgumentException("Watch expressions require a Sleigh language");
}
WatchValuePcodeExecutorState state = buildWatchState(tool, coordinates);
return new PcodeExecutor<>(slang, state.getArithmetic(), state, Reason.INSPECT);
}
}

View file

@ -486,20 +486,13 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
}
protected static LocationTrackingSpec getLocationTrackingSpec(String name) {
return LocationTrackingSpec.fromConfigName(name);
return LocationTrackingSpecFactory.fromConfigName(name);
}
protected static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
return AutoReadMemorySpec.fromConfigName(name);
}
protected final LocationTrackingSpec trackNone =
getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME);
protected final LocationTrackingSpec trackPc =
getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME);
protected final LocationTrackingSpec trackSp =
getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME);
protected final AutoReadMemorySpec readNone =
getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME);
protected final AutoReadMemorySpec readVisible =

View file

@ -39,8 +39,7 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec;
import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
@ -271,7 +270,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceThread thread1;
TraceThread thread2;
DebuggerListingProvider extraProvider = SwingExecutorService.LATER
.submit(() -> listingPlugin.createListingIfMissing(trackPc, true))
.submit(() -> listingPlugin.createListingIfMissing(PCLocationTrackingSpec.INSTANCE,
true))
.get();
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceMemoryManager memory = tb.trace.getMemoryManager();
@ -330,7 +330,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
//Pre-check
assertEquals(tb.addr(0x00400000), listingProvider.getLocation().getAddress());
listingProvider.setTrackingSpec(trackSp);
listingProvider.setTrackingSpec(SPLocationTrackingSpec.INSTANCE);
waitForSwing();
ProgramLocation loc = listingProvider.getLocation();
@ -340,7 +340,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testFollowsCurrentTraceOnTraceChangeWithoutRegisterTracking() throws Exception {
listingProvider.setTrackingSpec(trackNone);
listingProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
try ( //
ToyDBTraceBuilder b1 =
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
@ -396,7 +396,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testFollowsCurrentThreadOnThreadChangeWithoutRegisterTracking() throws Exception {
listingProvider.setTrackingSpec(trackNone);
listingProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
try ( //
ToyDBTraceBuilder b1 =
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
@ -735,13 +735,17 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertArrayEquals(zero, buf.array());
runSwing(() -> trace.getProgramView().getMemory().setForceFullView(true));
goToDyn(addr(trace, 0x55550000));
waitRecorder(recorder);
buf.clear();
assertEquals(data.length,
trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(data, buf.array());
waitForPass(noExc(() -> {
goToDyn(addr(trace, 0x55550000));
waitRecorder(recorder);
buf.clear();
assertEquals(data.length,
trace.getMemoryManager()
.getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
assertArrayEquals(data, buf.array());
}));
}
@Test
@ -787,7 +791,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals("(nowhere)", listingProvider.locationLabel.getText());
DebuggerListingProvider extraProvider =
runSwing(() -> listingPlugin.createListingIfMissing(trackNone, false));
runSwing(() -> listingPlugin.createListingIfMissing(NoneLocationTrackingSpec.INSTANCE,
false));
waitForSwing();
assertEquals(traceManager.getCurrentView(), extraProvider.getProgram());
assertEquals("(nowhere)", extraProvider.locationLabel.getText());
@ -877,7 +882,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
waitForSwing();
// Check the default is track pc
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
listingProvider.actionTrackLocation.getCurrentUserData());
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
goToDyn(tb.addr(0x00400000));
@ -888,14 +894,16 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
performAction(listingProvider.actionTrackLocation);
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
setActionStateWithTrigger(listingProvider.actionTrackLocation, trackSp,
setActionStateWithTrigger(listingProvider.actionTrackLocation,
SPLocationTrackingSpec.INSTANCE,
EventTrigger.GUI_ACTION);
waitForSwing();
assertEquals(tb.addr(0x1fff8765), listingProvider.getLocation().getAddress());
listingProvider.setTrackingSpec(trackNone);
listingProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
waitForSwing();
assertEquals(trackNone, listingProvider.actionTrackLocation.getCurrentUserData());
assertEquals(NoneLocationTrackingSpec.INSTANCE,
listingProvider.actionTrackLocation.getCurrentUserData());
}
@Test
@ -1022,7 +1030,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
// NOTE: Action does not exist for main dynamic listing
DebuggerListingProvider extraProvider =
runSwing(() -> listingPlugin.createListingIfMissing(trackNone, true));
runSwing(() -> listingPlugin.createListingIfMissing(NoneLocationTrackingSpec.INSTANCE,
true));
waitForSwing();
assertTrue(extraProvider.actionFollowsCurrentThread.isEnabled());
assertTrue(extraProvider.actionFollowsCurrentThread.isSelected());
@ -1258,7 +1267,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testActivateTraceChangeLanguage() throws Exception {
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
listingProvider.actionTrackLocation.getCurrentUserData());
createSnaplessTrace("x86:LE:64:default");
@ -1300,7 +1310,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testActivateThreadTracks() throws Exception {
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
listingProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
Register pc = tb.language.getProgramCounter();
@ -1331,7 +1342,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testActivateSnapTracks() throws Exception {
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
listingProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
Register pc = tb.language.getProgramCounter();
@ -1359,7 +1371,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testActivateFrameTracks() throws Exception {
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
listingProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
TraceThread thread;
@ -1387,7 +1400,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testRegsPCChangedTracks() throws Exception {
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
listingProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
@ -1417,7 +1431,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testRegsPCChangedTracksDespiteStackWithNoPC() throws Exception {
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
listingProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
@ -1451,7 +1466,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testStackPCChangedTracks() throws Exception {
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
listingProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
DBTraceStackManager sm = tb.trace.getStackManager();

View file

@ -48,7 +48,7 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.app.services.DebuggerStateEditingService;
@ -248,7 +248,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
TraceThread thread1;
TraceThread thread2;
DebuggerMemoryBytesProvider extraProvider = SwingExecutorService.LATER
.submit(() -> memBytesPlugin.createViewerIfMissing(trackPc, true))
.submit(() -> memBytesPlugin.createViewerIfMissing(PCLocationTrackingSpec.INSTANCE,
true))
.get();
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceMemoryManager memory = tb.trace.getMemoryManager();
@ -307,7 +308,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
//Pre-check
assertNull(memBytesProvider.getLocation());
runSwing(() -> memBytesProvider.setTrackingSpec(trackSp));
runSwing(() -> memBytesProvider.setTrackingSpec(SPLocationTrackingSpec.INSTANCE));
ProgramLocation loc = memBytesProvider.getLocation();
assertEquals(tb.trace.getProgramView(), loc.getProgram());
@ -316,7 +317,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testFollowsCurrentTraceOnTraceChangeWithoutRegisterTracking() throws Exception {
runSwing(() -> memBytesProvider.setTrackingSpec(trackNone));
runSwing(() -> memBytesProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE));
try ( //
ToyDBTraceBuilder b1 =
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
@ -367,7 +368,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testFollowsCurrentThreadOnThreadChangeWithoutRegisterTracking() throws Exception {
runSwing(() -> memBytesProvider.setTrackingSpec(trackNone));
runSwing(() -> memBytesProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE));
try ( //
ToyDBTraceBuilder b1 =
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
@ -578,7 +579,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
assertEquals("(nowhere)", memBytesProvider.locationLabel.getText());
DebuggerMemoryBytesProvider extraProvider =
runSwing(() -> memBytesPlugin.createViewerIfMissing(trackNone, false));
runSwing(() -> memBytesPlugin.createViewerIfMissing(NoneLocationTrackingSpec.INSTANCE,
false));
waitForSwing();
assertEquals(traceManager.getCurrentView(), extraProvider.getProgram());
assertEquals("(nowhere)", extraProvider.locationLabel.getText());
@ -670,7 +672,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
waitForSwing();
// Check the default is track pc
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
memBytesProvider.actionTrackLocation.getCurrentUserData());
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
goToDyn(tb.addr(0x00400000));
@ -681,13 +684,15 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
performAction(memBytesProvider.actionTrackLocation);
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
setActionStateWithTrigger(memBytesProvider.actionTrackLocation, trackSp,
setActionStateWithTrigger(memBytesProvider.actionTrackLocation,
SPLocationTrackingSpec.INSTANCE,
EventTrigger.GUI_ACTION);
waitForSwing();
assertEquals(tb.addr(0x1fff8765), memBytesProvider.getLocation().getAddress());
runSwing(() -> memBytesProvider.setTrackingSpec(trackNone));
assertEquals(trackNone, memBytesProvider.actionTrackLocation.getCurrentUserData());
runSwing(() -> memBytesProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE));
assertEquals(NoneLocationTrackingSpec.INSTANCE,
memBytesProvider.actionTrackLocation.getCurrentUserData());
}
@Test
@ -721,7 +726,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
// NOTE: Action does not exist for main dynamic listing
DebuggerMemoryBytesProvider extraProvider =
runSwing(() -> memBytesPlugin.createViewerIfMissing(trackNone, true));
runSwing(() -> memBytesPlugin.createViewerIfMissing(NoneLocationTrackingSpec.INSTANCE,
true));
waitForSwing();
assertTrue(extraProvider.actionFollowsCurrentThread.isEnabled());
assertTrue(extraProvider.actionFollowsCurrentThread.isSelected());
@ -902,7 +908,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testActivateThreadTracks() throws Exception {
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
memBytesProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
Register pc = tb.language.getProgramCounter();
@ -933,7 +940,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testActivateSnapTracks() throws Exception {
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
memBytesProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
Register pc = tb.language.getProgramCounter();
@ -961,7 +969,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testActivateFrameTracks() throws Exception {
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
memBytesProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
TraceThread thread;
@ -989,7 +998,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testRegsPCChangedTracks() throws Exception {
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
memBytesProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
@ -1019,7 +1029,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testRegsPCChangedTracksDespiteStackWithNoPC() throws Exception {
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
memBytesProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
@ -1053,7 +1064,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
@Test
public void testStackPCChangedTracks() throws Exception {
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
assertEquals(PCLocationTrackingSpec.INSTANCE,
memBytesProvider.actionTrackLocation.getCurrentUserData());
createAndOpenTrace();
DBTraceStackManager sm = tb.trace.getStackManager();

View file

@ -30,7 +30,6 @@ import com.google.common.collect.Range;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterDataSettingsDialog;
@ -766,8 +765,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
addRegisterValues(thread);
addRegisterTypes(thread);
// Ensure cause is goto PC, not register tracking
listingPlugin.setTrackingSpec(
LocationTrackingSpec.fromConfigName(NoneLocationTrackingSpec.CONFIG_NAME));
listingPlugin.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
activateThread(thread);
waitForSwing();

View file

@ -22,7 +22,6 @@ import java.nio.ByteBuffer;
import java.util.*;
import java.util.stream.Collectors;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.*;
import com.google.common.collect.Range;
@ -135,7 +134,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
waitForWatches();
assertEquals("0x400000", row.getRawValueString());
assertEquals("", row.getValueString()); // NB. No data type set
@ -153,8 +152,9 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
row.setExpression("r0");
setRegisterValues(thread);
waitForWatches();
waitForPass(() -> assertEquals("0x400000", row.getRawValueString()));
assertEquals("0x400000", row.getRawValueString());
assertNoErr(row);
}
@ -169,7 +169,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
waitForWatches();
assertEquals("0x400000", row.getRawValueString());
assertEquals("400000h", row.getValueString());
@ -190,7 +190,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
watchesProvider.watchFilterPanel.setSelectedItem(row);
waitForSwing();
waitForWatches();
performEnabledAction(watchesProvider, watchesProvider.actionApplyDataType, true);
@ -199,6 +199,12 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals(FormatSettingsDefinition.DECIMAL, format.getChoice(u400000));
}
protected void waitForWatches() {
waitForSwing();
watchesProvider.waitEvaluate(1000);
waitForSwing();
}
@Test
public void testWatchWithDataTypeSettings() {
setRegisterValues(thread);
@ -210,7 +216,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
waitForWatches();
assertEquals("0x400000", row.getRawValueString());
assertEquals("400000h", row.getValueString());
@ -236,7 +242,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
waitForWatches();
watchesProvider.watchFilterPanel.setSelectedItem(row);
waitForSwing();
@ -263,7 +269,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
waitForWatches();
assertEquals("0xdeadbeef", row.getRawValueString());
assertEquals("DEADBEEFh", row.getValueString());
@ -285,7 +291,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
waitForWatches();
assertEquals("0x400008", row.getRawValueString());
assertEquals("400008h", row.getValueString());
@ -315,7 +321,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(trace);
traceManager.activateThread(thread);
waitForSwing();
waitForWatches();
// Verify no target read has occurred yet
TraceMemorySpace regs =
@ -331,15 +337,11 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
row.setExpression("*:4 r0");
row.setDataType(LongDataType.dataType);
waitForWatches();
waitForPass(() -> {
if (row.getError() != null) {
ExceptionUtils.rethrow(row.getError());
}
assertEquals("{ 01 02 03 04 }", row.getRawValueString());
assertEquals("1020304h", row.getValueString());
});
assertNoErr(row);
assertEquals("{ 01 02 03 04 }", row.getRawValueString());
assertEquals("1020304h", row.getValueString());
}
protected void runTestIsEditableEmu(String expression, boolean expectWritable) {
@ -353,7 +355,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
waitForSwing();
waitForWatches();
assertNoErr(row);
assertFalse(row.isRawValueEditable());
@ -387,6 +389,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
waitForWatches();
performAction(watchesProvider.actionEnableEdits);
@ -554,6 +557,8 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
performAction(watchesProvider.actionAdd);
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
row.setExpression(expression);
waitForWatches();
performAction(watchesProvider.actionEnableEdits);
return row;
@ -731,7 +736,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
rowR0.setDataType(PointerDataType.dataType);
registersProvider.setSelectedRow(rowR0);
});
waitForSwing();
waitForWatches();
performEnabledAction(registersProvider, watchesProvider.actionAddFromRegister, true);
@ -760,7 +765,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
row.setExpression("*:8 r0");
traceManager.activateThread(thread);
waitForSwing();
waitForWatches();
assertEquals(symbol, row.getSymbol());
}

View file

@ -34,8 +34,13 @@ import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorState;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.database.UndoableTransaction;
/**
* Test the {@link DirectBytesTracePcodeExecutorState} in combination with
@ -86,6 +91,59 @@ public class TraceRecorderPcodeExecTest extends AbstractGhidraHeadedDebuggerGUIT
assertEquals(11, Utils.bytesToLong(result, result.length, language.isBigEndian()));
}
@Test
public void testExecutorEvalInScratchReadsLive() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
Register::isBaseRegister);
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
waitOn(regs.writeRegistersNamed(Map.of(
"r0", new byte[] { 5 },
"r1", new byte[] { 6 })));
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
Trace trace = recorder.getTrace();
SleighLanguage language = (SleighLanguage) trace.getBaseLanguage();
PcodeExpression expr = SleighProgramCompiler
.compileExpression(language, "r0 + r1");
Register r0 = language.getRegister("r0");
Register r1 = language.getRegister("r1");
waitForPass(() -> {
// TODO: A little brittle: Depends on a specific snap advancement strategy
assertEquals(3, trace.getTimeManager().getSnapshotCount());
DebuggerRegisterMapper rm = recorder.getRegisterMapper(thread);
assertNotNull(rm);
assertNotNull(rm.getTargetRegister("r0"));
assertNotNull(rm.getTargetRegister("r1"));
assertTrue(rm.getRegistersOnTarget().contains(r0));
assertTrue(rm.getRegistersOnTarget().contains(r1));
});
TraceSchedule oneTick = TraceSchedule.snap(recorder.getSnap()).steppedForward(thread, 1);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Scratch")) {
TraceSnapshot scratch = trace.getTimeManager().getSnapshot(Long.MIN_VALUE, true);
scratch.setSchedule(oneTick);
scratch.setDescription("Faked");
TraceMemorySpace space = trace.getMemoryManager().getMemoryRegisterSpace(thread, true);
space.setValue(scratch.getKey(), new RegisterValue(r0, BigInteger.valueOf(10)));
}
PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(tool,
DebuggerCoordinates.NOWHERE.recorder(recorder).thread(thread).time(oneTick));
// In practice, this should be backgrounded, but we're in a test thread
byte[] result = expr.evaluate(executor);
assertEquals(16, Utils.bytesToLong(result, result.length, language.isBigEndian()));
}
@Test
public void testExecutorWrite() throws Throwable {
createTestModel();

View file

@ -0,0 +1,114 @@
/* ###
* 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.pcode.exec.trace;
import java.util.HashMap;
import java.util.Map;
import javax.help.UnsupportedOperationException;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemBuffer;
/**
* An auxilliary state piece that reports the (trace) address ranges
*
* <p>
* Except for unique spaces, sets are ignored, and gets simply echo back the range of addresses of
* the requested read. In unique spaces, the "addresses read" is treated as the value, so that
* values transiting unique space can correct have their source address ranges reported. Use this
* with {@link AddressesReadPcodeArithmetic} to compute the union of these ranges during Sleigh
* expression evaluation. The ranges are translated from the guest platform, if applicable, to the
* trace address. In the case of registers, the addresses are also translated to the appropriate
* overlay space, if applicable.
*/
public class AddressesReadTracePcodeExecutorStatePiece
extends AbstractLongOffsetPcodeExecutorStatePiece<byte[], AddressSetView, AddressSpace>
implements TracePcodeExecutorStatePiece<byte[], AddressSetView> {
protected final PcodeTraceDataAccess data;
private final Map<Long, AddressSetView> unique = new HashMap<>();
/**
* Construct the state piece
*
* @param data the trace data access shim
*/
public AddressesReadTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
super(data.getLanguage(), BytesPcodeArithmetic.forLanguage(data.getLanguage()),
AddressesReadPcodeArithmetic.INSTANCE);
this.data = data;
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
throw new ConcretionError("Cannot make 'addresses read' concrete buffers", purpose);
}
@Override
public PcodeTraceDataAccess getData() {
return data;
}
@Override
public void writeDown(PcodeTraceDataAccess into) {
throw new UnsupportedOperationException();
}
@Override
protected AddressSpace getForSpace(AddressSpace space, boolean toWrite) {
return space;
}
@Override
protected void setInSpace(AddressSpace space, long offset, int size, AddressSetView val) {
if (!space.isUniqueSpace()) {
return;
}
// TODO: size is not considered
unique.put(offset, val);
}
@Override
protected AddressSetView getFromSpace(AddressSpace space, long offset, int size,
Reason reason) {
if (space.isUniqueSpace()) {
AddressSetView result = unique.get(offset);
if (result == null) {
return new AddressSet();
}
return result;
}
Address start = data.translate(space.getAddress(offset));
if (start == null) {
return new AddressSet();
}
try {
return new AddressSet(new AddressRangeImpl(start, size));
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
}
@Override
public void clear() {
unique.clear();
}
}

View file

@ -67,7 +67,7 @@ public class DirectBytesTracePcodeExecutorStatePiece
*
* @param data the trace-data access shim
*/
protected DirectBytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
public DirectBytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
this(BytesPcodeArithmetic.forLanguage(data.getLanguage()), data);
}
@ -138,4 +138,9 @@ public class DirectBytesTracePcodeExecutorStatePiece
public void writeDown(PcodeTraceDataAccess into) {
// Writes directly, so just ignore
}
@Override
public void clear() {
unique.clear();
}
}

View file

@ -120,4 +120,9 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
throw new ConcretionError("Cannot make TraceMemoryState into a concrete buffer", purpose);
}
@Override
public void clear() {
unique.clear();
}
}

View file

@ -51,6 +51,11 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
this.mm = platform.getTrace().getMemoryManager();
}
@Override
public TraceTimeViewport getViewport() {
return viewport;
}
@Override
public Language getLanguage() {
return platform.getLanguage();
@ -186,6 +191,15 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
return ops.getViewBytes(snap, toOverlay(hostStart), buf);
}
@Override
public Address translate(Address address) {
Address host = platform.mapGuestToHost(address);
if (host == null) {
return null;
}
return toOverlay(host);
}
@Override
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
return new DefaultPcodeTracePropertyAccess<>(this, name, type);

View file

@ -46,12 +46,12 @@ public class DefaultPcodeTraceAccess extends AbstractPcodeTraceAccess //
}
@Override
public DefaultPcodeTraceMemoryAccess newDataForSharedState() {
protected DefaultPcodeTraceMemoryAccess newDataForSharedState() {
return new DefaultPcodeTraceMemoryAccess(platform, snap, viewport);
}
@Override
public DefaultPcodeTraceRegistersAccess newDataForLocalState(TraceThread thread, int frame) {
protected DefaultPcodeTraceRegistersAccess newDataForLocalState(TraceThread thread, int frame) {
return new DefaultPcodeTraceRegistersAccess(platform, snap, thread, frame, viewport);
}
}

View file

@ -101,6 +101,14 @@ public class DefaultPcodeTraceThreadAccess
return memory.getBytes(start, buf);
}
@Override
public Address translate(Address address) {
if (address.isRegisterAddress()) {
return registers.translate(address);
}
return memory.translate(address);
}
@Override
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
throw new UnsupportedOperationException("This is meant for p-code executor use");

View file

@ -16,6 +16,7 @@
package ghidra.pcode.exec.trace.data;
import ghidra.lifecycle.Internal;
import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.property.TracePropertyMapOperations;
@ -27,4 +28,6 @@ public interface InternalPcodeTraceDataAccess extends PcodeTraceDataAccess {
<T> TracePropertyMapOperations<T> getPropertyOps(String name, Class<T> type,
boolean createIfAbsent);
TraceTimeViewport getViewport();
}

View file

@ -106,6 +106,18 @@ public interface PcodeTraceAccess {
*/
PcodeTraceRegistersAccess getDataForLocalState(TraceThread thread, int frame);
/**
* Construct a new trace thread data-access shim
*
* @param shared the shared (memory) state
* @param local the local (register) state
* @return the thread data-access shim
*/
default PcodeTraceDataAccess newPcodeTraceThreadAccess(PcodeTraceMemoryAccess shared,
PcodeTraceRegistersAccess local) {
return new DefaultPcodeTraceThreadAccess(shared, local);
}
/**
* Get the data-access shim for use in an executor having thread context
*
@ -123,7 +135,7 @@ public interface PcodeTraceAccess {
if (thread == null) {
return getDataForSharedState();
}
return new DefaultPcodeTraceThreadAccess(getDataForSharedState(),
return newPcodeTraceThreadAccess(getDataForSharedState(),
getDataForLocalState(thread, frame));
}
}

View file

@ -123,6 +123,14 @@ public interface PcodeTraceDataAccess {
*/
int getBytes(Address start, ByteBuffer buf);
/**
* Translate the given emulator address to a host/overlay address
*
* @param address the emulator address
* @return the host/overlay address
*/
Address translate(Address address);
/**
* Get a property-access shim for the named property
*

View file

@ -291,6 +291,7 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
}
}
@Override
public List<Range<Long>> getOrderedSpans() {
try (LockHold hold = trace.lockRead()) {
synchronized (ordered) {

View file

@ -173,6 +173,9 @@ public interface TraceTimeViewport {
<T> AddressSet computeVisibleParts(AddressSetView set, Range<Long> lifespan, T object,
Occlusion<T> occlusion);
List<Range<Long>> getOrderedSpans();
/**
* Get the snaps involved in the view in most-recent-first order
*

View file

@ -112,4 +112,17 @@ public class ThreadPcodeExecutorState<T> implements PcodeExecutorState<T> {
public PcodeExecutorState<T> getLocalState() {
return localState;
}
/**
* {@inheritDoc}
*
* <p>
* This will only clear the thread's local state, lest we invoke clear on the shared state for
* every thread. Instead, if necessary, the machine should clear its local state then clear each
* thread's local state.
*/
@Override
public void clear() {
localState.clear();
}
}

View file

@ -142,4 +142,11 @@ public abstract class AbstractBytesPcodeExecutorStatePiece<S extends BytesPcodeE
public MemBuffer getConcreteBuffer(Address address, PcodeArithmetic.Purpose purpose) {
return new StateMemBuffer(address, getForSpace(address.getAddressSpace(), false));
}
@Override
public void clear() {
for (S space : spaceMap.values()) {
space.clear();
}
}
}

View file

@ -73,6 +73,11 @@ public enum AddressOfPcodeArithmetic implements PcodeArithmetic<Address> {
return null;
}
@Override
public Address fromConst(BigInteger value, int size) {
return null;
}
@Override
public Address fromConst(long value, int size) {
return null;
@ -80,7 +85,7 @@ public enum AddressOfPcodeArithmetic implements PcodeArithmetic<Address> {
@Override
public byte[] toConcrete(Address value, Purpose purpose) {
throw new ConcretionError("Cannot decide branches using 'address of'", purpose);
throw new ConcretionError("Cannot make 'address of' concrete", purpose);
}
@Override

View file

@ -70,6 +70,7 @@ public class AddressOfPcodeExecutorStatePiece
if (!space.isUniqueSpace()) {
return;
}
// TODO: size is not considered
long lOffset = addressArithmetic.toLong(offset, Purpose.STORE);
unique.put(lOffset, val);
}
@ -88,4 +89,9 @@ public class AddressOfPcodeExecutorStatePiece
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
throw new ConcretionError("Cannot make 'address of' concrete buffers", purpose);
}
@Override
public void clear() {
unique.clear();
}
}

View file

@ -0,0 +1,91 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import javax.help.UnsupportedOperationException;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.lang.Endian;
/**
* An auxilliary arithmetic that reports the union of all addresses read, typically during the
* evaluation of an expression.
*/
public enum AddressesReadPcodeArithmetic implements PcodeArithmetic<AddressSetView> {
/** The singleton instance */
INSTANCE;
@Override
public Endian getEndian() {
return null;
}
@Override
public AddressSetView unaryOp(int opcode, int sizeout, int sizein1, AddressSetView in1) {
return in1;
}
@Override
public AddressSetView binaryOp(int opcode, int sizeout, int sizein1, AddressSetView in1,
int sizein2, AddressSetView in2) {
return in1.union(in2);
}
@Override
public AddressSetView modBeforeStore(int sizeout, int sizeinAddress, AddressSetView inAddress,
int sizeinValue, AddressSetView inValue) {
return inValue;
}
@Override
public AddressSetView modAfterLoad(int sizeout, int sizeinAddress, AddressSetView inAddress,
int sizeinValue, AddressSetView inValue) {
return inValue.union(inAddress);
}
@Override
public AddressSetView fromConst(byte[] value) {
return new AddressSet();
}
@Override
public AddressSetView fromConst(BigInteger value, int size, boolean isContextreg) {
return new AddressSet();
}
@Override
public AddressSetView fromConst(BigInteger value, int size) {
return new AddressSet();
}
@Override
public AddressSetView fromConst(long value, int size) {
return new AddressSet();
}
@Override
public byte[] toConcrete(AddressSetView value, Purpose purpose) {
throw new ConcretionError("Cannot make 'addresses read' concrete", purpose);
}
@Override
public long sizeOf(AddressSetView value) {
throw new UnsupportedOperationException();
}
}

View file

@ -172,4 +172,8 @@ public class BytesPcodeExecutorStateSpace<B> {
}
return readBytes(offset, size, reason);
}
public void clear() {
bytes.clear();
}
}

View file

@ -70,4 +70,9 @@ public class DefaultPcodeExecutorState<T> implements PcodeExecutorState<T> {
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return piece.getConcreteBuffer(address, purpose);
}
@Override
public void clear() {
piece.clear();
}
}

View file

@ -116,4 +116,9 @@ public class PairedPcodeExecutorState<L, R> implements PcodeExecutorState<Pair<L
Reason reason) {
return piece.getVar(space, offset.getLeft(), size, quantize, reason);
}
@Override
public void clear() {
piece.clear();
}
}

View file

@ -35,13 +35,15 @@ import ghidra.program.model.mem.MemBuffer;
*
* <p>
* To compose three or more states, first ask if it is really necessary. Second, consider
* implementing the {@link PcodeExecutorStatePiece} interface directly. Third, use the Church-style
* triple. In that third case, the implementor must decide which side has the nested tuple. Putting
* it on the right keeps the concrete piece (conventionally on the left) in the most shallow
* position, so it can be accessed efficiently. However, putting it on the left (implying it's in
* the deepest position) keeps the concrete piece near the other pieces to which it's most closely
* bound. The latter goal is only important when the paired arithmetics mix information between
* their elements.
* implementing the {@link PcodeExecutorStatePiece} interface for a record type. Third, use the
* Church-style triple. In that third case, it is recommended to compose the nested pair on the
* right of the top pair: Compose the two right pieces into a single piece, then use
* {@link PairedPcodeExecutorState} to compose a concrete state with the composed piece, yielding a
* state of triples. This can be applied ad nauseam to compose arbitrarily large tuples; however, at
* a certain point clients should consider creating a record and implementing the state piece and/or
* state interface. It's helpful to use this implementation as a reference. Alternatively, the
* {@code Debugger} module has a {@code WatchValuePcodeExecutorState} which follows this
* recommendation.
*
* @see PairedPcodeExecutorState
* @param <A> the type of offset, usually the type of a controlling state
@ -122,4 +124,10 @@ public class PairedPcodeExecutorStatePiece<A, L, R>
public PcodeExecutorStatePiece<A, R> getRight() {
return right;
}
@Override
public void clear() {
left.clear();
right.clear();
}
}

View file

@ -238,4 +238,15 @@ public interface PcodeExecutorStatePiece<A, T> {
default long quantizeOffset(AddressSpace space, long offset) {
return space.truncateAddressableWordOffset(offset) * space.getAddressableUnitSize();
}
/**
* Erase the entire state or piece
*
* <p>
* This is generally only useful when the state is itself a cache to another object. This will
* ensure the state is reading from that object rather than a stale cache. If this is not a
* cache, this could in fact clear the whole state, and the machine using it will be left in the
* dark.
*/
void clear();
}

View file

@ -115,4 +115,11 @@ public abstract class AbstractTaintPcodeExecutorStatePiece<S extends TaintSpace>
protected TaintVec getFromSpace(S space, long offset, int size, Reason reason) {
return space.get(offset, size);
}
@Override
public void clear() {
for (S space : spaceMap.values()) {
space.clear();
}
}
}

View file

@ -62,9 +62,9 @@ public class TaintSpace {
* Retrieve the taint sets for the variable at the given offset
*
* <p>
* This retrieves as many taint sets as there are elements in the given buffer vector. This first
* element becomes the taint set at the given offset, then each subsequent element becomes the
* taint set at each subsequent offset until the vector is filled. This is analogous to the
* This retrieves as many taint sets as there are elements in the given buffer vector. This
* first element becomes the taint set at the given offset, then each subsequent element becomes
* the taint set at each subsequent offset until the vector is filled. This is analogous to the
* manner in which bytes would be "read" from concrete state, starting at a given offset, into a
* destination array.
*
@ -108,4 +108,8 @@ public class TaintSpace {
protected TaintSet whenNull(long offset) {
return TaintSet.EMPTY;
}
public void clear() {
taints.clear();
}
}

View file

@ -18,6 +18,7 @@ package docking.action.builder;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import javax.swing.Icon;
@ -26,7 +27,7 @@ import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger;
/**
/**
* Builder for {@link MultiStateDockingAction}
*
* @param <T> The action state type
@ -38,9 +39,11 @@ public class MultiStateActionBuilder<T> extends
private boolean useCheckboxForIcons;
private List<ActionState<T>> states = new ArrayList<>();
private Supplier<List<ActionState<T>>> generator = null;
/**
* Builder constructor
*
* @param name the name of the action to be built
* @param owner the owner of the action to be build
*/
@ -55,8 +58,10 @@ public class MultiStateActionBuilder<T> extends
/**
* Sets the primary callback to be executed when this action changes its action state.
* This builder will throw an {@link IllegalStateException} if one of the build methods is
* called without providing this callback
*
* <p>
* This builder will throw an {@link IllegalStateException} if one of the build methods is
* called without providing this callback.
*
* @param biConsumer the callback to execute when the selected action state is changed.
* @return this builder (for chaining)
@ -69,10 +74,12 @@ public class MultiStateActionBuilder<T> extends
}
/**
* Overrides the default icons for actions shown in popup menu of the multi-state action. By
* default, the popup menu items will use the icons as provided by the {@link ActionState}.
* By passing true to this method, icons will not be used in the popup menu. Instead, a
* checkbox icon will be used to show the active action state.
* Overrides the default icons for actions shown in popup menu of the multi-state action.
*
* <p>
* By default, the popup menu items will use the icons as provided by the {@link ActionState}.
* By passing true to this method, icons will not be used in the popup menu. Instead, a checkbox
* icon will be used to show the active action state.
*
* @param b true to use a checkbox
* @return this MultiActionDockingActionBuilder (for chaining)
@ -83,7 +90,7 @@ public class MultiStateActionBuilder<T> extends
}
/**
* Add an action state
* Add an action state
*
* @param displayName the name to appear in the action menu
* @param icon the icon to appear in the action menu
@ -96,7 +103,7 @@ public class MultiStateActionBuilder<T> extends
}
/**
* Add an action state
* Add an action state
*
* @param actionState the action state to add
* @return this MultiActionDockingActionBuilder (for chaining)
@ -107,7 +114,7 @@ public class MultiStateActionBuilder<T> extends
}
/**
* Add a list of action states
* Add a list of action states
*
* @param list a list of ActionStates;
* @return this MultiActionDockingActionBuilder (for chaining)
@ -117,6 +124,21 @@ public class MultiStateActionBuilder<T> extends
return self();
}
/**
* Generate the states dynamically upon the user clicking the button
*
* <p>
* It is highly recommended that the current state is included in the list of available states.
* Otherwise, the user could become confused or frustrated.
*
* @param generator a function from action context to available states
* @return this MultiActionDockingActionBuilder (for chaining)
*/
public MultiStateActionBuilder<T> stateGenerator(Supplier<List<ActionState<T>>> generator) {
this.generator = generator;
return self();
}
@Override
public MultiStateDockingAction<T> build() {
validate();
@ -138,6 +160,16 @@ public class MultiStateActionBuilder<T> extends
super.actionPerformed(context);
}
}
@Override
protected List<ActionState<T>> getStates() {
if (generator == null) {
return super.getStates();
}
else {
return generator.get();
}
}
};
for (ActionState<T> actionState : states) {

View file

@ -15,15 +15,17 @@
*/
package docking.menu;
import java.util.Objects;
import javax.swing.Icon;
import ghidra.util.HelpLocation;
import ghidra.util.SystemUtilities;
/**
* Note: this class overrides the <code>equals(Object)</code> and relies upon the <code>equals</code>
* method of the <code>userData</code> object. Thus, if it is important that equals work for you in
* the non-standard identity way, then you must override <code>equals</code> in your user data objects.
* Note: this class overrides the <code>equals(Object)</code> and relies upon the
* <code>equals</code> method of the <code>userData</code> object. Thus, if it is important that
* equals work for you in the non-standard identity way, then you must override <code>equals</code>
* in your user data objects.
*
* @param <T> the type of the action state
*/
@ -73,7 +75,7 @@ public class ActionState<T> {
ActionState<?> otherState = (ActionState<?>) other;
if (!SystemUtilities.isEqual(userData, otherState.userData)) {
if (!Objects.equals(userData, otherState.userData)) {
return false;
}

View file

@ -31,14 +31,18 @@ import help.Help;
import resources.icons.EmptyIcon;
/**
* An action that can be in one of multiple states. The button of this action has a
* drop-down icon that allows users to change the state of the button. As the user changes the
* state of this action, {@link #actionStateChanged(ActionState, EventTrigger)} will be called.
* Clients may also use the button of this action to respond to button presses by overriding
* An action that can be in one of multiple states.
*
* <p>
* The button of this action has a drop-down icon that allows users to change the state of the
* button. As the user changes the state of this action,
* {@link #actionStateChanged(ActionState, EventTrigger)} will be called. Clients may also use the
* button of this action to respond to button presses by overriding
* {@link #actionPerformed(ActionContext)}.
*
* <p>This action is intended primarily for use as toolbar actions. Alternatively, some clients
* use this action to add a button to custom widgets. In the custom usage case, clients should use
* <p>
* This action is intended primarily for use as toolbar actions. Alternatively, some clients use
* this action to add a button to custom widgets. In the custom use case, clients should use
* {@link NonToolbarMultiStateAction}.
*
* @param <T> the type of the user data
@ -49,7 +53,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
private static Icon EMPTY_ICON = new EmptyIcon(16, 16);
private List<ActionState<T>> actionStates = new ArrayList<>();
private int currentStateIndex = -1;
private ActionState<T> currentState = null;
private MultiActionDockingActionIf multiActionGenerator;
private MultipleActionDockingToolbarButton multipleButton;
@ -57,11 +61,10 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
private boolean useCheckboxForIcons;
/**
* Call this constructor with this action will not be added to a toolbar
* Constructor
*
* @param name the action name
* @param owner the owner
* @see #MultiStateDockingAction(String, String, boolean)
*/
public MultiStateDockingAction(String name, String owner) {
super(name, owner);
@ -71,22 +74,9 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
super.setToolBarData(new ToolBarData(null));
}
/**
* Use this constructor explicitly when this action is used in a toolbar, passing true
* for <code>isToolbarAction</code> (see the javadoc header note).
*
* @param name the action name
* @param owner the owner
* @param isToolbarAction true if this action is a toolbar action
* @deprecated use {@link #MultiStateDockingAction(String, String)}
*/
@Deprecated(forRemoval = true, since = "10.2")
protected MultiStateDockingAction(String name, String owner, boolean isToolbarAction) {
this(name, owner);
}
/**
* This method will be called as the user changes the selected button state
*
* @param newActionState the newly selected state
* @param trigger the source of the event
*/
@ -94,11 +84,12 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
/**
* This method is called when the user clicks the button <B>when this action is used as part of
* the default {@link DockingAction} framework.</B>
* the default {@link DockingAction} framework.</B>
*
* This is the callback to be overridden when the child wishes to respond to user button
* presses that are on the button and not the drop-down. The default behavior is to show the
* popup menu when the button is clicked.
* <p>
* This is the callback to be overridden when the child wishes to respond to user button presses
* that are on the button and not the drop-down. The default behavior is to show the popup menu
* when the button is clicked.
*/
@Override
public void actionPerformed(ActionContext context) {
@ -106,10 +97,12 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
}
/**
* Overrides the default icons for actions shown in popup menu of the multi-state action. By
* default, the popup menu items will use the icons as provided by the {@link ActionState}.
* By passing true to this method, icons will not be used in the popup menu. Instead, a
* checkbox icon will be used to show the active action state.
* Overrides the default icons for actions shown in popup menu of the multi-state action.
*
* <p>
* By default, the popup menu items will use the icons as provided by the {@link ActionState}.
* By passing true to this method, icons will not be used in the popup menu. Instead, a checkbox
* icon will be used to show the active action state.
*
* @param useCheckboxForIcons true to use a checkbox
*/
@ -118,9 +111,11 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
}
/**
* Sets the icon to use if the active action state does not supply an icon. This is useful if
* you wish for your action states to not use icon, but desire the action itself to have an
* icon.
* Sets the icon to use if the active action state does not supply an icon.
*
* <p>
* This is useful if you wish for your action states to not use icon, but desire the action
* itself to have an icon.
*
* @param icon the icon
*/
@ -128,18 +123,38 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
this.defaultIcon = icon;
}
/**
* Extension point: Get the states to display when the button is clicked
*
* <p>
* This is called when the button is clicked, immediately before the menu is displayed. It is
* generally recommended to ensure the current state is included in this list. The states will
* be displayed in the order of the returned list.
*
* @return the list of possible states
*/
protected List<ActionState<T>> getStates() {
return actionStates;
}
private void updateStates() {
List<ActionState<T>> newStates = getStates();
if (newStates.equals(actionStates)) {
return;
}
actionStates.clear();
actionStates.addAll(newStates);
}
protected List<DockingActionIf> getStateActions() {
ActionState<T> selectedState = actionStates.get(currentStateIndex);
updateStates();
List<DockingActionIf> actions = new ArrayList<>(actionStates.size());
for (ActionState<T> actionState : actionStates) {
//@formatter:off
boolean isSelected = actionState == selectedState;
DockingActionIf a = useCheckboxForIcons ?
new ActionStateToggleAction(actionState, isSelected) :
new ActionStateAction(actionState, isSelected);
boolean isSelected = actionState.equals(currentState);
DockingActionIf a = useCheckboxForIcons
? new ActionStateToggleAction(actionState, isSelected)
: new ActionStateAction(actionState, isSelected);
actions.add(a);
//@formatter:on
}
return actions;
}
@ -155,8 +170,8 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
}
/**
* add the supplied {@code ActionState}
* if {@code fireFirstEvent} is {@code true} the first one will fire its event
* Add the supplied {@code ActionState}.
*
* @param actionState the {@code ActionState} to add
*/
public void addActionState(ActionState<T> actionState) {
@ -175,11 +190,11 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
}
public T getCurrentUserData() {
return actionStates.get(currentStateIndex).getUserData();
return currentState == null ? null : currentState.getUserData();
}
public ActionState<T> getCurrentState() {
return actionStates.get(currentStateIndex);
return currentState;
}
public List<ActionState<T>> getAllActionStates() {
@ -187,6 +202,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
}
public void setCurrentActionStateByUserData(T t) {
updateStates();
for (ActionState<T> actionState : actionStates) {
// Note: most clients will pass a T that is already in our list. However, to be more
@ -194,7 +210,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
// problem using equals() here.
// if (actionState.getUserData() == t) {
if (actionState.getUserData().equals(t)) {
setCurrentActionState(actionState);
doSetCurrentActionState(actionState, EventTrigger.API_CALL);
return;
}
}
@ -208,13 +224,16 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
}
public void setCurrentActionStateWithTrigger(ActionState<T> actionState, EventTrigger trigger) {
int indexOf = actionStates.indexOf(actionState);
if (indexOf < 0) {
updateStates();
doSetCurrentActionState(actionState, trigger);
}
protected void doSetCurrentActionState(ActionState<T> actionState, EventTrigger trigger) {
if (!actionStates.contains(actionState)) {
throw new IllegalArgumentException(
"Attempted to set actionState to unknown ActionState.");
}
currentStateIndex = indexOf;
currentState = actionState;
setButtonState(actionState);
ToolBarData tbd = getToolBarData();
@ -240,9 +259,8 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
@Override
public JButton doCreateButton() {
multipleButton = new MultipleActionDockingToolbarButton(multiActionGenerator);
if (currentStateIndex >= 0) {
ActionState<T> actionState = actionStates.get(currentStateIndex);
setButtonState(actionState);
if (currentState != null) {
setButtonState(currentState);
}
return multipleButton;