mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-08-28 13:30:21 +00:00
GP-2581: Allow dynamic listing and memory view to follow the adress of a watch.
This commit is contained in:
parent
dc76aa811e
commit
958afa58cc
|
@ -7,4 +7,4 @@ DebuggerPlatformOpinion
|
||||||
DebuggerProgramLaunchOpinion
|
DebuggerProgramLaunchOpinion
|
||||||
DebuggerRegisterColumnFactory
|
DebuggerRegisterColumnFactory
|
||||||
DisassemblyInject
|
DisassemblyInject
|
||||||
LocationTrackingSpec
|
LocationTrackingSpecFactory
|
||||||
|
|
|
@ -110,8 +110,8 @@
|
||||||
|
|
||||||
<LI>Track Program Counter (by stack) - navigates this listing to the current program counter
|
<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,
|
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
|
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 property
|
recommended for debugger connection developers to verify the stack records are being properly
|
||||||
interpreted by the GUI.</LI>
|
interpreted by the GUI.</LI>
|
||||||
|
|
||||||
<LI>Track Program Counter (by register) - navigates this listing to the current program
|
<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
|
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
|
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
|
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>
|
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>
|
</UL>
|
||||||
|
|
||||||
<H3><A name="go_to"></A>Go To (G)</H3>
|
<H3><A name="go_to"></A>Go To (G)</H3>
|
||||||
|
|
|
@ -73,38 +73,9 @@
|
||||||
|
|
||||||
<H3><A name="track_location"></A>Track Location</H3>
|
<H3><A name="track_location"></A>Track Location</H3>
|
||||||
|
|
||||||
<P>This action is always available on all memory windows. It configures automatic navigation
|
<P>This action is equivalent to the same action in the <A href=
|
||||||
for the window. When location tracking is enabled, the window is automatically navigated to an
|
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html#track_location">Dynamic
|
||||||
address computed from the trace's or target's machine state. <B>NOTE:</B> This feature is
|
Listing</A> window. <B>NOTE:</B> This feature is disabled when the edit toggle is on.</P>
|
||||||
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>
|
|
||||||
|
|
||||||
<H3><A name="go_to"></A>Go To (G)</H3>
|
<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_PC_BY_STACK = "Track Program Counter (by Stack)";
|
||||||
String NAME_SP = "Track Stack Pointer";
|
String NAME_SP = "Track Stack Pointer";
|
||||||
String NAME_NONE = "Do Not Track";
|
String NAME_NONE = "Do Not Track";
|
||||||
|
String NAME_PREFIX_WATCH = "Track address of watch: ";
|
||||||
|
|
||||||
// TODO: Separate icons for Program Counter and Stack Pointer
|
// TODO: Separate icons for Program Counter and Stack Pointer
|
||||||
Icon ICON_PC = ICON_REGISTER_MARKER;
|
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 {
|
public interface DebuggerTrackLocationAction extends TrackLocationAction {
|
||||||
|
|
||||||
// TODO: Update the action when new specs enter the class path?
|
|
||||||
static MultiStateActionBuilder<LocationTrackingSpec> builder(Plugin owner) {
|
static MultiStateActionBuilder<LocationTrackingSpec> builder(Plugin owner) {
|
||||||
MultiStateActionBuilder<LocationTrackingSpec> builder = TrackLocationAction.builder(owner);
|
MultiStateActionBuilder<LocationTrackingSpec> builder = TrackLocationAction.builder(owner);
|
||||||
builder.toolBarGroup(owner.getName());
|
builder.toolBarGroup(owner.getName());
|
||||||
builder.useCheckboxForIcons(true);
|
builder.useCheckboxForIcons(true);
|
||||||
for (LocationTrackingSpec spec : LocationTrackingSpec.allSpecs().values()) {
|
|
||||||
builder.addState(spec.getMenuName(), spec.getMenuIcon(), spec);
|
|
||||||
}
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ package ghidra.app.plugin.core.debug.gui.action;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Objects;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.ComponentProvider;
|
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.plugin.core.debug.gui.listing.DebuggerTrackedRegisterListingBackgroundColorModel;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel;
|
import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.framework.options.AutoOptions;
|
import ghidra.framework.options.AutoOptions;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.options.annotation.AutoOptionConsumed;
|
import ghidra.framework.options.annotation.AutoOptionConsumed;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
||||||
import ghidra.program.model.address.Address;
|
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
|
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.stack.TraceStack;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
import ghidra.trace.util.TraceAddressSpace;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
public class DebuggerTrackLocationTrait {
|
public class DebuggerTrackLocationTrait {
|
||||||
protected static final AutoConfigState.ClassHandler<DebuggerTrackLocationTrait> CONFIG_STATE_HANDLER =
|
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.
|
// Should only happen during transitional times, if at all.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!spec.affectedByRegisterChange(space, range, current)) {
|
if (!tracker.affectedByBytesChange(space, range, current)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
doTrack();
|
doTrack();
|
||||||
|
@ -78,7 +79,7 @@ public class DebuggerTrackLocationTrait {
|
||||||
// Should only happen during transitional times, if at all.
|
// Should only happen during transitional times, if at all.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!spec.affectedByStackChange(stack, current)) {
|
if (!tracker.affectedByStackChange(stack, current)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
doTrack();
|
doTrack();
|
||||||
|
@ -134,11 +135,11 @@ public class DebuggerTrackLocationTrait {
|
||||||
|
|
||||||
protected MultiStateDockingAction<LocationTrackingSpec> action;
|
protected MultiStateDockingAction<LocationTrackingSpec> action;
|
||||||
|
|
||||||
private final LocationTrackingSpec defaultSpec =
|
private final LocationTrackingSpec defaultSpec = PCLocationTrackingSpec.INSTANCE;
|
||||||
LocationTrackingSpec.fromConfigName(PCLocationTrackingSpec.CONFIG_NAME);
|
|
||||||
|
|
||||||
@AutoConfigStateField(codec = TrackingSpecConfigFieldCodec.class)
|
@AutoConfigStateField(codec = TrackingSpecConfigFieldCodec.class)
|
||||||
protected LocationTrackingSpec spec = defaultSpec;
|
protected LocationTrackingSpec spec = defaultSpec;
|
||||||
|
protected LocationTracker tracker = spec.getTracker();
|
||||||
|
|
||||||
protected final PluginTool tool;
|
protected final PluginTool tool;
|
||||||
protected final Plugin plugin;
|
protected final Plugin plugin;
|
||||||
|
@ -209,10 +210,9 @@ public class DebuggerTrackLocationTrait {
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiStateDockingAction<LocationTrackingSpec> installAction() {
|
public MultiStateDockingAction<LocationTrackingSpec> installAction() {
|
||||||
// TODO: Add "other" option, and present most-recent in menu, too
|
// TODO: Only those Sleigh expressions applicable to the current thread's registers?
|
||||||
// TODO: "other" as in arbitrary expression?
|
|
||||||
// Only those applicable to the current thread's registers, though.
|
|
||||||
action = DebuggerTrackLocationAction.builder(plugin)
|
action = DebuggerTrackLocationAction.builder(plugin)
|
||||||
|
.stateGenerator(this::getStates)
|
||||||
.onAction(this::clickedSpecButton)
|
.onAction(this::clickedSpecButton)
|
||||||
.onActionStateChanged(this::clickedSpecMenu)
|
.onActionStateChanged(this::clickedSpecMenu)
|
||||||
.buildAndInstallLocal(provider);
|
.buildAndInstallLocal(provider);
|
||||||
|
@ -220,6 +220,21 @@ public class DebuggerTrackLocationTrait {
|
||||||
return action;
|
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) {
|
protected void clickedSpecButton(ActionContext ctx) {
|
||||||
doTrack();
|
doTrack();
|
||||||
}
|
}
|
||||||
|
@ -232,12 +247,13 @@ public class DebuggerTrackLocationTrait {
|
||||||
protected void doSetSpec(LocationTrackingSpec spec) {
|
protected void doSetSpec(LocationTrackingSpec spec) {
|
||||||
if (this.spec != spec) {
|
if (this.spec != spec) {
|
||||||
this.spec = spec;
|
this.spec = spec;
|
||||||
|
this.tracker = spec.getTracker();
|
||||||
specChanged(spec);
|
specChanged(spec);
|
||||||
}
|
}
|
||||||
doTrack();
|
doTrack();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ProgramLocation computeTrackedLocation() {
|
protected CompletableFuture<ProgramLocation> computeTrackedLocation() {
|
||||||
// Change of register values (for current frame)
|
// Change of register values (for current frame)
|
||||||
// Change of stack pc (for current frame)
|
// Change of stack pc (for current frame)
|
||||||
// Change of current view (if not caused by goTo)
|
// Change of current view (if not caused by goTo)
|
||||||
|
@ -248,16 +264,22 @@ public class DebuggerTrackLocationTrait {
|
||||||
DebuggerCoordinates cur = current;
|
DebuggerCoordinates cur = current;
|
||||||
TraceThread thread = cur.getThread();
|
TraceThread thread = cur.getThread();
|
||||||
if (thread == null || spec == null) {
|
if (thread == null || spec == null) {
|
||||||
return null;
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
// NB: view's snap may be forked for emulation
|
// NB: view's snap may be forked for emulation
|
||||||
Address address = spec.computeTraceAddress(tool, cur);
|
return tracker.computeTraceAddress(tool, cur).thenApply(address -> {
|
||||||
return address == null ? null : new ProgramLocation(cur.getView(), address);
|
return address == null ? null : new ProgramLocation(cur.getView(), address);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doTrack() {
|
protected void doTrack() {
|
||||||
trackedLocation = computeTrackedLocation();
|
computeTrackedLocation().thenAccept(loc -> {
|
||||||
locationTracked();
|
trackedLocation = loc;
|
||||||
|
locationTracked();
|
||||||
|
}).exceptionally(ex -> {
|
||||||
|
Msg.error(this, "Error while computing location: " + ex);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addNewListeners() {
|
protected void addNewListeners() {
|
||||||
|
@ -296,7 +318,7 @@ public class DebuggerTrackLocationTrait {
|
||||||
|
|
||||||
public void readConfigState(SaveState saveState) {
|
public void readConfigState(SaveState saveState) {
|
||||||
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
|
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
|
||||||
|
tracker = spec.getTracker();
|
||||||
action.setCurrentActionStateByUserData(spec);
|
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;
|
package ghidra.app.plugin.core.debug.gui.action;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
import javax.swing.Icon;
|
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.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils;
|
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.AutoConfigState.ConfigFieldCodec;
|
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.TraceAddressSnapRange;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
|
||||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||||
import ghidra.trace.model.stack.TraceStack;
|
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
import ghidra.trace.util.TraceAddressSpace;
|
||||||
import ghidra.util.classfinder.ClassSearcher;
|
|
||||||
import ghidra.util.classfinder.ExtensionPoint;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A specification for automatic navigation of the dynamic listing
|
* 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
|
* want 3 different common expressions readily available in the drop-down list. It might make sense
|
||||||
* to generate a tracking specification from each Watch.
|
* to generate a tracking specification from each Watch.
|
||||||
*/
|
*/
|
||||||
public interface LocationTrackingSpec extends ExtensionPoint {
|
public interface LocationTrackingSpec {
|
||||||
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;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Codec for saving/restoring the tracking specification
|
||||||
|
*/
|
||||||
public static class TrackingSpecConfigFieldCodec
|
public static class TrackingSpecConfigFieldCodec
|
||||||
implements ConfigFieldCodec<LocationTrackingSpec> {
|
implements ConfigFieldCodec<LocationTrackingSpec> {
|
||||||
@Override
|
@Override
|
||||||
public LocationTrackingSpec read(SaveState state, String name,
|
public LocationTrackingSpec read(SaveState state, String name,
|
||||||
LocationTrackingSpec current) {
|
LocationTrackingSpec current) {
|
||||||
String specName = state.getString(name, null);
|
String specName = state.getString(name, null);
|
||||||
return fromConfigName(specName);
|
return LocationTrackingSpecFactory.fromConfigName(specName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -112,29 +86,6 @@ public interface LocationTrackingSpec extends ExtensionPoint {
|
||||||
return true;
|
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
|
* Get the configuration name
|
||||||
*
|
*
|
||||||
|
@ -171,42 +122,13 @@ public interface LocationTrackingSpec extends ExtensionPoint {
|
||||||
String computeTitle(DebuggerCoordinates coordinates);
|
String computeTitle(DebuggerCoordinates coordinates);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the trace address to "goto"
|
* Get (or create) the actual location tracking logic
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* If the coordinates indicate emulation, i.e., the schedule is non-empty, the trace manager
|
* Having a separate object from the spec gives implementations the option of keeping state on a
|
||||||
* will already have performed the emulation and stored the results in a "scratch" snap. In
|
* per-window basis.
|
||||||
* 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
|
* @return the tracker
|
||||||
* @param coordinates the trace, thread, snap, etc., of the tool
|
|
||||||
* @return the address to navigate to
|
|
||||||
*/
|
*/
|
||||||
Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates);
|
LocationTracker getTracker();
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
package ghidra.app.plugin.core.debug.gui.action;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
||||||
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.trace.model.TraceAddressSnapRange;
|
import ghidra.trace.model.TraceAddressSnapRange;
|
||||||
import ghidra.trace.model.stack.TraceStack;
|
import ghidra.trace.model.stack.TraceStack;
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
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";
|
public static final String CONFIG_NAME = "TRACK_NONE";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -49,12 +54,18 @@ public class NoneLocationTrackingSpec implements LocationTrackingSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
|
public LocationTracker getTracker() {
|
||||||
return null;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
TraceAddressSnapRange range, DebuggerCoordinates coordinates) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,9 @@ import ghidra.program.model.address.AddressSpace;
|
||||||
import ghidra.program.model.lang.Register;
|
import ghidra.program.model.lang.Register;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
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";
|
public static final String CONFIG_NAME = "TRACK_PC_BY_REGISTER";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.action;
|
package ghidra.app.plugin.core.debug.gui.action;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
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.model.thread.TraceThread;
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
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";
|
public static final String CONFIG_NAME = "TRACK_PC_BY_STACK";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -52,7 +56,11 @@ public class PCByStackLocationTrackingSpec implements LocationTrackingSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
|
public LocationTracker getTracker() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address doComputeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
|
||||||
Trace trace = coordinates.getTrace();
|
Trace trace = coordinates.getTrace();
|
||||||
TraceThread thread = coordinates.getThread();
|
TraceThread thread = coordinates.getThread();
|
||||||
long snap = coordinates.getSnap();
|
long snap = coordinates.getSnap();
|
||||||
|
@ -68,6 +76,12 @@ public class PCByStackLocationTrackingSpec implements LocationTrackingSpec {
|
||||||
return frame.getProgramCounter(snap);
|
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.
|
// Note it does no good to override affectByRegChange. It must do what we'd avoid anyway.
|
||||||
@Override
|
@Override
|
||||||
public boolean affectedByStackChange(TraceStack stack, DebuggerCoordinates coordinates) {
|
public boolean affectedByStackChange(TraceStack stack, DebuggerCoordinates coordinates) {
|
||||||
|
@ -89,7 +103,7 @@ public class PCByStackLocationTrackingSpec implements LocationTrackingSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean affectedByRegisterChange(TraceAddressSpace space, TraceAddressSnapRange range,
|
public boolean affectedByBytesChange(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||||
DebuggerCoordinates coordinates) {
|
DebuggerCoordinates coordinates) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.action;
|
package ghidra.app.plugin.core.debug.gui.action;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
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.model.stack.TraceStack;
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
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";
|
public static final String CONFIG_NAME = "TRACK_PC";
|
||||||
|
|
||||||
private static final PCByRegisterLocationTrackingSpec BY_REG =
|
private static final PCByRegisterLocationTrackingSpec BY_REG =
|
||||||
new PCByRegisterLocationTrackingSpec();
|
PCByRegisterLocationTrackingSpec.INSTANCE;
|
||||||
private static final PCByStackLocationTrackingSpec BY_STACK =
|
private static final PCByStackLocationTrackingSpec BY_STACK =
|
||||||
new PCByStackLocationTrackingSpec();
|
PCByStackLocationTrackingSpec.INSTANCE;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getConfigName() {
|
public String getConfigName() {
|
||||||
|
@ -54,14 +58,22 @@ public class PCLocationTrackingSpec implements LocationTrackingSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
|
public LocationTracker getTracker() {
|
||||||
if (coordinates.getTime().isSnapOnly()) {
|
return this;
|
||||||
Address pc = BY_STACK.computeTraceAddress(tool, coordinates);
|
}
|
||||||
if (pc != null) {
|
|
||||||
return pc;
|
@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.doComputeTraceAddress(tool, coordinates);
|
||||||
return BY_REG.computeTraceAddress(tool, coordinates);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note it does no good to override affectByRegChange. It must do what we'd avoid anyway.
|
// 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
|
@Override
|
||||||
public boolean affectedByRegisterChange(TraceAddressSpace space, TraceAddressSnapRange range,
|
public boolean affectedByBytesChange(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||||
DebuggerCoordinates coordinates) {
|
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;
|
package ghidra.app.plugin.core.debug.gui.action;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
|
@ -29,8 +31,7 @@ import ghidra.trace.model.stack.TraceStack;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
import ghidra.trace.util.TraceAddressSpace;
|
||||||
|
|
||||||
// TODO: Use this, or allow arbitrary expressions
|
public interface RegisterLocationTrackingSpec extends LocationTrackingSpec, LocationTracker {
|
||||||
public interface RegisterLocationTrackingSpec extends LocationTrackingSpec {
|
|
||||||
Register computeRegister(DebuggerCoordinates coordinates);
|
Register computeRegister(DebuggerCoordinates coordinates);
|
||||||
|
|
||||||
AddressSpace computeDefaultAddressSpace(DebuggerCoordinates coordinates);
|
AddressSpace computeDefaultAddressSpace(DebuggerCoordinates coordinates);
|
||||||
|
@ -45,7 +46,11 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
|
default LocationTracker getTracker() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
default Address doComputeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
|
||||||
Trace trace = coordinates.getTrace();
|
Trace trace = coordinates.getTrace();
|
||||||
TracePlatform platform = coordinates.getPlatform();
|
TracePlatform platform = coordinates.getPlatform();
|
||||||
TraceThread thread = coordinates.getThread();
|
TraceThread thread = coordinates.getThread();
|
||||||
|
@ -81,7 +86,13 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
TraceAddressSnapRange range, DebuggerCoordinates coordinates) {
|
||||||
if (!LocationTrackingSpec.changeIsCurrent(space, range, coordinates)) {
|
if (!LocationTrackingSpec.changeIsCurrent(space, range, coordinates)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -23,7 +23,9 @@ import ghidra.program.model.address.AddressSpace;
|
||||||
import ghidra.program.model.lang.Register;
|
import ghidra.program.model.lang.Register;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
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";
|
public static final String CONFIG_NAME = "TRACK_SP";
|
||||||
|
|
||||||
@Override
|
@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
|
provider.readConfigState(providerState); // Yes, config
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
provider.setTrackingSpec(
|
provider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
|
||||||
LocationTrackingSpec.fromConfigName(NoneLocationTrackingSpec.CONFIG_NAME));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -243,8 +243,7 @@ public class DebuggerMemoryBytesPlugin
|
||||||
provider.readConfigState(providerState); // Yes, config
|
provider.readConfigState(providerState); // Yes, config
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
provider.setTrackingSpec(
|
provider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
|
||||||
LocationTrackingSpec.fromConfigName(NoneLocationTrackingSpec.CONFIG_NAME));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,9 @@ import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
eventsConsumed = {
|
eventsConsumed = {
|
||||||
TraceActivatedPluginEvent.class,
|
TraceActivatedPluginEvent.class,
|
||||||
},
|
},
|
||||||
|
servicesProvided = {
|
||||||
|
DebuggerWatchesService.class,
|
||||||
|
},
|
||||||
servicesRequired = {
|
servicesRequired = {
|
||||||
DebuggerModelService.class,
|
DebuggerModelService.class,
|
||||||
DebuggerTraceManagerService.class,
|
DebuggerTraceManagerService.class,
|
||||||
|
@ -44,11 +47,13 @@ public class DebuggerWatchesPlugin extends AbstractDebuggerPlugin {
|
||||||
|
|
||||||
public DebuggerWatchesPlugin(PluginTool tool) {
|
public DebuggerWatchesPlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
|
|
||||||
|
provider = new DebuggerWatchesProvider(this);
|
||||||
|
registerServiceProvided(DebuggerWatchesService.class, provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() {
|
||||||
provider = new DebuggerWatchesProvider(this);
|
|
||||||
super.init();
|
super.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.awt.event.MouseEvent;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.*;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
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.DebuggerResources.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegisterActionContext;
|
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegisterActionContext;
|
||||||
import ghidra.app.plugin.core.debug.gui.register.RegisterRow;
|
import ghidra.app.plugin.core.debug.gui.register.RegisterRow;
|
||||||
|
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.async.AsyncDebouncer;
|
import ghidra.async.AsyncDebouncer;
|
||||||
import ghidra.async.AsyncTimer;
|
import ghidra.async.AsyncTimer;
|
||||||
|
@ -59,9 +61,13 @@ import ghidra.framework.options.annotation.AutoOptionDefined;
|
||||||
import ghidra.framework.options.annotation.HelpInfo;
|
import ghidra.framework.options.annotation.HelpInfo;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
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.pcode.exec.trace.TraceSleighUtils;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.data.DataType;
|
import ghidra.program.model.data.DataType;
|
||||||
|
import ghidra.program.model.lang.Language;
|
||||||
import ghidra.program.model.lang.Register;
|
import ghidra.program.model.lang.Register;
|
||||||
import ghidra.program.model.listing.*;
|
import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.model.symbol.Symbol;
|
import ghidra.program.model.symbol.Symbol;
|
||||||
|
@ -71,17 +77,20 @@ import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
|
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
|
||||||
import ghidra.trace.model.Trace.TraceMemoryStateChangeType;
|
import ghidra.trace.model.Trace.TraceMemoryStateChangeType;
|
||||||
|
import ghidra.trace.model.guest.TracePlatform;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
import ghidra.trace.util.TraceAddressSpace;
|
||||||
import ghidra.util.*;
|
import ghidra.util.HelpLocation;
|
||||||
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.table.GhidraTable;
|
import ghidra.util.table.GhidraTable;
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
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 KEY_ROW_COUNT = "rowCount";
|
||||||
private static final String PREFIX_ROW = "row";
|
private static final String PREFIX_ROW = "row";
|
||||||
|
|
||||||
|
@ -304,7 +313,13 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
||||||
final DebuggerWatchesPlugin plugin;
|
final DebuggerWatchesPlugin plugin;
|
||||||
|
|
||||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||||
|
DebuggerCoordinates previous = DebuggerCoordinates.NOWHERE;
|
||||||
private Trace currentTrace; // Copy for transition
|
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
|
@AutoServiceConsumed
|
||||||
private DebuggerListingService listingService; // For goto and selection
|
private DebuggerListingService listingService; // For goto and selection
|
||||||
|
@ -790,14 +805,24 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
||||||
watchRow.setDataType(regRow.getDataType());
|
watchRow.setDataType(regRow.getDataType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public WatchRow addWatch(String expression) {
|
public WatchRow addWatch(String expression) {
|
||||||
WatchRow row = new WatchRow(this, "");
|
WatchRow row = new WatchRow(this, "");
|
||||||
row.setCoordinates(current);
|
|
||||||
watchTableModel.add(row);
|
watchTableModel.add(row);
|
||||||
row.setExpression(expression);
|
row.setExpression(expression);
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeWatch(WatchRow row) {
|
||||||
|
watchTableModel.delete(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized List<WatchRow> getWatches() {
|
||||||
|
return List.copyOf(watchTableModel.getModelData());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JComponent getComponent() {
|
public JComponent getComponent() {
|
||||||
return mainPanel;
|
return mainPanel;
|
||||||
|
@ -831,44 +856,37 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
||||||
current = coordinates;
|
current = coordinates;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
previous = current;
|
||||||
current = coordinates;
|
current = coordinates;
|
||||||
|
|
||||||
doSetTrace(current.getTrace());
|
doSetTrace(current.getTrace());
|
||||||
|
|
||||||
setRowsContext(coordinates);
|
TracePlatform platform = current.getPlatform();
|
||||||
|
Language lang = platform == null ? null : platform.getLanguage();
|
||||||
if (current.isAliveAndReadsPresent()) {
|
if (lang instanceof SleighLanguage slang) {
|
||||||
readTarget();
|
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();
|
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() {
|
public synchronized void doCheckDepsAndReevaluate() {
|
||||||
|
asyncWatchExecutor.getState().clear();
|
||||||
for (WatchRow row : watchTableModel.getModelData()) {
|
for (WatchRow row : watchTableModel.getModelData()) {
|
||||||
AddressSetView reads = row.getReads();
|
AddressSetView reads = row.getReads();
|
||||||
if (reads == null || reads.intersects(changed)) {
|
if (reads == null || reads.intersects(changed)) {
|
||||||
row.doTargetReads();
|
|
||||||
row.reevaluate();
|
row.reevaluate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
changed.clear();
|
changed.clear();
|
||||||
Swing.runIfSwingOrRunLater(() -> {
|
|
||||||
watchTableModel.fireTableDataChanged();
|
|
||||||
contextChanged();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reevaluate() {
|
public void reevaluate() {
|
||||||
|
@ -914,4 +932,14 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
||||||
public void goToTime(TraceSchedule time) {
|
public void goToTime(TraceSchedule time) {
|
||||||
traceManager.activateTime(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.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
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.DataTypeManagerService;
|
||||||
import ghidra.app.services.DebuggerStateEditingService;
|
import ghidra.app.services.DebuggerStateEditingService;
|
||||||
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
|
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
|
||||||
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.docking.settings.Settings;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.docking.settings.SettingsImpl;
|
import ghidra.docking.settings.SettingsImpl;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.pcode.exec.*;
|
import ghidra.pcode.exec.*;
|
||||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
|
||||||
import ghidra.pcode.exec.trace.*;
|
|
||||||
import ghidra.pcode.utils.Utils;
|
import ghidra.pcode.utils.Utils;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.data.DataType;
|
import ghidra.program.model.data.DataType;
|
||||||
import ghidra.program.model.data.DataTypeEncodeException;
|
import ghidra.program.model.data.DataTypeEncodeException;
|
||||||
import ghidra.program.model.lang.Language;
|
|
||||||
import ghidra.program.model.listing.Function;
|
import ghidra.program.model.listing.Function;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.mem.ByteMemBufferImpl;
|
import ghidra.program.model.mem.ByteMemBufferImpl;
|
||||||
|
@ -49,8 +46,8 @@ import ghidra.trace.model.*;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
import ghidra.trace.model.guest.TracePlatform;
|
||||||
import ghidra.trace.model.memory.TraceMemoryState;
|
import ghidra.trace.model.memory.TraceMemoryState;
|
||||||
import ghidra.trace.model.symbol.TraceLabelSymbol;
|
import ghidra.trace.model.symbol.TraceLabelSymbol;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.*;
|
import ghidra.util.NumericUtilities;
|
||||||
|
|
||||||
public class WatchRow {
|
public class WatchRow {
|
||||||
public static final int TRUNCATE_BYTES_LENGTH = 64;
|
public static final int TRUNCATE_BYTES_LENGTH = 64;
|
||||||
|
@ -59,12 +56,6 @@ public class WatchRow {
|
||||||
private static final String KEY_SETTINGS = "settings";
|
private static final String KEY_SETTINGS = "settings";
|
||||||
|
|
||||||
private final DebuggerWatchesProvider provider;
|
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 expression;
|
||||||
private String typePath;
|
private String typePath;
|
||||||
|
@ -101,14 +92,14 @@ public class WatchRow {
|
||||||
protected void recompile() {
|
protected void recompile() {
|
||||||
compiled = null;
|
compiled = null;
|
||||||
error = null;
|
error = null;
|
||||||
|
if (provider.language == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (expression == null || expression.length() == 0) {
|
if (expression == null || expression.length() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (language == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
compiled = SleighProgramCompiler.compileExpression(language, expression);
|
compiled = SleighProgramCompiler.compileExpression(provider.language, expression);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
error = 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() {
|
protected void reevaluate() {
|
||||||
blank();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
CompletableFuture.runAsync(() -> {
|
||||||
Pair<byte[], TraceMemoryState> valueWithState = compiled.evaluate(executorWithState);
|
if (compiled == null || compiled.getLanguage() != language) {
|
||||||
Pair<byte[], Address> valueWithAddress = compiled.evaluate(executorWithAddress);
|
recompile();
|
||||||
|
}
|
||||||
|
if (compiled == null) {
|
||||||
|
provider.contextChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WatchValue fullValue = compiled.evaluate(executor);
|
||||||
|
prevValue = prevExec == null ? null : compiled.evaluate(prevExec);
|
||||||
|
|
||||||
TracePlatform platform = provider.current.getPlatform();
|
TracePlatform platform = provider.current.getPlatform();
|
||||||
value = valueWithState.getLeft();
|
value = fullValue.bytes();
|
||||||
error = null;
|
error = null;
|
||||||
state = valueWithState.getRight();
|
state = fullValue.state();
|
||||||
// TODO: Optional column for guest address?
|
// TODO: Optional column for guest address?
|
||||||
address = platform.mapGuestToHost(valueWithAddress.getRight());
|
address = platform.mapGuestToHost(fullValue.address());
|
||||||
symbol = computeSymbol();
|
symbol = computeSymbol();
|
||||||
reads = platform.mapGuestToHost(executorWithAddress.getReads());
|
// reads piece uses trace access to translate to host/overlay already
|
||||||
|
reads = fullValue.reads();
|
||||||
|
|
||||||
valueObj = parseAsDataTypeObj();
|
valueObj = parseAsDataTypeObj();
|
||||||
valueString = parseAsDataTypeStr();
|
valueString = parseAsDataTypeStr();
|
||||||
}
|
}, provider.workQueue).exceptionally(e -> {
|
||||||
catch (Exception e) {
|
|
||||||
error = e;
|
error = e;
|
||||||
}
|
provider.contextChanged();
|
||||||
|
return null;
|
||||||
|
}).thenRunAsync(() -> {
|
||||||
|
provider.watchTableModel.fireTableDataChanged();
|
||||||
|
provider.contextChanged();
|
||||||
|
}, AsyncUtils.SWING_EXECUTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String parseAsDataTypeStr() {
|
protected String parseAsDataTypeStr() {
|
||||||
if (dataType == null || value == null) {
|
if (dataType == null || value == null) {
|
||||||
return "";
|
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);
|
return dataType.getRepresentation(buffer, settings, value.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: DataType settings
|
|
||||||
|
|
||||||
protected Object parseAsDataTypeObj() {
|
protected Object parseAsDataTypeObj() {
|
||||||
if (dataType == null || value == null) {
|
if (dataType == null || value == null) {
|
||||||
return 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);
|
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) {
|
public void setExpression(String expression) {
|
||||||
if (!Objects.equals(this.expression, expression)) {
|
if (!Objects.equals(this.expression, expression)) {
|
||||||
prevValue = null;
|
prevValue = null;
|
||||||
// NB. Allow fall-through so user can re-evaluate via nop edit.
|
// NB. Allow fall-through so user can re-evaluate via nop edit.
|
||||||
}
|
}
|
||||||
this.expression = expression;
|
this.expression = expression;
|
||||||
blank();
|
this.compiled = null;
|
||||||
recompile();
|
|
||||||
if (error != null) {
|
|
||||||
provider.contextChanged();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (asyncExecutor != null) {
|
|
||||||
doTargetReads();
|
|
||||||
}
|
|
||||||
reevaluate();
|
reevaluate();
|
||||||
provider.contextChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getExpression() {
|
public String getExpression() {
|
||||||
|
@ -325,6 +186,7 @@ public class WatchRow {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Try from the trace first
|
// Try from the trace first
|
||||||
|
Trace trace = provider.current.getTrace();
|
||||||
if (trace != null) {
|
if (trace != null) {
|
||||||
dataType = trace.getDataTypeManager().getDataType(typePath);
|
dataType = trace.getDataTypeManager().getDataType(typePath);
|
||||||
if (dataType != null) {
|
if (dataType != null) {
|
||||||
|
@ -378,7 +240,7 @@ public class WatchRow {
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void settingsChanged() {
|
protected void settingsChanged() {
|
||||||
if (dataType != null) {
|
if (dataType != null) {
|
||||||
savedSettings.write(dataType.getSettingsDefinitions(), dataType.getDefaultSettings());
|
savedSettings.write(dataType.getSettingsDefinitions(), dataType.getDefaultSettings());
|
||||||
}
|
}
|
||||||
|
@ -406,12 +268,12 @@ public class WatchRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRawValueString() {
|
public String getRawValueString() {
|
||||||
if (value == null) {
|
if (value == null || provider.language == null) {
|
||||||
return "??";
|
return "??";
|
||||||
}
|
}
|
||||||
if (address == null || !address.getAddressSpace().isMemorySpace()) {
|
if (address == null || !address.getAddressSpace().isMemorySpace()) {
|
||||||
BigInteger asBigInt =
|
BigInteger asBigInt = Utils.bytesToBigInteger(value, value.length,
|
||||||
Utils.bytesToBigInteger(value, value.length, language.isBigEndian(), false);
|
provider.language.isBigEndian(), false);
|
||||||
return "0x" + asBigInt.toString(16);
|
return "0x" + asBigInt.toString(16);
|
||||||
}
|
}
|
||||||
if (value.length > TRUNCATE_BYTES_LENGTH) {
|
if (value.length > TRUNCATE_BYTES_LENGTH) {
|
||||||
|
@ -437,6 +299,10 @@ public class WatchRow {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public String getValueString() {
|
public String getValueString() {
|
||||||
return valueString;
|
return valueString;
|
||||||
}
|
}
|
||||||
|
@ -456,7 +322,7 @@ public class WatchRow {
|
||||||
if (editingService == null) {
|
if (editingService == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
StateEditor editor = editingService.createStateEditor(coordinates);
|
StateEditor editor = editingService.createStateEditor(provider.current);
|
||||||
return editor.isVariableEditable(address, getValueLength());
|
return editor.isVariableEditable(address, getValueLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,7 +354,7 @@ public class WatchRow {
|
||||||
val = new BigInteger(intString, 10);
|
val = new BigInteger(intString, 10);
|
||||||
}
|
}
|
||||||
setRawValueBytes(
|
setRawValueBytes(
|
||||||
Utils.bigIntegerToBytes(val, value.length, trace.getBaseLanguage().isBigEndian()));
|
Utils.bigIntegerToBytes(val, value.length, provider.language.isBigEndian()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRawValueBytes(byte[] bytes) {
|
public void setRawValueBytes(byte[] bytes) {
|
||||||
|
@ -507,7 +373,7 @@ public class WatchRow {
|
||||||
if (editingService == null) {
|
if (editingService == null) {
|
||||||
throw new AssertionError("No editing service");
|
throw new AssertionError("No editing service");
|
||||||
}
|
}
|
||||||
StateEditor editor = editingService.createStateEditor(coordinates);
|
StateEditor editor = editingService.createStateEditor(provider.current);
|
||||||
editor.setVariable(address, bytes).exceptionally(ex -> {
|
editor.setVariable(address, bytes).exceptionally(ex -> {
|
||||||
Msg.showError(this, null, "Write Failed",
|
Msg.showError(this, null, "Write Failed",
|
||||||
"Could not modify watch value (on target)", ex);
|
"Could not modify watch value (on target)", ex);
|
||||||
|
@ -523,7 +389,7 @@ public class WatchRow {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
byte[] encoded = dataType.encodeRepresentation(valueString,
|
byte[] encoded = dataType.encodeRepresentation(valueString,
|
||||||
new ByteMemBufferImpl(address, value, language.isBigEndian()),
|
new ByteMemBufferImpl(address, value, provider.language.isBigEndian()),
|
||||||
SettingsImpl.NO_SETTINGS, value.length);
|
SettingsImpl.NO_SETTINGS, value.length);
|
||||||
setRawValueBytes(encoded);
|
setRawValueBytes(encoded);
|
||||||
}
|
}
|
||||||
|
@ -550,8 +416,10 @@ public class WatchRow {
|
||||||
if (address == null || !address.isMemoryAddress()) {
|
if (address == null || !address.isMemoryAddress()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
DebuggerCoordinates current = provider.current;
|
||||||
|
Trace trace = current.getTrace();
|
||||||
Collection<? extends TraceLabelSymbol> labels =
|
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()) {
|
if (!labels.isEmpty()) {
|
||||||
return labels.iterator().next();
|
return labels.iterator().next();
|
||||||
}
|
}
|
||||||
|
@ -560,7 +428,7 @@ public class WatchRow {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
TraceLocation dloc =
|
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);
|
ProgramLocation sloc = provider.mappingService.getOpenMappedLocation(dloc);
|
||||||
if (sloc == null) {
|
if (sloc == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -65,7 +65,7 @@ public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAcc
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLive() {
|
public boolean isLive() {
|
||||||
return recorder != null && recorder.isRecording() && recorder.getSnap() == snap;
|
return InternalPcodeDebuggerDataAccess.super.isLive();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -60,7 +60,7 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLive() {
|
public boolean isLive() {
|
||||||
return recorder != null && recorder.isRecording() && recorder.getSnap() == snap;
|
return InternalPcodeDebuggerDataAccess.super.isLive();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -75,7 +75,7 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Boolean> readFromTargetRegisters(AddressSetView guestView) {
|
public CompletableFuture<Boolean> readFromTargetRegisters(AddressSetView guestView) {
|
||||||
if (!isLive()) {
|
if (guestView.isEmpty() || !isLive()) {
|
||||||
return CompletableFuture.completedFuture(false);
|
return CompletableFuture.completedFuture(false);
|
||||||
}
|
}
|
||||||
Set<Register> toRead = new HashSet<>();
|
Set<Register> toRead = new HashSet<>();
|
||||||
|
|
|
@ -19,10 +19,25 @@ import ghidra.app.services.TraceRecorder;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.lifecycle.Internal;
|
import ghidra.lifecycle.Internal;
|
||||||
import ghidra.pcode.exec.trace.data.InternalPcodeTraceDataAccess;
|
import ghidra.pcode.exec.trace.data.InternalPcodeTraceDataAccess;
|
||||||
|
import ghidra.trace.model.TraceTimeViewport;
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
public interface InternalPcodeDebuggerDataAccess extends InternalPcodeTraceDataAccess {
|
public interface InternalPcodeDebuggerDataAccess extends InternalPcodeTraceDataAccess {
|
||||||
PluginTool getTool();
|
PluginTool getTool();
|
||||||
|
|
||||||
TraceRecorder getRecorder();
|
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.app.plugin.processors.sleigh.SleighLanguage;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.pcode.emu.ThreadPcodeExecutorState;
|
import ghidra.pcode.emu.ThreadPcodeExecutorState;
|
||||||
|
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
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.lang.Language;
|
||||||
|
import ghidra.program.model.mem.MemBuffer;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
import ghidra.trace.model.guest.TracePlatform;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for evaluating or executing Sleigh/p-code in the Debugger
|
* 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
|
* 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.
|
* 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
|
* @param coordinates the coordinates
|
||||||
* @return the state
|
* @return the state
|
||||||
*/
|
*/
|
||||||
public static PcodeExecutorState<byte[]> executorStateForCoordinates(PluginTool tool,
|
public static PcodeExecutorState<byte[]> executorStateForCoordinates(PluginTool tool,
|
||||||
DebuggerCoordinates coordinates) {
|
DebuggerCoordinates coordinates) {
|
||||||
// TODO: Make platform part of coordinates
|
|
||||||
Trace trace = coordinates.getTrace();
|
Trace trace = coordinates.getTrace();
|
||||||
if (trace == null) {
|
if (trace == null) {
|
||||||
throw new IllegalArgumentException("Coordinates have no trace");
|
throw new IllegalArgumentException("Coordinates have no trace");
|
||||||
|
@ -57,7 +64,7 @@ public enum DebuggerPcodeUtils {
|
||||||
"Given trace or platform does not use a Sleigh language");
|
"Given trace or platform does not use a Sleigh language");
|
||||||
}
|
}
|
||||||
DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(tool,
|
DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(tool,
|
||||||
coordinates.getRecorder(), platform, coordinates.getSnap());
|
coordinates.getRecorder(), platform, coordinates.getViewSnap());
|
||||||
PcodeExecutorState<byte[]> shared =
|
PcodeExecutorState<byte[]> shared =
|
||||||
new RWTargetMemoryPcodeExecutorState(access.getDataForSharedState(), Mode.RW);
|
new RWTargetMemoryPcodeExecutorState(access.getDataForSharedState(), Mode.RW);
|
||||||
if (coordinates.getThread() == null) {
|
if (coordinates.getThread() == null) {
|
||||||
|
@ -87,4 +94,261 @@ public enum DebuggerPcodeUtils {
|
||||||
return new PcodeExecutor<>(slang, BytesPcodeArithmetic.forLanguage(slang), state,
|
return new PcodeExecutor<>(slang, BytesPcodeArithmetic.forLanguage(slang), state,
|
||||||
Reason.INSPECT);
|
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) {
|
protected static LocationTrackingSpec getLocationTrackingSpec(String name) {
|
||||||
return LocationTrackingSpec.fromConfigName(name);
|
return LocationTrackingSpecFactory.fromConfigName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
|
protected static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
|
||||||
return AutoReadMemorySpec.fromConfigName(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 =
|
protected final AutoReadMemorySpec readNone =
|
||||||
getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME);
|
getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME);
|
||||||
protected final AutoReadMemorySpec readVisible =
|
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.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
|
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.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog;
|
|
||||||
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
|
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.BoundAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
|
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
|
||||||
|
@ -271,7 +270,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
TraceThread thread1;
|
TraceThread thread1;
|
||||||
TraceThread thread2;
|
TraceThread thread2;
|
||||||
DebuggerListingProvider extraProvider = SwingExecutorService.LATER
|
DebuggerListingProvider extraProvider = SwingExecutorService.LATER
|
||||||
.submit(() -> listingPlugin.createListingIfMissing(trackPc, true))
|
.submit(() -> listingPlugin.createListingIfMissing(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
true))
|
||||||
.get();
|
.get();
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
DBTraceMemoryManager memory = tb.trace.getMemoryManager();
|
DBTraceMemoryManager memory = tb.trace.getMemoryManager();
|
||||||
|
@ -330,7 +330,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
//Pre-check
|
//Pre-check
|
||||||
assertEquals(tb.addr(0x00400000), listingProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x00400000), listingProvider.getLocation().getAddress());
|
||||||
|
|
||||||
listingProvider.setTrackingSpec(trackSp);
|
listingProvider.setTrackingSpec(SPLocationTrackingSpec.INSTANCE);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
ProgramLocation loc = listingProvider.getLocation();
|
ProgramLocation loc = listingProvider.getLocation();
|
||||||
|
@ -340,7 +340,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFollowsCurrentTraceOnTraceChangeWithoutRegisterTracking() throws Exception {
|
public void testFollowsCurrentTraceOnTraceChangeWithoutRegisterTracking() throws Exception {
|
||||||
listingProvider.setTrackingSpec(trackNone);
|
listingProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
|
||||||
try ( //
|
try ( //
|
||||||
ToyDBTraceBuilder b1 =
|
ToyDBTraceBuilder b1 =
|
||||||
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
|
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
|
||||||
|
@ -396,7 +396,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFollowsCurrentThreadOnThreadChangeWithoutRegisterTracking() throws Exception {
|
public void testFollowsCurrentThreadOnThreadChangeWithoutRegisterTracking() throws Exception {
|
||||||
listingProvider.setTrackingSpec(trackNone);
|
listingProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
|
||||||
try ( //
|
try ( //
|
||||||
ToyDBTraceBuilder b1 =
|
ToyDBTraceBuilder b1 =
|
||||||
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
|
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
|
||||||
|
@ -735,13 +735,17 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
assertArrayEquals(zero, buf.array());
|
assertArrayEquals(zero, buf.array());
|
||||||
|
|
||||||
runSwing(() -> trace.getProgramView().getMemory().setForceFullView(true));
|
runSwing(() -> trace.getProgramView().getMemory().setForceFullView(true));
|
||||||
goToDyn(addr(trace, 0x55550000));
|
|
||||||
waitRecorder(recorder);
|
|
||||||
|
|
||||||
buf.clear();
|
waitForPass(noExc(() -> {
|
||||||
assertEquals(data.length,
|
goToDyn(addr(trace, 0x55550000));
|
||||||
trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
|
waitRecorder(recorder);
|
||||||
assertArrayEquals(data, buf.array());
|
|
||||||
|
buf.clear();
|
||||||
|
assertEquals(data.length,
|
||||||
|
trace.getMemoryManager()
|
||||||
|
.getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf));
|
||||||
|
assertArrayEquals(data, buf.array());
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -787,7 +791,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
assertEquals("(nowhere)", listingProvider.locationLabel.getText());
|
assertEquals("(nowhere)", listingProvider.locationLabel.getText());
|
||||||
|
|
||||||
DebuggerListingProvider extraProvider =
|
DebuggerListingProvider extraProvider =
|
||||||
runSwing(() -> listingPlugin.createListingIfMissing(trackNone, false));
|
runSwing(() -> listingPlugin.createListingIfMissing(NoneLocationTrackingSpec.INSTANCE,
|
||||||
|
false));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
assertEquals(traceManager.getCurrentView(), extraProvider.getProgram());
|
assertEquals(traceManager.getCurrentView(), extraProvider.getProgram());
|
||||||
assertEquals("(nowhere)", extraProvider.locationLabel.getText());
|
assertEquals("(nowhere)", extraProvider.locationLabel.getText());
|
||||||
|
@ -877,7 +882,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
// Check the default is track pc
|
// Check the default is track pc
|
||||||
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
listingProvider.actionTrackLocation.getCurrentUserData());
|
||||||
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
|
||||||
|
|
||||||
goToDyn(tb.addr(0x00400000));
|
goToDyn(tb.addr(0x00400000));
|
||||||
|
@ -888,14 +894,16 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
performAction(listingProvider.actionTrackLocation);
|
performAction(listingProvider.actionTrackLocation);
|
||||||
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
|
||||||
|
|
||||||
setActionStateWithTrigger(listingProvider.actionTrackLocation, trackSp,
|
setActionStateWithTrigger(listingProvider.actionTrackLocation,
|
||||||
|
SPLocationTrackingSpec.INSTANCE,
|
||||||
EventTrigger.GUI_ACTION);
|
EventTrigger.GUI_ACTION);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
assertEquals(tb.addr(0x1fff8765), listingProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x1fff8765), listingProvider.getLocation().getAddress());
|
||||||
|
|
||||||
listingProvider.setTrackingSpec(trackNone);
|
listingProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
assertEquals(trackNone, listingProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(NoneLocationTrackingSpec.INSTANCE,
|
||||||
|
listingProvider.actionTrackLocation.getCurrentUserData());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1022,7 +1030,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
// NOTE: Action does not exist for main dynamic listing
|
// NOTE: Action does not exist for main dynamic listing
|
||||||
DebuggerListingProvider extraProvider =
|
DebuggerListingProvider extraProvider =
|
||||||
runSwing(() -> listingPlugin.createListingIfMissing(trackNone, true));
|
runSwing(() -> listingPlugin.createListingIfMissing(NoneLocationTrackingSpec.INSTANCE,
|
||||||
|
true));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
assertTrue(extraProvider.actionFollowsCurrentThread.isEnabled());
|
assertTrue(extraProvider.actionFollowsCurrentThread.isEnabled());
|
||||||
assertTrue(extraProvider.actionFollowsCurrentThread.isSelected());
|
assertTrue(extraProvider.actionFollowsCurrentThread.isSelected());
|
||||||
|
@ -1258,7 +1267,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateTraceChangeLanguage() throws Exception {
|
public void testActivateTraceChangeLanguage() throws Exception {
|
||||||
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
listingProvider.actionTrackLocation.getCurrentUserData());
|
||||||
|
|
||||||
createSnaplessTrace("x86:LE:64:default");
|
createSnaplessTrace("x86:LE:64:default");
|
||||||
|
|
||||||
|
@ -1300,7 +1310,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateThreadTracks() throws Exception {
|
public void testActivateThreadTracks() throws Exception {
|
||||||
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
listingProvider.actionTrackLocation.getCurrentUserData());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
Register pc = tb.language.getProgramCounter();
|
Register pc = tb.language.getProgramCounter();
|
||||||
|
@ -1331,7 +1342,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateSnapTracks() throws Exception {
|
public void testActivateSnapTracks() throws Exception {
|
||||||
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
listingProvider.actionTrackLocation.getCurrentUserData());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
Register pc = tb.language.getProgramCounter();
|
Register pc = tb.language.getProgramCounter();
|
||||||
|
@ -1359,7 +1371,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateFrameTracks() throws Exception {
|
public void testActivateFrameTracks() throws Exception {
|
||||||
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
listingProvider.actionTrackLocation.getCurrentUserData());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
TraceThread thread;
|
TraceThread thread;
|
||||||
|
@ -1387,7 +1400,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRegsPCChangedTracks() throws Exception {
|
public void testRegsPCChangedTracks() throws Exception {
|
||||||
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
listingProvider.actionTrackLocation.getCurrentUserData());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
|
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
@ -1417,7 +1431,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRegsPCChangedTracksDespiteStackWithNoPC() throws Exception {
|
public void testRegsPCChangedTracksDespiteStackWithNoPC() throws Exception {
|
||||||
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
listingProvider.actionTrackLocation.getCurrentUserData());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
|
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
@ -1451,7 +1466,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStackPCChangedTracks() throws Exception {
|
public void testStackPCChangedTracks() throws Exception {
|
||||||
assertEquals(trackPc, listingProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
listingProvider.actionTrackLocation.getCurrentUserData());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
DBTraceStackManager sm = tb.trace.getStackManager();
|
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.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
|
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.gui.listing.DebuggerListingPlugin;
|
||||||
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
|
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
|
||||||
import ghidra.app.services.DebuggerStateEditingService;
|
import ghidra.app.services.DebuggerStateEditingService;
|
||||||
|
@ -248,7 +248,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
TraceThread thread1;
|
TraceThread thread1;
|
||||||
TraceThread thread2;
|
TraceThread thread2;
|
||||||
DebuggerMemoryBytesProvider extraProvider = SwingExecutorService.LATER
|
DebuggerMemoryBytesProvider extraProvider = SwingExecutorService.LATER
|
||||||
.submit(() -> memBytesPlugin.createViewerIfMissing(trackPc, true))
|
.submit(() -> memBytesPlugin.createViewerIfMissing(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
true))
|
||||||
.get();
|
.get();
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
DBTraceMemoryManager memory = tb.trace.getMemoryManager();
|
DBTraceMemoryManager memory = tb.trace.getMemoryManager();
|
||||||
|
@ -307,7 +308,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
//Pre-check
|
//Pre-check
|
||||||
assertNull(memBytesProvider.getLocation());
|
assertNull(memBytesProvider.getLocation());
|
||||||
|
|
||||||
runSwing(() -> memBytesProvider.setTrackingSpec(trackSp));
|
runSwing(() -> memBytesProvider.setTrackingSpec(SPLocationTrackingSpec.INSTANCE));
|
||||||
|
|
||||||
ProgramLocation loc = memBytesProvider.getLocation();
|
ProgramLocation loc = memBytesProvider.getLocation();
|
||||||
assertEquals(tb.trace.getProgramView(), loc.getProgram());
|
assertEquals(tb.trace.getProgramView(), loc.getProgram());
|
||||||
|
@ -316,7 +317,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFollowsCurrentTraceOnTraceChangeWithoutRegisterTracking() throws Exception {
|
public void testFollowsCurrentTraceOnTraceChangeWithoutRegisterTracking() throws Exception {
|
||||||
runSwing(() -> memBytesProvider.setTrackingSpec(trackNone));
|
runSwing(() -> memBytesProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE));
|
||||||
try ( //
|
try ( //
|
||||||
ToyDBTraceBuilder b1 =
|
ToyDBTraceBuilder b1 =
|
||||||
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
|
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
|
||||||
|
@ -367,7 +368,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFollowsCurrentThreadOnThreadChangeWithoutRegisterTracking() throws Exception {
|
public void testFollowsCurrentThreadOnThreadChangeWithoutRegisterTracking() throws Exception {
|
||||||
runSwing(() -> memBytesProvider.setTrackingSpec(trackNone));
|
runSwing(() -> memBytesProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE));
|
||||||
try ( //
|
try ( //
|
||||||
ToyDBTraceBuilder b1 =
|
ToyDBTraceBuilder b1 =
|
||||||
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
|
new ToyDBTraceBuilder(name.getMethodName() + "_1", LANGID_TOYBE64); //
|
||||||
|
@ -578,7 +579,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
assertEquals("(nowhere)", memBytesProvider.locationLabel.getText());
|
assertEquals("(nowhere)", memBytesProvider.locationLabel.getText());
|
||||||
|
|
||||||
DebuggerMemoryBytesProvider extraProvider =
|
DebuggerMemoryBytesProvider extraProvider =
|
||||||
runSwing(() -> memBytesPlugin.createViewerIfMissing(trackNone, false));
|
runSwing(() -> memBytesPlugin.createViewerIfMissing(NoneLocationTrackingSpec.INSTANCE,
|
||||||
|
false));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
assertEquals(traceManager.getCurrentView(), extraProvider.getProgram());
|
assertEquals(traceManager.getCurrentView(), extraProvider.getProgram());
|
||||||
assertEquals("(nowhere)", extraProvider.locationLabel.getText());
|
assertEquals("(nowhere)", extraProvider.locationLabel.getText());
|
||||||
|
@ -670,7 +672,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
// Check the default is track pc
|
// Check the default is track pc
|
||||||
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
memBytesProvider.actionTrackLocation.getCurrentUserData());
|
||||||
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
|
||||||
|
|
||||||
goToDyn(tb.addr(0x00400000));
|
goToDyn(tb.addr(0x00400000));
|
||||||
|
@ -681,13 +684,15 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
performAction(memBytesProvider.actionTrackLocation);
|
performAction(memBytesProvider.actionTrackLocation);
|
||||||
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
|
||||||
|
|
||||||
setActionStateWithTrigger(memBytesProvider.actionTrackLocation, trackSp,
|
setActionStateWithTrigger(memBytesProvider.actionTrackLocation,
|
||||||
|
SPLocationTrackingSpec.INSTANCE,
|
||||||
EventTrigger.GUI_ACTION);
|
EventTrigger.GUI_ACTION);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
assertEquals(tb.addr(0x1fff8765), memBytesProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x1fff8765), memBytesProvider.getLocation().getAddress());
|
||||||
|
|
||||||
runSwing(() -> memBytesProvider.setTrackingSpec(trackNone));
|
runSwing(() -> memBytesProvider.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE));
|
||||||
assertEquals(trackNone, memBytesProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(NoneLocationTrackingSpec.INSTANCE,
|
||||||
|
memBytesProvider.actionTrackLocation.getCurrentUserData());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -721,7 +726,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
// NOTE: Action does not exist for main dynamic listing
|
// NOTE: Action does not exist for main dynamic listing
|
||||||
DebuggerMemoryBytesProvider extraProvider =
|
DebuggerMemoryBytesProvider extraProvider =
|
||||||
runSwing(() -> memBytesPlugin.createViewerIfMissing(trackNone, true));
|
runSwing(() -> memBytesPlugin.createViewerIfMissing(NoneLocationTrackingSpec.INSTANCE,
|
||||||
|
true));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
assertTrue(extraProvider.actionFollowsCurrentThread.isEnabled());
|
assertTrue(extraProvider.actionFollowsCurrentThread.isEnabled());
|
||||||
assertTrue(extraProvider.actionFollowsCurrentThread.isSelected());
|
assertTrue(extraProvider.actionFollowsCurrentThread.isSelected());
|
||||||
|
@ -902,7 +908,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateThreadTracks() throws Exception {
|
public void testActivateThreadTracks() throws Exception {
|
||||||
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
memBytesProvider.actionTrackLocation.getCurrentUserData());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
Register pc = tb.language.getProgramCounter();
|
Register pc = tb.language.getProgramCounter();
|
||||||
|
@ -933,7 +940,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateSnapTracks() throws Exception {
|
public void testActivateSnapTracks() throws Exception {
|
||||||
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
memBytesProvider.actionTrackLocation.getCurrentUserData());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
Register pc = tb.language.getProgramCounter();
|
Register pc = tb.language.getProgramCounter();
|
||||||
|
@ -961,7 +969,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateFrameTracks() throws Exception {
|
public void testActivateFrameTracks() throws Exception {
|
||||||
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
memBytesProvider.actionTrackLocation.getCurrentUserData());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
TraceThread thread;
|
TraceThread thread;
|
||||||
|
@ -989,7 +998,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRegsPCChangedTracks() throws Exception {
|
public void testRegsPCChangedTracks() throws Exception {
|
||||||
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
memBytesProvider.actionTrackLocation.getCurrentUserData());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
|
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
@ -1019,7 +1029,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRegsPCChangedTracksDespiteStackWithNoPC() throws Exception {
|
public void testRegsPCChangedTracksDespiteStackWithNoPC() throws Exception {
|
||||||
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
memBytesProvider.actionTrackLocation.getCurrentUserData());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
|
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
@ -1053,7 +1064,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStackPCChangedTracks() throws Exception {
|
public void testStackPCChangedTracks() throws Exception {
|
||||||
assertEquals(trackPc, memBytesProvider.actionTrackLocation.getCurrentUserData());
|
assertEquals(PCLocationTrackingSpec.INSTANCE,
|
||||||
|
memBytesProvider.actionTrackLocation.getCurrentUserData());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
DBTraceStackManager sm = tb.trace.getStackManager();
|
DBTraceStackManager sm = tb.trace.getStackManager();
|
||||||
|
|
|
@ -30,7 +30,6 @@ import com.google.common.collect.Range;
|
||||||
|
|
||||||
import generic.test.category.NightlyCategory;
|
import generic.test.category.NightlyCategory;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
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.action.NoneLocationTrackingSpec;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterDataSettingsDialog;
|
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterDataSettingsDialog;
|
||||||
|
@ -766,8 +765,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
||||||
addRegisterValues(thread);
|
addRegisterValues(thread);
|
||||||
addRegisterTypes(thread);
|
addRegisterTypes(thread);
|
||||||
// Ensure cause is goto PC, not register tracking
|
// Ensure cause is goto PC, not register tracking
|
||||||
listingPlugin.setTrackingSpec(
|
listingPlugin.setTrackingSpec(NoneLocationTrackingSpec.INSTANCE);
|
||||||
LocationTrackingSpec.fromConfigName(NoneLocationTrackingSpec.CONFIG_NAME));
|
|
||||||
activateThread(thread);
|
activateThread(thread);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ import java.nio.ByteBuffer;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
@ -135,7 +134,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
waitForSwing();
|
waitForWatches();
|
||||||
|
|
||||||
assertEquals("0x400000", row.getRawValueString());
|
assertEquals("0x400000", row.getRawValueString());
|
||||||
assertEquals("", row.getValueString()); // NB. No data type set
|
assertEquals("", row.getValueString()); // NB. No data type set
|
||||||
|
@ -153,8 +152,9 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
row.setExpression("r0");
|
row.setExpression("r0");
|
||||||
|
|
||||||
setRegisterValues(thread);
|
setRegisterValues(thread);
|
||||||
|
waitForWatches();
|
||||||
|
|
||||||
waitForPass(() -> assertEquals("0x400000", row.getRawValueString()));
|
assertEquals("0x400000", row.getRawValueString());
|
||||||
assertNoErr(row);
|
assertNoErr(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
waitForSwing();
|
waitForWatches();
|
||||||
|
|
||||||
assertEquals("0x400000", row.getRawValueString());
|
assertEquals("0x400000", row.getRawValueString());
|
||||||
assertEquals("400000h", row.getValueString());
|
assertEquals("400000h", row.getValueString());
|
||||||
|
@ -190,7 +190,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
watchesProvider.watchFilterPanel.setSelectedItem(row);
|
watchesProvider.watchFilterPanel.setSelectedItem(row);
|
||||||
waitForSwing();
|
waitForWatches();
|
||||||
|
|
||||||
performEnabledAction(watchesProvider, watchesProvider.actionApplyDataType, true);
|
performEnabledAction(watchesProvider, watchesProvider.actionApplyDataType, true);
|
||||||
|
|
||||||
|
@ -199,6 +199,12 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
assertEquals(FormatSettingsDefinition.DECIMAL, format.getChoice(u400000));
|
assertEquals(FormatSettingsDefinition.DECIMAL, format.getChoice(u400000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void waitForWatches() {
|
||||||
|
waitForSwing();
|
||||||
|
watchesProvider.waitEvaluate(1000);
|
||||||
|
waitForSwing();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWatchWithDataTypeSettings() {
|
public void testWatchWithDataTypeSettings() {
|
||||||
setRegisterValues(thread);
|
setRegisterValues(thread);
|
||||||
|
@ -210,7 +216,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
waitForSwing();
|
waitForWatches();
|
||||||
|
|
||||||
assertEquals("0x400000", row.getRawValueString());
|
assertEquals("0x400000", row.getRawValueString());
|
||||||
assertEquals("400000h", row.getValueString());
|
assertEquals("400000h", row.getValueString());
|
||||||
|
@ -236,7 +242,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
waitForSwing();
|
waitForWatches();
|
||||||
|
|
||||||
watchesProvider.watchFilterPanel.setSelectedItem(row);
|
watchesProvider.watchFilterPanel.setSelectedItem(row);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
@ -263,7 +269,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
waitForSwing();
|
waitForWatches();
|
||||||
|
|
||||||
assertEquals("0xdeadbeef", row.getRawValueString());
|
assertEquals("0xdeadbeef", row.getRawValueString());
|
||||||
assertEquals("DEADBEEFh", row.getValueString());
|
assertEquals("DEADBEEFh", row.getValueString());
|
||||||
|
@ -285,7 +291,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
waitForSwing();
|
waitForWatches();
|
||||||
|
|
||||||
assertEquals("0x400008", row.getRawValueString());
|
assertEquals("0x400008", row.getRawValueString());
|
||||||
assertEquals("400008h", row.getValueString());
|
assertEquals("400008h", row.getValueString());
|
||||||
|
@ -315,7 +321,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
traceManager.openTrace(trace);
|
traceManager.openTrace(trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
waitForSwing();
|
waitForWatches();
|
||||||
|
|
||||||
// Verify no target read has occurred yet
|
// Verify no target read has occurred yet
|
||||||
TraceMemorySpace regs =
|
TraceMemorySpace regs =
|
||||||
|
@ -331,15 +337,11 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
|
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
|
||||||
row.setExpression("*:4 r0");
|
row.setExpression("*:4 r0");
|
||||||
row.setDataType(LongDataType.dataType);
|
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);
|
assertNoErr(row);
|
||||||
|
assertEquals("{ 01 02 03 04 }", row.getRawValueString());
|
||||||
|
assertEquals("1020304h", row.getValueString());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void runTestIsEditableEmu(String expression, boolean expectWritable) {
|
protected void runTestIsEditableEmu(String expression, boolean expectWritable) {
|
||||||
|
@ -353,7 +355,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||||
waitForSwing();
|
waitForWatches();
|
||||||
|
|
||||||
assertNoErr(row);
|
assertNoErr(row);
|
||||||
assertFalse(row.isRawValueEditable());
|
assertFalse(row.isRawValueEditable());
|
||||||
|
@ -387,6 +389,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||||
|
waitForWatches();
|
||||||
|
|
||||||
performAction(watchesProvider.actionEnableEdits);
|
performAction(watchesProvider.actionEnableEdits);
|
||||||
|
|
||||||
|
@ -554,6 +557,8 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
performAction(watchesProvider.actionAdd);
|
performAction(watchesProvider.actionAdd);
|
||||||
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
|
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
|
||||||
row.setExpression(expression);
|
row.setExpression(expression);
|
||||||
|
waitForWatches();
|
||||||
|
|
||||||
performAction(watchesProvider.actionEnableEdits);
|
performAction(watchesProvider.actionEnableEdits);
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
|
@ -731,7 +736,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
rowR0.setDataType(PointerDataType.dataType);
|
rowR0.setDataType(PointerDataType.dataType);
|
||||||
registersProvider.setSelectedRow(rowR0);
|
registersProvider.setSelectedRow(rowR0);
|
||||||
});
|
});
|
||||||
waitForSwing();
|
waitForWatches();
|
||||||
|
|
||||||
performEnabledAction(registersProvider, watchesProvider.actionAddFromRegister, true);
|
performEnabledAction(registersProvider, watchesProvider.actionAddFromRegister, true);
|
||||||
|
|
||||||
|
@ -760,7 +765,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
row.setExpression("*:8 r0");
|
row.setExpression("*:8 r0");
|
||||||
|
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
waitForSwing();
|
waitForWatches();
|
||||||
|
|
||||||
assertEquals(symbol, row.getSymbol());
|
assertEquals(symbol, row.getSymbol());
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,13 @@ import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||||
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorState;
|
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorState;
|
||||||
import ghidra.pcode.utils.Utils;
|
import ghidra.pcode.utils.Utils;
|
||||||
import ghidra.program.model.lang.Register;
|
import ghidra.program.model.lang.Register;
|
||||||
|
import ghidra.program.model.lang.RegisterValue;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
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
|
* 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()));
|
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
|
@Test
|
||||||
public void testExecutorWrite() throws Throwable {
|
public void testExecutorWrite() throws Throwable {
|
||||||
createTestModel();
|
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
|
* @param data the trace-data access shim
|
||||||
*/
|
*/
|
||||||
protected DirectBytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
|
public DirectBytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
|
||||||
this(BytesPcodeArithmetic.forLanguage(data.getLanguage()), data);
|
this(BytesPcodeArithmetic.forLanguage(data.getLanguage()), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,4 +138,9 @@ public class DirectBytesTracePcodeExecutorStatePiece
|
||||||
public void writeDown(PcodeTraceDataAccess into) {
|
public void writeDown(PcodeTraceDataAccess into) {
|
||||||
// Writes directly, so just ignore
|
// 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) {
|
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
|
||||||
throw new ConcretionError("Cannot make TraceMemoryState into a concrete buffer", 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();
|
this.mm = platform.getTrace().getMemoryManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TraceTimeViewport getViewport() {
|
||||||
|
return viewport;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Language getLanguage() {
|
public Language getLanguage() {
|
||||||
return platform.getLanguage();
|
return platform.getLanguage();
|
||||||
|
@ -186,6 +191,15 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
|
||||||
return ops.getViewBytes(snap, toOverlay(hostStart), buf);
|
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
|
@Override
|
||||||
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
|
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
|
||||||
return new DefaultPcodeTracePropertyAccess<>(this, name, type);
|
return new DefaultPcodeTracePropertyAccess<>(this, name, type);
|
||||||
|
|
|
@ -46,12 +46,12 @@ public class DefaultPcodeTraceAccess extends AbstractPcodeTraceAccess //
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DefaultPcodeTraceMemoryAccess newDataForSharedState() {
|
protected DefaultPcodeTraceMemoryAccess newDataForSharedState() {
|
||||||
return new DefaultPcodeTraceMemoryAccess(platform, snap, viewport);
|
return new DefaultPcodeTraceMemoryAccess(platform, snap, viewport);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DefaultPcodeTraceRegistersAccess newDataForLocalState(TraceThread thread, int frame) {
|
protected DefaultPcodeTraceRegistersAccess newDataForLocalState(TraceThread thread, int frame) {
|
||||||
return new DefaultPcodeTraceRegistersAccess(platform, snap, thread, frame, viewport);
|
return new DefaultPcodeTraceRegistersAccess(platform, snap, thread, frame, viewport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,14 @@ public class DefaultPcodeTraceThreadAccess
|
||||||
return memory.getBytes(start, buf);
|
return memory.getBytes(start, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Address translate(Address address) {
|
||||||
|
if (address.isRegisterAddress()) {
|
||||||
|
return registers.translate(address);
|
||||||
|
}
|
||||||
|
return memory.translate(address);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
|
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
|
||||||
throw new UnsupportedOperationException("This is meant for p-code executor use");
|
throw new UnsupportedOperationException("This is meant for p-code executor use");
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package ghidra.pcode.exec.trace.data;
|
package ghidra.pcode.exec.trace.data;
|
||||||
|
|
||||||
import ghidra.lifecycle.Internal;
|
import ghidra.lifecycle.Internal;
|
||||||
|
import ghidra.trace.model.TraceTimeViewport;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
import ghidra.trace.model.guest.TracePlatform;
|
||||||
import ghidra.trace.model.property.TracePropertyMapOperations;
|
import ghidra.trace.model.property.TracePropertyMapOperations;
|
||||||
|
|
||||||
|
@ -27,4 +28,6 @@ public interface InternalPcodeTraceDataAccess extends PcodeTraceDataAccess {
|
||||||
|
|
||||||
<T> TracePropertyMapOperations<T> getPropertyOps(String name, Class<T> type,
|
<T> TracePropertyMapOperations<T> getPropertyOps(String name, Class<T> type,
|
||||||
boolean createIfAbsent);
|
boolean createIfAbsent);
|
||||||
|
|
||||||
|
TraceTimeViewport getViewport();
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,18 @@ public interface PcodeTraceAccess {
|
||||||
*/
|
*/
|
||||||
PcodeTraceRegistersAccess getDataForLocalState(TraceThread thread, int frame);
|
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
|
* Get the data-access shim for use in an executor having thread context
|
||||||
*
|
*
|
||||||
|
@ -123,7 +135,7 @@ public interface PcodeTraceAccess {
|
||||||
if (thread == null) {
|
if (thread == null) {
|
||||||
return getDataForSharedState();
|
return getDataForSharedState();
|
||||||
}
|
}
|
||||||
return new DefaultPcodeTraceThreadAccess(getDataForSharedState(),
|
return newPcodeTraceThreadAccess(getDataForSharedState(),
|
||||||
getDataForLocalState(thread, frame));
|
getDataForLocalState(thread, frame));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,14 @@ public interface PcodeTraceDataAccess {
|
||||||
*/
|
*/
|
||||||
int getBytes(Address start, ByteBuffer buf);
|
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
|
* Get a property-access shim for the named property
|
||||||
*
|
*
|
||||||
|
|
|
@ -291,6 +291,7 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public List<Range<Long>> getOrderedSpans() {
|
public List<Range<Long>> getOrderedSpans() {
|
||||||
try (LockHold hold = trace.lockRead()) {
|
try (LockHold hold = trace.lockRead()) {
|
||||||
synchronized (ordered) {
|
synchronized (ordered) {
|
||||||
|
|
|
@ -173,6 +173,9 @@ public interface TraceTimeViewport {
|
||||||
<T> AddressSet computeVisibleParts(AddressSetView set, Range<Long> lifespan, T object,
|
<T> AddressSet computeVisibleParts(AddressSetView set, Range<Long> lifespan, T object,
|
||||||
Occlusion<T> occlusion);
|
Occlusion<T> occlusion);
|
||||||
|
|
||||||
|
|
||||||
|
List<Range<Long>> getOrderedSpans();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the snaps involved in the view in most-recent-first order
|
* 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() {
|
public PcodeExecutorState<T> getLocalState() {
|
||||||
return localState;
|
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) {
|
public MemBuffer getConcreteBuffer(Address address, PcodeArithmetic.Purpose purpose) {
|
||||||
return new StateMemBuffer(address, getForSpace(address.getAddressSpace(), false));
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Address fromConst(BigInteger value, int size) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Address fromConst(long value, int size) {
|
public Address fromConst(long value, int size) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -80,7 +85,7 @@ public enum AddressOfPcodeArithmetic implements PcodeArithmetic<Address> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] toConcrete(Address value, Purpose purpose) {
|
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
|
@Override
|
||||||
|
|
|
@ -70,6 +70,7 @@ public class AddressOfPcodeExecutorStatePiece
|
||||||
if (!space.isUniqueSpace()) {
|
if (!space.isUniqueSpace()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// TODO: size is not considered
|
||||||
long lOffset = addressArithmetic.toLong(offset, Purpose.STORE);
|
long lOffset = addressArithmetic.toLong(offset, Purpose.STORE);
|
||||||
unique.put(lOffset, val);
|
unique.put(lOffset, val);
|
||||||
}
|
}
|
||||||
|
@ -88,4 +89,9 @@ public class AddressOfPcodeExecutorStatePiece
|
||||||
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
|
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
|
||||||
throw new ConcretionError("Cannot make 'address of' concrete buffers", 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);
|
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) {
|
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
|
||||||
return piece.getConcreteBuffer(address, 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) {
|
Reason reason) {
|
||||||
return piece.getVar(space, offset.getLeft(), size, quantize, 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>
|
* <p>
|
||||||
* To compose three or more states, first ask if it is really necessary. Second, consider
|
* 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
|
* implementing the {@link PcodeExecutorStatePiece} interface for a record type. Third, use the
|
||||||
* triple. In that third case, the implementor must decide which side has the nested tuple. Putting
|
* Church-style triple. In that third case, it is recommended to compose the nested pair on the
|
||||||
* it on the right keeps the concrete piece (conventionally on the left) in the most shallow
|
* right of the top pair: Compose the two right pieces into a single piece, then use
|
||||||
* position, so it can be accessed efficiently. However, putting it on the left (implying it's in
|
* {@link PairedPcodeExecutorState} to compose a concrete state with the composed piece, yielding a
|
||||||
* the deepest position) keeps the concrete piece near the other pieces to which it's most closely
|
* state of triples. This can be applied ad nauseam to compose arbitrarily large tuples; however, at
|
||||||
* bound. The latter goal is only important when the paired arithmetics mix information between
|
* a certain point clients should consider creating a record and implementing the state piece and/or
|
||||||
* their elements.
|
* 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
|
* @see PairedPcodeExecutorState
|
||||||
* @param <A> the type of offset, usually the type of a controlling state
|
* @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() {
|
public PcodeExecutorStatePiece<A, R> getRight() {
|
||||||
return right;
|
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) {
|
default long quantizeOffset(AddressSpace space, long offset) {
|
||||||
return space.truncateAddressableWordOffset(offset) * space.getAddressableUnitSize();
|
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) {
|
protected TaintVec getFromSpace(S space, long offset, int size, Reason reason) {
|
||||||
return space.get(offset, size);
|
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
|
* Retrieve the taint sets for the variable at the given offset
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This retrieves as many taint sets as there are elements in the given buffer vector. This first
|
* This retrieves as many taint sets as there are elements in the given buffer vector. This
|
||||||
* element becomes the taint set at the given offset, then each subsequent element becomes the
|
* first element becomes the taint set at the given offset, then each subsequent element becomes
|
||||||
* taint set at each subsequent offset until the vector is filled. This is analogous to the
|
* 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
|
* manner in which bytes would be "read" from concrete state, starting at a given offset, into a
|
||||||
* destination array.
|
* destination array.
|
||||||
*
|
*
|
||||||
|
@ -108,4 +108,8 @@ public class TaintSpace {
|
||||||
protected TaintSet whenNull(long offset) {
|
protected TaintSet whenNull(long offset) {
|
||||||
return TaintSet.EMPTY;
|
return TaintSet.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
taints.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package docking.action.builder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ import docking.menu.ActionState;
|
||||||
import docking.menu.MultiStateDockingAction;
|
import docking.menu.MultiStateDockingAction;
|
||||||
import docking.widgets.EventTrigger;
|
import docking.widgets.EventTrigger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder for {@link MultiStateDockingAction}
|
* Builder for {@link MultiStateDockingAction}
|
||||||
*
|
*
|
||||||
* @param <T> The action state type
|
* @param <T> The action state type
|
||||||
|
@ -38,9 +39,11 @@ public class MultiStateActionBuilder<T> extends
|
||||||
private boolean useCheckboxForIcons;
|
private boolean useCheckboxForIcons;
|
||||||
|
|
||||||
private List<ActionState<T>> states = new ArrayList<>();
|
private List<ActionState<T>> states = new ArrayList<>();
|
||||||
|
private Supplier<List<ActionState<T>>> generator = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder constructor
|
* Builder constructor
|
||||||
|
*
|
||||||
* @param name the name of the action to be built
|
* @param name the name of the action to be built
|
||||||
* @param owner the owner of the action to be build
|
* @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.
|
* 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.
|
* @param biConsumer the callback to execute when the selected action state is changed.
|
||||||
* @return this builder (for chaining)
|
* @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
|
* Overrides the default icons for actions shown in popup menu of the multi-state action.
|
||||||
* 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
|
* <p>
|
||||||
* checkbox icon will be used to show the active action state.
|
* 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
|
* @param b true to use a checkbox
|
||||||
* @return this MultiActionDockingActionBuilder (for chaining)
|
* @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 displayName the name to appear in the action menu
|
||||||
* @param icon the icon 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
|
* @param actionState the action state to add
|
||||||
* @return this MultiActionDockingActionBuilder (for chaining)
|
* @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;
|
* @param list a list of ActionStates;
|
||||||
* @return this MultiActionDockingActionBuilder (for chaining)
|
* @return this MultiActionDockingActionBuilder (for chaining)
|
||||||
|
@ -117,6 +124,21 @@ public class MultiStateActionBuilder<T> extends
|
||||||
return self();
|
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
|
@Override
|
||||||
public MultiStateDockingAction<T> build() {
|
public MultiStateDockingAction<T> build() {
|
||||||
validate();
|
validate();
|
||||||
|
@ -138,6 +160,16 @@ public class MultiStateActionBuilder<T> extends
|
||||||
super.actionPerformed(context);
|
super.actionPerformed(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<ActionState<T>> getStates() {
|
||||||
|
if (generator == null) {
|
||||||
|
return super.getStates();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return generator.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (ActionState<T> actionState : states) {
|
for (ActionState<T> actionState : states) {
|
||||||
|
|
|
@ -15,15 +15,17 @@
|
||||||
*/
|
*/
|
||||||
package docking.menu;
|
package docking.menu;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.SystemUtilities;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note: this class overrides the <code>equals(Object)</code> and relies upon the <code>equals</code>
|
* Note: this class overrides the <code>equals(Object)</code> and relies upon the
|
||||||
* method of the <code>userData</code> object. Thus, if it is important that equals work for you in
|
* <code>equals</code> method of the <code>userData</code> object. Thus, if it is important that
|
||||||
* the non-standard identity way, then you must override <code>equals</code> in your user data objects.
|
* 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
|
* @param <T> the type of the action state
|
||||||
*/
|
*/
|
||||||
|
@ -73,7 +75,7 @@ public class ActionState<T> {
|
||||||
|
|
||||||
ActionState<?> otherState = (ActionState<?>) other;
|
ActionState<?> otherState = (ActionState<?>) other;
|
||||||
|
|
||||||
if (!SystemUtilities.isEqual(userData, otherState.userData)) {
|
if (!Objects.equals(userData, otherState.userData)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,14 +31,18 @@ import help.Help;
|
||||||
import resources.icons.EmptyIcon;
|
import resources.icons.EmptyIcon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An action that can be in one of multiple states. The button of this action has a
|
* An action that can be in one of multiple states.
|
||||||
* 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.
|
* <p>
|
||||||
* Clients may also use the button of this action to respond to button presses by overriding
|
* 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)}.
|
* {@link #actionPerformed(ActionContext)}.
|
||||||
*
|
*
|
||||||
* <p>This action is intended primarily for use as toolbar actions. Alternatively, some clients
|
* <p>
|
||||||
* use this action to add a button to custom widgets. In the custom usage case, clients should use
|
* 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}.
|
* {@link NonToolbarMultiStateAction}.
|
||||||
*
|
*
|
||||||
* @param <T> the type of the user data
|
* @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 static Icon EMPTY_ICON = new EmptyIcon(16, 16);
|
||||||
|
|
||||||
private List<ActionState<T>> actionStates = new ArrayList<>();
|
private List<ActionState<T>> actionStates = new ArrayList<>();
|
||||||
private int currentStateIndex = -1;
|
private ActionState<T> currentState = null;
|
||||||
private MultiActionDockingActionIf multiActionGenerator;
|
private MultiActionDockingActionIf multiActionGenerator;
|
||||||
private MultipleActionDockingToolbarButton multipleButton;
|
private MultipleActionDockingToolbarButton multipleButton;
|
||||||
|
|
||||||
|
@ -57,11 +61,10 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
||||||
private boolean useCheckboxForIcons;
|
private boolean useCheckboxForIcons;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call this constructor with this action will not be added to a toolbar
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param name the action name
|
* @param name the action name
|
||||||
* @param owner the owner
|
* @param owner the owner
|
||||||
* @see #MultiStateDockingAction(String, String, boolean)
|
|
||||||
*/
|
*/
|
||||||
public MultiStateDockingAction(String name, String owner) {
|
public MultiStateDockingAction(String name, String owner) {
|
||||||
super(name, owner);
|
super(name, owner);
|
||||||
|
@ -71,22 +74,9 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
||||||
super.setToolBarData(new ToolBarData(null));
|
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
|
* This method will be called as the user changes the selected button state
|
||||||
|
*
|
||||||
* @param newActionState the newly selected state
|
* @param newActionState the newly selected state
|
||||||
* @param trigger the source of the event
|
* @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
|
* 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
|
* <p>
|
||||||
* presses that are on the button and not the drop-down. The default behavior is to show the
|
* This is the callback to be overridden when the child wishes to respond to user button presses
|
||||||
* popup menu when the button is clicked.
|
* 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
|
@Override
|
||||||
public void actionPerformed(ActionContext context) {
|
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
|
* Overrides the default icons for actions shown in popup menu of the multi-state action.
|
||||||
* 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
|
* <p>
|
||||||
* checkbox icon will be used to show the active action state.
|
* 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
|
* @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
|
* Sets the icon to use if the active action state does not supply an icon.
|
||||||
* you wish for your action states to not use icon, but desire the action itself to have 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
|
* @param icon the icon
|
||||||
*/
|
*/
|
||||||
|
@ -128,18 +123,38 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
||||||
this.defaultIcon = icon;
|
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() {
|
protected List<DockingActionIf> getStateActions() {
|
||||||
ActionState<T> selectedState = actionStates.get(currentStateIndex);
|
updateStates();
|
||||||
List<DockingActionIf> actions = new ArrayList<>(actionStates.size());
|
List<DockingActionIf> actions = new ArrayList<>(actionStates.size());
|
||||||
for (ActionState<T> actionState : actionStates) {
|
for (ActionState<T> actionState : actionStates) {
|
||||||
|
boolean isSelected = actionState.equals(currentState);
|
||||||
//@formatter:off
|
DockingActionIf a = useCheckboxForIcons
|
||||||
boolean isSelected = actionState == selectedState;
|
? new ActionStateToggleAction(actionState, isSelected)
|
||||||
DockingActionIf a = useCheckboxForIcons ?
|
: new ActionStateAction(actionState, isSelected);
|
||||||
new ActionStateToggleAction(actionState, isSelected) :
|
|
||||||
new ActionStateAction(actionState, isSelected);
|
|
||||||
actions.add(a);
|
actions.add(a);
|
||||||
//@formatter:on
|
|
||||||
}
|
}
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
@ -155,8 +170,8 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add the supplied {@code ActionState}
|
* Add the supplied {@code ActionState}.
|
||||||
* if {@code fireFirstEvent} is {@code true} the first one will fire its event
|
*
|
||||||
* @param actionState the {@code ActionState} to add
|
* @param actionState the {@code ActionState} to add
|
||||||
*/
|
*/
|
||||||
public void addActionState(ActionState<T> actionState) {
|
public void addActionState(ActionState<T> actionState) {
|
||||||
|
@ -175,11 +190,11 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
public T getCurrentUserData() {
|
public T getCurrentUserData() {
|
||||||
return actionStates.get(currentStateIndex).getUserData();
|
return currentState == null ? null : currentState.getUserData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionState<T> getCurrentState() {
|
public ActionState<T> getCurrentState() {
|
||||||
return actionStates.get(currentStateIndex);
|
return currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ActionState<T>> getAllActionStates() {
|
public List<ActionState<T>> getAllActionStates() {
|
||||||
|
@ -187,6 +202,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentActionStateByUserData(T t) {
|
public void setCurrentActionStateByUserData(T t) {
|
||||||
|
updateStates();
|
||||||
for (ActionState<T> actionState : actionStates) {
|
for (ActionState<T> actionState : actionStates) {
|
||||||
|
|
||||||
// Note: most clients will pass a T that is already in our list. However, to be more
|
// 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.
|
// problem using equals() here.
|
||||||
// if (actionState.getUserData() == t) {
|
// if (actionState.getUserData() == t) {
|
||||||
if (actionState.getUserData().equals(t)) {
|
if (actionState.getUserData().equals(t)) {
|
||||||
setCurrentActionState(actionState);
|
doSetCurrentActionState(actionState, EventTrigger.API_CALL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,13 +224,16 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentActionStateWithTrigger(ActionState<T> actionState, EventTrigger trigger) {
|
public void setCurrentActionStateWithTrigger(ActionState<T> actionState, EventTrigger trigger) {
|
||||||
int indexOf = actionStates.indexOf(actionState);
|
updateStates();
|
||||||
if (indexOf < 0) {
|
doSetCurrentActionState(actionState, trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doSetCurrentActionState(ActionState<T> actionState, EventTrigger trigger) {
|
||||||
|
if (!actionStates.contains(actionState)) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Attempted to set actionState to unknown ActionState.");
|
"Attempted to set actionState to unknown ActionState.");
|
||||||
}
|
}
|
||||||
currentStateIndex = indexOf;
|
currentState = actionState;
|
||||||
|
|
||||||
setButtonState(actionState);
|
setButtonState(actionState);
|
||||||
|
|
||||||
ToolBarData tbd = getToolBarData();
|
ToolBarData tbd = getToolBarData();
|
||||||
|
@ -240,9 +259,8 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
|
||||||
@Override
|
@Override
|
||||||
public JButton doCreateButton() {
|
public JButton doCreateButton() {
|
||||||
multipleButton = new MultipleActionDockingToolbarButton(multiActionGenerator);
|
multipleButton = new MultipleActionDockingToolbarButton(multiActionGenerator);
|
||||||
if (currentStateIndex >= 0) {
|
if (currentState != null) {
|
||||||
ActionState<T> actionState = actionStates.get(currentStateIndex);
|
setButtonState(currentState);
|
||||||
setButtonState(actionState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return multipleButton;
|
return multipleButton;
|
||||||
|
|
Loading…
Reference in a new issue