mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-08-28 05:20:21 +00:00
Merge remote-tracking branch 'origin/GP-2581_Dan_followWatch--SQUASHED'
This commit is contained in:
commit
de9ec734ae
|
@ -7,4 +7,4 @@ DebuggerPlatformOpinion
|
|||
DebuggerProgramLaunchOpinion
|
||||
DebuggerRegisterColumnFactory
|
||||
DisassemblyInject
|
||||
LocationTrackingSpec
|
||||
LocationTrackingSpecFactory
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -243,8 +243,7 @@ public class DebuggerMemoryBytesPlugin
|
|||
provider.readConfigState(providerState); // Yes, config
|
||||
}
|
||||
else {
|
||||
provider.setTrackingSpec(
|
||||
LocationTrackingSpec.fromConfigName(NoneLocationTrackingSpec.CONFIG_NAME));
|
||||
provider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -291,6 +291,7 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Range<Long>> getOrderedSpans() {
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
synchronized (ordered) {
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -172,4 +172,8 @@ public class BytesPcodeExecutorStateSpace<B> {
|
|||
}
|
||||
return readBytes(offset, size, reason);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
bytes.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue