mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-22 05:29:01 +08:00
GP-6787: Add 'Emulate Function' action and dialog.
This commit is contained in:
+95
@@ -0,0 +1,95 @@
|
||||
/* ###
|
||||
* 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.debug.api.modules;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
|
||||
public class IdentityDebuggerAddressTranslator implements DebuggerAddressTranslator {
|
||||
private final Trace trace;
|
||||
private final Program program;
|
||||
|
||||
public IdentityDebuggerAddressTranslator(Trace trace, Program program) {
|
||||
this.trace = trace;
|
||||
this.program = program;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Program> getOpenMappedProgramsAtSnap(Trace trace, long snap) {
|
||||
return Set.of(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramLocation getOpenMappedLocation(TraceLocation loc) {
|
||||
return new ProgramLocation(program, loc.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramLocation getStaticLocationFromDynamic(ProgramLocation loc) {
|
||||
return new ProgramLocation(program, loc.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<TraceLocation> getOpenMappedLocations(ProgramLocation loc) {
|
||||
return Set.of(new DefaultTraceLocation(trace, null, Lifespan.ALL, loc.getAddress()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceLocation getOpenMappedLocation(Trace trace, ProgramLocation loc, long snap) {
|
||||
if (trace != this.trace) {
|
||||
return null;
|
||||
}
|
||||
return new DefaultTraceLocation(trace, null, Lifespan.ALL, loc.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramLocation getDynamicLocationFromStatic(TraceProgramView view,
|
||||
ProgramLocation loc) {
|
||||
if (view.getTrace() != this.trace) {
|
||||
return null;
|
||||
}
|
||||
return new ProgramLocation(view, loc.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Program, Collection<MappedAddressRange>> getOpenMappedViews(Trace trace,
|
||||
AddressSetView set, long snap) {
|
||||
return Map.ofEntries(
|
||||
Map.entry(program, set.stream().map(r -> new MappedAddressRange(r, r)).toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TraceSpan, Collection<MappedAddressRange>> getOpenMappedViews(Program program,
|
||||
AddressSetView set) {
|
||||
if (program != this.program) {
|
||||
return Map.of();
|
||||
}
|
||||
return Map.ofEntries(Map.entry(new DefaultTraceSpan(trace, Lifespan.ALL),
|
||||
set.stream().map(r -> new MappedAddressRange(r, r)).toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<URL> getMappedProgramUrlsInView(Trace trace, AddressSetView set, long snap) {
|
||||
// This is not necessary here
|
||||
return Set.of();
|
||||
}
|
||||
}
|
||||
+4
@@ -239,6 +239,10 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
|
||||
createActions();
|
||||
}
|
||||
|
||||
public void cleanConnectionNode(TraceRmiConnectionNode node) {
|
||||
rootNode.cleanConnectionNode(node);
|
||||
}
|
||||
|
||||
private void buildMainPanel() {
|
||||
mainPanel = new JPanel(new BorderLayout());
|
||||
|
||||
|
||||
+14
-3
@@ -42,7 +42,13 @@ public class TraceRmiConnectionNode extends AbstractTraceRmiManagerNode {
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
return connection.getDescription() + " at " + connection.getRemoteAddress();
|
||||
try {
|
||||
return connection.getDescription() + " at " + connection.getRemoteAddress();
|
||||
}
|
||||
catch (Exception e) {
|
||||
provider.cleanConnectionNode(this);
|
||||
return "Error: " + e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,8 +58,13 @@ public class TraceRmiConnectionNode extends AbstractTraceRmiManagerNode {
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return "Trace RMI Connection to " + connection.getDescription() + " at " +
|
||||
connection.getRemoteAddress();
|
||||
try {
|
||||
return "Trace RMI Connection to " + connection.getDescription() + " at " +
|
||||
connection.getRemoteAddress();
|
||||
}
|
||||
catch (Exception e) {
|
||||
return "Error: " + e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+10
@@ -25,6 +25,7 @@ import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.target.TargetPublicationListener;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
@@ -83,6 +84,15 @@ public class TraceRmiServiceNode extends AbstractTraceRmiManagerNode
|
||||
removeNode(node);
|
||||
}
|
||||
|
||||
@Internal
|
||||
public void cleanConnectionNode(TraceRmiConnectionNode node) {
|
||||
TraceRmiConnection connection = node.getConnection();
|
||||
synchronized (connectionNodes) {
|
||||
connectionNodes.remove(connection);
|
||||
}
|
||||
removeNode(node);
|
||||
}
|
||||
|
||||
private TraceRmiAcceptorNode newAcceptorNode(TraceRmiAcceptor acceptor) {
|
||||
return new TraceRmiAcceptorNode(provider, acceptor);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ src/main/help/help/topics/DebuggerCopyActionsPlugin/DebuggerCopyActionsPlugin.ht
|
||||
src/main/help/help/topics/DebuggerCopyActionsPlugin/images/DebuggerCopyIntoProgramDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerDisassemblerPlugin/DebuggerDisassemblerPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerEmulationServicePlugin/DebuggerEmulationServicePlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerEmulationServicePlugin/images/DebuggerEmulateFunctionDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerListingPlugin.png||GHIDRA||||END|
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.debug.flatapi.FlatDebuggerAPI;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
|
||||
|
||||
//List all address ranges that are "known" at the current snapshot
|
||||
//This script must be run from the Debugger tool, or another tool with the required plugins.
|
||||
//@category Debugger
|
||||
//@keybinding
|
||||
//@menupath
|
||||
//@toolbar
|
||||
public class ListAllKnownMemoryScript extends GhidraScript implements FlatDebuggerAPI {
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
Trace trace = requireCurrentTrace();
|
||||
long snap = getCurrentSnap();
|
||||
|
||||
AddressSetView addresses =
|
||||
trace.getMemoryManager().getAddressesWithState(snap, StatePredicate.IS_KNOWN);
|
||||
show("Known Memory at %d".formatted(snap), addresses);
|
||||
}
|
||||
}
|
||||
+189
@@ -48,6 +48,12 @@
|
||||
Optionally, other registers can be initialized via the UI or a script. The new thread is
|
||||
activated so that control actions will affect it by default.</P>
|
||||
|
||||
<H3><A name="emulate_function"></A> Emulate Function</H3>
|
||||
|
||||
<P>This action is available whenever the cursor is within a function in the Static Listing. It
|
||||
displays a <A href="#emulate_function_dialog">dialog</A> for harnessing and emulating the
|
||||
current function.</P>
|
||||
|
||||
<H3><A name="configure_emulator"></A> Configure Emulator</H3>
|
||||
|
||||
<P>This action is always available. It lists emulators available for configuration. Selecting
|
||||
@@ -62,5 +68,188 @@
|
||||
emulated breakpoint, or when you patch the trace database. If you do not invalidate the cache,
|
||||
the effects of your change may not appear, since the trace manager may recall a cached snapshot
|
||||
instead of actually emulating.</P>
|
||||
|
||||
<H2><A name="emulate_function_dialog"></A> Emulate Function Dialog</H2>
|
||||
|
||||
<P>This dialog provides a means of harnessing and emulating a target function. Depending on the
|
||||
size and scope of the function and the configuration of the emulator, the emulation may or may
|
||||
not complete. See further below for options and actions in the dialog.</P>
|
||||
|
||||
<DIV class="image">
|
||||
<IMG alt="" src="images/DebuggerEmulateFunctionDialog.png">
|
||||
</DIV>
|
||||
|
||||
<P>The dialog supports the addition of vararg arguments, custom initializations, and heap
|
||||
initializations. All of these inputs are handled in the top table. The "Run" button will
|
||||
perform the emulation and capture the outputs into the bottom table. Analogous to the inputs,
|
||||
the Outputs table can be configured with custom variables and pointer dereferences. The
|
||||
emulation session can optionally be captured into a trace, which is opened automatically in
|
||||
your tool, usually the Debugger or Emulator. Multiple sessions can be run from the same dialog,
|
||||
allowing trial-and-error runs while also iterating on the target function's markup.</P>
|
||||
|
||||
<H3>Option: Ending Return Address</H3>
|
||||
|
||||
<P>In order to detect the proper completion of the target function, the harness places a
|
||||
breakpoint at a "sentinel" address. That sentinel address must be placed where the function
|
||||
expects the return address. This option allows specification of the sentinel address.
|
||||
Generally, anything easily recognizable that does not conflict with a real address should work.
|
||||
<B>NOTE:</B> You cannot specify the location of the return address. This is determined
|
||||
automatically through static analysis, but you can place the sentinel address through a custom
|
||||
input, if the analysis fails or is incorrect.</P>
|
||||
|
||||
<H3>Option: Snapshot Period</H3>
|
||||
|
||||
<P>By default, the harness captures a snapshot of the emulator's state into a trace after every
|
||||
instruction step. This feature can be disabled entirely by setting this option to 0 (zero), in
|
||||
which case no trace is captured or opened in your tool. Only the outputs are captured.
|
||||
Otherwise, the period indicates how many instructions are executed between consecutive
|
||||
snapshots. If only a starting and ending snapshot are desired, set this to a large number. The
|
||||
initial and final snapshots are always captured, no matter the (non-zero) period.</P>
|
||||
|
||||
<H3><A name="next_alloc"></A> Option: Next Allocation</H3>
|
||||
|
||||
<P>Set this to the location of the heap, if applicable. Any address not already used by the
|
||||
program with plenty of space above should suffice. It may help if the upper digits of the
|
||||
address are easily recognizable. Several of the below actions on the Inputs table will
|
||||
automatically initialize pointers to the start of an "allocated block". This field is then
|
||||
automatically incremented by the size of that allocation. You may adjust this field at any time
|
||||
to "undo" allocations, establish a second heap, etc.</P>
|
||||
|
||||
<H3>Inputs table</H3>
|
||||
|
||||
<P>The Inputs table lists of all the configured inputs. The types and values of the inputs are
|
||||
specified in this table. <B>NOTE:</B> No actual initializations are performed until right
|
||||
before emulation. If two inputs happen to be at the same address, that conflict will not be
|
||||
discovered until clicking "Run." No two inputs can have the same name. Adding an input with a
|
||||
duplicate name replaces the existing one.</P>
|
||||
|
||||
<P>The default inputs are derived from the target function's parameters. Adjusting the type of
|
||||
an input <EM>does not</EM> edit or update the target function in the program database. The
|
||||
columns are:</P>
|
||||
|
||||
<UL>
|
||||
<LI>Name — Provides an informal name for the input. For parameters, this is the name of
|
||||
the parameter. For custom inputs, this is "Custom 1" counting up in the order added. For
|
||||
vararg arguments, this is, e.g., "Vararg 3" couting up in the order added, starting after the
|
||||
number of fixed arguments. For allocated inputs, this is a C-like expression derived from the
|
||||
pointer to it and its type. This field <EM>cannot</EM> be edited.</LI>
|
||||
|
||||
<LI>Storage — Provides a formal Sleigh expression describing the location of the input.
|
||||
For custom inputs, this is specified by the user when added. For parameter and vararg inputs,
|
||||
this is derived from the target function's signature, custom storage, and/or calling
|
||||
convention. For pointer allocations, this is derived from the pointer to it and its type.
|
||||
This field <EM>cannot</EM> be edited.</LI>
|
||||
|
||||
<LI>Value — Controls the raw initial value of the input variable. This can be specified
|
||||
as a positive integer, e.g., <CODE>0x1234</CODE>, so long as it fits in the storage, or as a
|
||||
byte sequence, e.g., <CODE>{ 34 12 }</CODE>, so long as its length matches that of the
|
||||
storage.</LI>
|
||||
|
||||
<LI>Type — Controls the type of the input variable. This has no direct impact on the
|
||||
emulation nor on the program database (except to resolve the type), but permits the initial
|
||||
value to be specified using a representation suitable to that type.</LI>
|
||||
|
||||
<LI>Repr — Controls the initial value of the input variable as represented by the
|
||||
chosen type. <B>NOTE:</B> some types, particularly structure types, do not have a string
|
||||
representation, and so this box may appear empty. This field can be edited only if the chosen
|
||||
type implements an encoder.<BR>
|
||||
<BR>
|
||||
</LI>
|
||||
</UL>
|
||||
|
||||
<H3>Action: Remove Input</H3>
|
||||
|
||||
<P>This action removes the selected input(s) from the table. A removed input is no longer
|
||||
initialized. <B>NOTE:</B> Inputs can have dependencies, e.g., an allocated block depends on its
|
||||
pointer, because the storage location of that block is computed from the value of the pointer.
|
||||
Removing the pointer will leave it uninitialized, likely resulting in the block's location
|
||||
being 0 (zero). This will generally still work, but is usually not desired. Re-adding the
|
||||
relevant pointer can fix this. So long as the old and new names match, the dependency
|
||||
relationship is restored, too.</P>
|
||||
|
||||
<H3>Action: Clear Inputs</H3>
|
||||
|
||||
<P>For when things have gone so far south you need to start over. Typically this is followed by
|
||||
a Refresh, too.</P>
|
||||
|
||||
<H3>Action: Add Custom Input</H3>
|
||||
|
||||
<P>This action prompts the user for a Sleigh expression of a variable's storage and adds it as
|
||||
a Custom input. Typically, this is a register, e.g., <CODE>RAX</CODE>, or a fixed address and
|
||||
size, e.g., <CODE>*:8 0x00401234</CODE>.</P>
|
||||
|
||||
<H3>Action: Add Vararg Input</H3>
|
||||
|
||||
<P>This action is only enabled if the target function has variable arguments. It prompts the
|
||||
user for a type, derives the storage from the target function's signature and calling convetion
|
||||
and adds it as a Vararg input. <B>NOTE:</B> Adjustments to this row's type <EM>will not</EM>
|
||||
automatically update the argument's storage. Consider deleting and re-adding an argument along
|
||||
with all its subsequent arguments if consistent storage is desired.</P>
|
||||
|
||||
<H3>Action: Allocate and Add Pointer Inputs</H3>
|
||||
|
||||
<P>This action is available when the selected input has a pointer type. (Multiple selections
|
||||
are supported.) The size of the pointed-to type is calculated; the <EM>pointer's</EM> value is
|
||||
initialized to the <A href="#next_alloc">Next Allocation</A>, which is then incremented by the
|
||||
size of the pointed-to type. A new input row is then added and selected, describing the
|
||||
variable pointed to by the formerly-selected input. Under normal operation, this allocates a
|
||||
single instance of the pointed-to type. To instead allocate an array of the pointed-to type,
|
||||
hold <B><Shift></B> when clicking the button. If the pointed-to type is a composite
|
||||
(struct or union), a separate row is generated for each field. For the array case, yes, this
|
||||
results in an <EM>n</EM>-by-<EM>m</EM> set of new rows. If the pointed-to type is a string (or
|
||||
<CODE>char</CODE>), hold <B><Ctrl></B> when clicking the button. This will prompt the
|
||||
user for string data type settings and then for the initial string value. It encodes it and
|
||||
allocates sufficient space for the encoding. <B>NOTE:</B> So long as edits to the Repr column
|
||||
result in encodings of smaller or equal size, no re-allocation is necessary. The table will
|
||||
still permit the edit, but it may result in conflicts. Either update the pointer values
|
||||
manually — this will automatically adjust the storage location of the pointed-to variable
|
||||
— or delete and re-allocate the string input.</P>
|
||||
|
||||
<H3>Action: Refresh Inputs</H3>
|
||||
|
||||
<P>This re-adds (likely replacing) all the inputs derived from the function's parameters. This
|
||||
is especially useful when trying to derive a function's signature by trial and error. Clicking
|
||||
this button after editing a function's signature will update the Parameter inputs accordingly.
|
||||
<B>NOTE:</B> This will not <EM>remove</EM> any Parameter inputs.</P>
|
||||
|
||||
<H3>Action: Type Settings</H3>
|
||||
|
||||
<P>This action is available in the right-click context menu on rows with an assigned type. It
|
||||
controls the settings on the chosen datatype, e.g., radix for integer types, or encodings for
|
||||
string types.</P>
|
||||
|
||||
<H3>Outputs table</H3>
|
||||
|
||||
<P>The Outputs table is analogous to the Inputs table, except that it displays values captured
|
||||
from the last successful emulation. The default output is derived from the target function's
|
||||
return type. It has the same columns and similar actions with the following exceptions:</P>
|
||||
|
||||
<UL>
|
||||
<LI>There is no <B>Add Vararg Output</B> action.</LI>
|
||||
|
||||
<LI>Only the Type column can be edited.</LI>
|
||||
|
||||
<LI>Adding Pointed-to outputs does not "allocate," since the pointer is expected to be set by
|
||||
the target function.</LI>
|
||||
|
||||
<LI>When adding pointed-to strings, the prompt is for the number of bytes to capture. The
|
||||
string decoder should only use what it needs, so just pick a reasonable maximum expected
|
||||
length. Adjust the type and settings using other actions afterward.</LI>
|
||||
</UL>
|
||||
|
||||
<H3>Probe Outputs</H3>
|
||||
|
||||
<P>The Outputs table also supports "Probe" outputs. These are outputs automatically generated
|
||||
during the emulation of the target function via the <CODE>emu_probe</CODE> userop. These are
|
||||
configured using the <A href=
|
||||
"help/topics/DebuggerBreakpointsPlugin/DebuggerBreakpointsPlugin.html#set_injection">Set
|
||||
Injection</A> action on a breakpoint. The breakpoint need only be enabled. Even if
|
||||
"ineffective,"" it will be installed by the harness. The <CODE>emu_probe</CODE> userop accepts
|
||||
exactly one argument. All Probe outputs are cleared at the start of emulation. Each time the
|
||||
userop is executed, its argument is captured with its location and value <EM>at the time of
|
||||
invocation</EM>. These are then added as Probe outputs, counting 1-up by invocation. If the
|
||||
userop is invoked within a loop of the target code, each run-time invocation gets a distinct
|
||||
row. Types can be applied as usual. <B>NOTE:</B> If the userop argument is anything other than
|
||||
a simple varnode, the location will be "$Unique."</P>
|
||||
</BODY>
|
||||
</HTML>
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
+1
-1
@@ -191,7 +191,7 @@
|
||||
debugger. This failure can be overcome by enabling <A href=
|
||||
"help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html">Force Full View</A>.</P>
|
||||
|
||||
<P>Some examples (in <b>Hex</b> mode):</P>
|
||||
<P>Some examples (in <B>Hex</B> mode):</P>
|
||||
|
||||
<UL>
|
||||
<LI><CODE>00401234</CODE> — A constant address</LI>
|
||||
|
||||
+1
-1
@@ -172,7 +172,7 @@ public enum BasicAutoReadMemorySpec implements AutoReadMemorySpec {
|
||||
AddressSet toRead = new AddressSet(quantize(12, visible));
|
||||
for (Lifespan span : coordinates.getView().getViewport().getOrderedSpans()) {
|
||||
AddressSetView alreadyKnown =
|
||||
mm.getAddressesWithState(span.lmin(), visible, StatePredicate.IS_KNOWN);
|
||||
mm.getAddressesWithState(span, toRead, StatePredicate.IS_KNOWN);
|
||||
toRead.delete(alreadyKnown);
|
||||
if (span.lmax() != span.lmin() || toRead.isEmpty()) {
|
||||
break;
|
||||
|
||||
+9
-31
@@ -1339,38 +1339,16 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isAllInvolvedTracesUsingEmulatedBreakpoints(ActionContext ctx) {
|
||||
if (controlService == null) {
|
||||
return false;
|
||||
}
|
||||
Set<Trace> traces = new HashSet<>();
|
||||
private boolean isAtLeastOneBreakpoint(ActionContext ctx) {
|
||||
Collection<LogicalBreakpoint> breakpoints = getLogicalBreakpoints(ctx);
|
||||
if (breakpoints != null) {
|
||||
if (breakpoints.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (LogicalBreakpoint lb : breakpoints) {
|
||||
traces.addAll(lb.getParticipatingTraces());
|
||||
}
|
||||
if (breakpoints != null && !breakpoints.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
else if (ctx instanceof DebuggerBreakpointLocationsActionContext locCtx) {
|
||||
Collection<TraceBreakpointLocation> locations = locCtx.getLocations();
|
||||
if (locations.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (TraceBreakpointLocation loc : locations) {
|
||||
traces.add(loc.getTrace());
|
||||
}
|
||||
if (ctx instanceof DebuggerBreakpointLocationsActionContext locCtx &&
|
||||
!locCtx.getLocations().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
for (Trace trace : traces) {
|
||||
if (!controlService.getCurrentMode(trace).useEmulatedBreakpoints()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final Set<TraceBreakpointKind> EXECUTE_KINDS =
|
||||
@@ -1400,11 +1378,11 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
|
||||
}
|
||||
|
||||
private boolean isPopupSetCondition(ActionContext ctx) {
|
||||
return isAllInvolvedTracesUsingEmulatedBreakpoints(ctx) && isAllBreakpointsExecution(ctx);
|
||||
return isAtLeastOneBreakpoint(ctx) && isAllBreakpointsExecution(ctx);
|
||||
}
|
||||
|
||||
private boolean isPopupSetInjection(ActionContext ctx) {
|
||||
return isAllInvolvedTracesUsingEmulatedBreakpoints(ctx) && isAllBreakpointsExecution(ctx);
|
||||
return isAtLeastOneBreakpoint(ctx) && isAllBreakpointsExecution(ctx);
|
||||
}
|
||||
|
||||
private String deriveCurrentSleigh(ActionContext ctx) {
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressFactory;
|
||||
|
||||
public abstract class AddressInputVerifier extends InputVerifier {
|
||||
private AddressFactory factory;
|
||||
|
||||
public AddressInputVerifier(AddressFactory factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(JComponent input) {
|
||||
if (!(input instanceof JTextField text)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Only JTextField is supported. Got %s".formatted(input.getClass()));
|
||||
}
|
||||
String str = text.getText();
|
||||
try {
|
||||
Address address = factory.getAddress(str);
|
||||
if (address == null) {
|
||||
reject("Invalid address: %s".formatted(str));
|
||||
return false;
|
||||
}
|
||||
if (!verifyAddress(address)) {
|
||||
reject("Invalid address: %s".formatted(str));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
reject("%s while parsing '%s'".formatted(e.getMessage(), str));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract boolean verifyAddress(Address address);
|
||||
|
||||
protected abstract void reject(String message);
|
||||
}
|
||||
+1148
File diff suppressed because it is too large
Load Diff
+346
@@ -0,0 +1,346 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import db.Transaction;
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.core.debug.gui.emulation.LocAndVal.WriterAndExecutor;
|
||||
import ghidra.app.plugin.core.debug.service.breakpoint.ProgramBreakpoint;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationIntegration;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.data.TranslatedPcodeDebuggerAccess;
|
||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingContext;
|
||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingContext.ChangeCollector;
|
||||
import ghidra.app.plugin.core.debug.stack.*;
|
||||
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.debug.api.breakpoint.LogicalBreakpoint;
|
||||
import ghidra.debug.api.emulation.PcodeDebuggerAccess;
|
||||
import ghidra.debug.api.modules.DebuggerAddressTranslator;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.pcode.emu.*;
|
||||
import ghidra.pcode.eval.ArithmeticVarnodeEvaluator;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.NumericUtilities;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class FunctionEmulationHarness implements AutoCloseable {
|
||||
|
||||
public record ProbeOut(Varnode vn, byte[] value) {
|
||||
public String toString(Language language) {
|
||||
return "%s = %s (%s)".formatted(vn.toString(language),
|
||||
NumericUtilities.convertBytesToString(value, ":"),
|
||||
Utils.bytesToBigInteger(value, vn.getSize(), language.isBigEndian(), false));
|
||||
}
|
||||
}
|
||||
|
||||
public record ReturnAddressInfo(Address location, long mask) {
|
||||
public Address computePhysicalLocation(Eval eval, CompilerSpec cSpec) {
|
||||
if (location.isMemoryAddress() || location.isRegisterAddress()) {
|
||||
return location;
|
||||
}
|
||||
if (location.isStackAddress()) {
|
||||
Register sp = cSpec.getStackPointer();
|
||||
LocAndVal spVal = eval.state.getVar(sp, Reason.INSPECT);
|
||||
Address stackBase = eval.state.getArithmetic()
|
||||
.toAddress(spVal, cSpec.getStackBaseSpace(), Purpose.INSPECT);
|
||||
return stackBase.add(location.getOffset());
|
||||
}
|
||||
throw new IllegalStateException("Unknown space for return address location");
|
||||
}
|
||||
}
|
||||
|
||||
public ReturnAddressInfo locateReturnAddress() {
|
||||
UnwindAnalysis unwindAnalysis = new UnwindAnalysis(program);
|
||||
try {
|
||||
UnwindInfo unwindInfo = unwindAnalysis.getUnwindInfo(function.getEntryPoint(), monitor);
|
||||
return new ReturnAddressInfo(unwindInfo.ofReturn(), unwindInfo.maskOfReturn());
|
||||
}
|
||||
catch (UnwindException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static FunctionEmulationHarness start(PluginTool tool, Function function,
|
||||
TaskMonitor monitor) throws IOException {
|
||||
ManagedDomainObject<Trace> mt = new ManagedDomainObject<Trace>(mdo -> ProgramEmulationUtils
|
||||
.launchEmulationTrace(function.getProgram(), function.getEntryPoint(), mdo));
|
||||
return new FunctionEmulationHarness(tool, mt, function, monitor);
|
||||
}
|
||||
|
||||
public class Eval {
|
||||
public final long snap;
|
||||
public final Writer writer;
|
||||
public final PcodeExecutor<LocAndVal> exec;
|
||||
public final PcodeExecutorState<LocAndVal> state;
|
||||
|
||||
private Eval(long snap) {
|
||||
this.snap = snap;
|
||||
WriterAndExecutor we = LocAndVal.buildExecutor(tool, start.snap(snap));
|
||||
this.writer = we.writer();
|
||||
this.exec = we.executor();
|
||||
this.state = exec.getState();
|
||||
}
|
||||
|
||||
public void writeNode(VarStorageNode node, LocAndVal value) {
|
||||
LocAndVal cur = node.compile(language).evaluate(exec);
|
||||
exec.getState().setVar(cur.loc().getAddress(), node.size(), false, value);
|
||||
}
|
||||
|
||||
public void writeVariable(VarStorage storage, LocAndVal value) {
|
||||
int shift = 0;
|
||||
for (VarStorageNode n : storage.nodes()) {
|
||||
LocAndVal piece = lvArith.binaryOp(PcodeOp.INT_RIGHT, n.size(),
|
||||
value.value().length, value, 4, lvArith.fromConst(shift, 4));
|
||||
shift += n.size();
|
||||
writeNode(n, piece);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeVariable(VarStorage storage, byte[] value) {
|
||||
writeVariable(storage, lvArith.fromConst(value));
|
||||
}
|
||||
|
||||
public void commit() {
|
||||
writer.writeDown(snap);
|
||||
}
|
||||
|
||||
public LocAndVal readNode(VarStorageNode node) {
|
||||
return node.compile(language).evaluate(exec);
|
||||
}
|
||||
|
||||
public LocAndVal readVariable(VarStorage storage) {
|
||||
int total = storage.size();
|
||||
LocAndVal value = lvArith.fromConst(0, total);
|
||||
for (VarStorageNode n : storage.nodes()) {
|
||||
LocAndVal piece = readNode(n);
|
||||
value = ArithmeticVarnodeEvaluator.catenate(lvArith, total, value, piece, n.size());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
class MonitoredCallbacks extends ComposedPcodeEmulationCallbacks<byte[]> {
|
||||
public MonitoredCallbacks(PcodeEmulationCallbacks<byte[]> cb) {
|
||||
super(cb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeStepOp(PcodeThread<byte[]> thread, PcodeOp op, PcodeFrame frame) {
|
||||
if (monitor.isCancelled()) {
|
||||
throw new InterruptPcodeExecutionException(frame, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class OverrideProbeUseropLibrary extends AnnotatedPcodeUseropLibrary<byte[]> {
|
||||
@PcodeUserop
|
||||
public void emu_probe(@OpExecutor PcodeExecutor<byte[]> exec, Varnode in) {
|
||||
probesOut.add(new ProbeOut(in, exec.getState().getVar(in, Reason.INSPECT)));
|
||||
}
|
||||
}
|
||||
|
||||
class MonitoredEmulator extends PcodeEmulator {
|
||||
public MonitoredEmulator(Language language, PcodeEmulationCallbacks<byte[]> cb) {
|
||||
super(language, new MonitoredCallbacks(cb));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
|
||||
return super.createUseropLibrary().compose(new OverrideProbeUseropLibrary(), true);
|
||||
}
|
||||
}
|
||||
|
||||
final PluginTool tool;
|
||||
final ManagedDomainObject<Trace> mt;
|
||||
final Transaction tx;
|
||||
public final Trace trace;
|
||||
final TraceThread thread;
|
||||
final DebuggerCoordinates start;
|
||||
final Eval initEval;
|
||||
public final PcodeArithmetic<LocAndVal> lvArith;
|
||||
final SleighLanguage language;
|
||||
final Function function;
|
||||
final Program program;
|
||||
final DebuggerAddressTranslator translator;
|
||||
final TaskMonitor monitor;
|
||||
|
||||
final Writer writer;
|
||||
final PcodeEmulator emulator;
|
||||
final PcodeThread<byte[]> emuThread;
|
||||
|
||||
final List<ProbeOut> probesOut = new ArrayList<>();
|
||||
|
||||
private FunctionEmulationHarness(PluginTool tool, ManagedDomainObject<Trace> mt,
|
||||
Function function, TaskMonitor monitor) {
|
||||
this.tool = tool;
|
||||
this.mt = mt;
|
||||
this.trace = mt.get();
|
||||
this.tx = trace.openTransaction("Emulate");
|
||||
this.thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
|
||||
this.start = DebuggerCoordinates.NOWHERE.thread(thread);
|
||||
this.initEval = new Eval(0);
|
||||
this.lvArith = initEval.exec.getArithmetic();
|
||||
this.language = initEval.exec.getLanguage();
|
||||
this.function = function;
|
||||
this.program = function.getProgram();
|
||||
this.monitor = monitor;
|
||||
|
||||
TracePlatform host = trace.getPlatformManager().getHostPlatform();
|
||||
this.translator = getTranslator();
|
||||
PcodeDebuggerAccess access = new TranslatedPcodeDebuggerAccess(null, host, 0) {
|
||||
@Override
|
||||
public DebuggerAddressTranslator getAddressTranslator() {
|
||||
return translator;
|
||||
}
|
||||
};
|
||||
writer = DebuggerEmulationIntegration.bytesDelayedWriteTrace(access);
|
||||
emulator = new MonitoredEmulator(language, writer.callbacks());
|
||||
emuThread = emulator.newThread(thread.getPath());
|
||||
}
|
||||
|
||||
private DebuggerAddressTranslator getTranslator() {
|
||||
// Don't use the service. It requires the trace and program to be opened in the tool
|
||||
DebuggerStaticMappingContext mapper = new DebuggerStaticMappingContext();
|
||||
try (ChangeCollector cc = mapper.collectChanges()) {
|
||||
mapper.addProgram(cc, program);
|
||||
mapper.addTrace(cc, trace);
|
||||
}
|
||||
return mapper;
|
||||
}
|
||||
|
||||
public Eval init() {
|
||||
return initEval;
|
||||
}
|
||||
|
||||
public Eval eval(long snap) {
|
||||
return new Eval(snap);
|
||||
}
|
||||
|
||||
public void placeSentinel(Address sentinel) {
|
||||
ReturnAddressInfo retInfo = locateReturnAddress();
|
||||
if (retInfo == null) {
|
||||
Address zero = language.getDefaultSpace().getAddress(0);
|
||||
Msg.warn(this,
|
||||
"Could not locate return address. Placing end break at %s".formatted(zero));
|
||||
emulator.addBreakpoint(zero, SleighUtils.CONDITION_ALWAYS);
|
||||
}
|
||||
else {
|
||||
initEval.state.setVar(
|
||||
retInfo.computePhysicalLocation(initEval, program.getCompilerSpec()),
|
||||
language.getProgramCounter().getNumBytes(), false, lvArith.fromConst(sentinel));
|
||||
emulator.addBreakpoint(sentinel.getNewAddress(sentinel.getOffset() & retInfo.mask),
|
||||
SleighUtils.CONDITION_ALWAYS);
|
||||
}
|
||||
}
|
||||
|
||||
public void installInjects() {
|
||||
Iterator<Bookmark> bit = program.getBookmarkManager()
|
||||
.getBookmarksIterator(LogicalBreakpoint.ENABLED_BOOKMARK_TYPE);
|
||||
while (bit.hasNext()) {
|
||||
ProgramBreakpoint brk = ProgramBreakpoint.fromBookmark(program, bit.next());
|
||||
String sleigh = brk.getEmuSleigh();
|
||||
if (sleigh == null || SleighUtils.UNCONDITIONAL_BREAK.equals(sleigh)) {
|
||||
continue;
|
||||
}
|
||||
emulator.inject(brk.getLocation().getAddress(), sleigh);
|
||||
}
|
||||
}
|
||||
|
||||
public void createSnapshot(long snap) {
|
||||
TraceSnapshot snapshot = trace.getTimeManager().getSnapshot(snap, true);
|
||||
snapshot.setDescription("Emulated");
|
||||
snapshot.setEventThread(thread);
|
||||
writer.writeDown(snap);
|
||||
}
|
||||
|
||||
public record EmulationResult(long snap, Throwable error) {
|
||||
/**
|
||||
* In the case we terminate normally, we should probably ensure the {@code return}
|
||||
* instruction highlighted. This can be accomplished by subtracting one from the last snap.
|
||||
* For abnormal termination, I think we should just go to the last snap and let whatever
|
||||
* problem(s) get displayed.
|
||||
*
|
||||
* @return the last snap - 1 if successful, otherwise just the last snap.
|
||||
*/
|
||||
public long defaultSnap() {
|
||||
if (error == null) {
|
||||
return snap - 1;
|
||||
}
|
||||
return snap;
|
||||
}
|
||||
}
|
||||
|
||||
public EmulationResult run(long snapshotPeriod) {
|
||||
long snap = 1;
|
||||
boolean makeFinalSnap = false;
|
||||
Throwable error = null;
|
||||
try {
|
||||
if (snapshotPeriod == 0) {
|
||||
makeFinalSnap = true;
|
||||
emuThread.run();
|
||||
throw new AssertionError("Shouldn't happen");
|
||||
}
|
||||
for (;; snap += 1) {
|
||||
emuThread.stepInstruction();
|
||||
makeFinalSnap = true;
|
||||
emuThread.stepInstruction(snapshotPeriod - 1);
|
||||
createSnapshot(snap);
|
||||
makeFinalSnap = false;
|
||||
}
|
||||
}
|
||||
catch (InterruptPcodeExecutionException e) {
|
||||
// Expected, but snap is now one ahead of the last snapshot
|
||||
snap--;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
error = t;
|
||||
snap--;
|
||||
}
|
||||
if (makeFinalSnap) {
|
||||
createSnapshot(++snap);
|
||||
}
|
||||
return new EmulationResult(snap, error);
|
||||
}
|
||||
|
||||
public List<ProbeOut> getProbesOut() {
|
||||
return probesOut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
tx.close();
|
||||
mt.close();
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.table.TableCellEditor;
|
||||
|
||||
import docking.widgets.table.CustomToStringCellRenderer;
|
||||
import docking.widgets.table.EnumeratedTableColumn;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
enum InputColumn implements EnumeratedTableColumn<InputColumn, InputRow> {
|
||||
NAME("Name", String.class, InputRow::getName),
|
||||
STORAGE("Storage", VarStorage.class, InputRow::getStorage),
|
||||
VALUE("Value", String.class, InputRow::getValueStr, InputRow::setValueStr) {
|
||||
@Override
|
||||
public GColumnRenderer<?> getRenderer() {
|
||||
return CustomToStringCellRenderer.MONO_OBJECT;
|
||||
}
|
||||
},
|
||||
TYPE("Type", DataType.class, InputRow::getType, InputRow::setType) {
|
||||
@Override
|
||||
public TableCellEditor getEditor() {
|
||||
return VarDataTypeEditor.INSTANCE;
|
||||
}
|
||||
},
|
||||
REPR("Repr", String.class, InputRow::getRepr, InputRow::setRepr) {
|
||||
@Override
|
||||
public boolean isEditable(InputRow row) {
|
||||
return row.isReprEditable();
|
||||
}
|
||||
};
|
||||
|
||||
final String header;
|
||||
final Class<?> cls;
|
||||
final Function<InputRow, Object> getter;
|
||||
final BiConsumer<InputRow, Object> setter;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> InputColumn(String header, Class<T> cls, Function<InputRow, T> getter,
|
||||
BiConsumer<InputRow, T> setter) {
|
||||
this.header = header;
|
||||
this.cls = cls;
|
||||
this.getter = (Function<InputRow, Object>) getter;
|
||||
this.setter = (BiConsumer<InputRow, Object>) setter;
|
||||
}
|
||||
|
||||
private <T> InputColumn(String header, Class<T> cls, Function<InputRow, T> getter) {
|
||||
this(header, cls, getter, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueClass() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueOf(InputRow row) {
|
||||
return getter.apply(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable(InputRow row) {
|
||||
return setter != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueOf(InputRow row, Object value) {
|
||||
setter.accept(row, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.data.DataTypeEncodeException;
|
||||
import ghidra.program.model.lang.CompilerSpec;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.listing.Variable;
|
||||
import ghidra.program.model.mem.ByteMemBufferImpl;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
|
||||
class InputRow extends VarRow {
|
||||
static InputRow fromVariable(Variable v, CompilerSpec cSpec) {
|
||||
return VarRow.fromVariable(InputRow::new, v, cSpec);
|
||||
}
|
||||
|
||||
final Set<String> depsByName; // Use names since the actual row can be replaced by refresh
|
||||
|
||||
InputRow(Language language, String name, VarStorage storage, DataType type,
|
||||
Set<String> depsByName) {
|
||||
super(language, name, storage, type);
|
||||
this.depsByName = depsByName;
|
||||
}
|
||||
|
||||
InputRow(Language language, String name, VarStorage storage, DataType type) {
|
||||
this(language, name, storage, type, Set.of());
|
||||
}
|
||||
|
||||
private void encodeRepr() throws DataTypeEncodeException {
|
||||
MemBuffer buf = new ByteMemBufferImpl(address, value, language.isBigEndian());
|
||||
byte[] data = type.encodeRepresentation(repr, buf, settings, length);
|
||||
if (data.length == length) {
|
||||
value = data;
|
||||
}
|
||||
else {
|
||||
value = Arrays.copyOf(data, length);
|
||||
}
|
||||
}
|
||||
|
||||
void setValueStr(String string) {
|
||||
RawStyle style = RawStyle.fromString(string);
|
||||
byte[] value = style.fromString(string, length, language);
|
||||
this.style = style;
|
||||
this.value = value;
|
||||
decodeValue();
|
||||
}
|
||||
|
||||
void setRepr(String repr) {
|
||||
String oldRepr = this.repr;
|
||||
this.repr = repr;
|
||||
try {
|
||||
encodeRepr();
|
||||
decodeValue();
|
||||
}
|
||||
catch (DataTypeEncodeException e) {
|
||||
this.repr = oldRepr;
|
||||
throw new IllegalArgumentException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isReprEditable() {
|
||||
return repr != null && type.isEncodable();
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
|
||||
class InputsTableModel extends VarTableModel<InputColumn, InputRow> {
|
||||
public InputsTableModel(ServiceProvider tool, DebuggerEmulateFunctionDialog dialog) {
|
||||
super(tool, "Inputs", InputColumn.class, dialog);
|
||||
}
|
||||
}
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.emulation.Mode;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer;
|
||||
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess;
|
||||
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
|
||||
public record LocAndVal(byte[] value, ValueLocation loc) {
|
||||
public enum LocAndValPcodeArithmetic implements PcodeArithmetic<LocAndVal> {
|
||||
BIG_ENDIAN(BytesPcodeArithmetic.BIG_ENDIAN, LocationPcodeArithmetic.BIG_ENDIAN),
|
||||
LITTLE_ENDIAN(BytesPcodeArithmetic.LITTLE_ENDIAN, LocationPcodeArithmetic.BIG_ENDIAN);
|
||||
|
||||
public static LocAndValPcodeArithmetic forEndian(boolean isBigEndian) {
|
||||
return isBigEndian ? BIG_ENDIAN : LITTLE_ENDIAN;
|
||||
}
|
||||
|
||||
public static LocAndValPcodeArithmetic forLanguage(Language language) {
|
||||
return forEndian(language.isBigEndian());
|
||||
}
|
||||
|
||||
private final BytesPcodeArithmetic bytes;
|
||||
private final LocationPcodeArithmetic location;
|
||||
|
||||
private LocAndValPcodeArithmetic(BytesPcodeArithmetic bytes,
|
||||
LocationPcodeArithmetic location) {
|
||||
this.bytes = bytes;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<LocAndVal> getDomain() {
|
||||
return LocAndVal.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Endian getEndian() {
|
||||
return bytes.getEndian();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocAndVal unaryOp(int opcode, int sizeout, int sizein1, LocAndVal in1) {
|
||||
return new LocAndVal(
|
||||
bytes.unaryOp(opcode, sizeout, sizein1, in1.value),
|
||||
location.unaryOp(opcode, sizeout, sizein1, in1.loc));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocAndVal binaryOp(int opcode, int sizeout, int sizein1, LocAndVal in1, int sizein2,
|
||||
LocAndVal in2) {
|
||||
return new LocAndVal(
|
||||
bytes.binaryOp(opcode, sizeout, sizein1, in1.value, sizein2, in2.value),
|
||||
location.binaryOp(opcode, sizeout, sizein1, in1.loc, sizein2, in2.loc));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocAndVal modBeforeStore(int sizeinOffset, AddressSpace space, LocAndVal inOffset,
|
||||
int sizeinValue, LocAndVal inValue) {
|
||||
return new LocAndVal(
|
||||
bytes.modBeforeStore(sizeinOffset, space, inOffset.value, sizeinValue,
|
||||
inValue.value),
|
||||
location.modBeforeStore(sizeinOffset, space, inOffset.loc, sizeinValue,
|
||||
inValue.loc));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocAndVal modAfterLoad(int sizeinOffset, AddressSpace space, LocAndVal inOffset,
|
||||
int sizeinValue, LocAndVal inValue) {
|
||||
return new LocAndVal(
|
||||
bytes.modAfterLoad(sizeinOffset, space, inOffset.value, sizeinValue, inValue.value),
|
||||
location.modAfterLoad(sizeinOffset, space, inOffset.loc, sizeinValue, inValue.loc));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocAndVal fromConst(byte[] value) {
|
||||
return new LocAndVal(
|
||||
bytes.fromConst(value),
|
||||
location.fromConst(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toConcrete(LocAndVal value, Purpose purpose) {
|
||||
return bytes.toConcrete(value.value, purpose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sizeOf(LocAndVal value) {
|
||||
return bytes.sizeOf(value.value);
|
||||
}
|
||||
}
|
||||
|
||||
public static class LocAndValPcodeExecutorStatePiece
|
||||
implements PcodeExecutorStatePiece<byte[], LocAndVal> {
|
||||
private final PcodeExecutorStatePiece<byte[], byte[]> bytesPiece;
|
||||
private final PcodeExecutorStatePiece<byte[], ValueLocation> locationPiece;
|
||||
|
||||
private final PcodeArithmetic<LocAndVal> arithmetic;
|
||||
|
||||
public LocAndValPcodeExecutorStatePiece(
|
||||
PcodeExecutorStatePiece<byte[], byte[]> bytesPiece,
|
||||
PcodeExecutorStatePiece<byte[], ValueLocation> locationPiece) {
|
||||
this.bytesPiece = bytesPiece;
|
||||
this.locationPiece = locationPiece;
|
||||
this.arithmetic = LocAndValPcodeArithmetic.forLanguage(bytesPiece.getLanguage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Language getLanguage() {
|
||||
return bytesPiece.getLanguage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PcodeArithmetic<byte[]> getAddressArithmetic() {
|
||||
return bytesPiece.getAddressArithmetic();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PcodeArithmetic<LocAndVal> getArithmetic() {
|
||||
return arithmetic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<PcodeExecutorStatePiece<?, ?>> streamPieces() {
|
||||
return Stream.of(bytesPiece, locationPiece);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocAndValPcodeExecutorStatePiece fork(PcodeStateCallbacks cb) {
|
||||
return new LocAndValPcodeExecutorStatePiece(
|
||||
bytesPiece.fork(cb),
|
||||
locationPiece.fork(cb));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVarInternal(AddressSpace space, byte[] offset, int size, LocAndVal val) {
|
||||
bytesPiece.setVarInternal(space, offset, size, val.value);
|
||||
locationPiece.setVarInternal(space, offset, size, val.loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVar(AddressSpace space, byte[] offset, int size, boolean quantize,
|
||||
LocAndVal val) {
|
||||
bytesPiece.setVar(space, offset, size, quantize, val.value);
|
||||
locationPiece.setVar(space, offset, size, quantize, val.loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocAndVal getVar(AddressSpace space, byte[] offset, int size, boolean quantize,
|
||||
Reason reason) {
|
||||
return new LocAndVal(
|
||||
bytesPiece.getVar(space, offset, size, quantize, reason),
|
||||
locationPiece.getVar(space, offset, size, quantize, reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocAndVal getVarInternal(AddressSpace space, byte[] offset, int size,
|
||||
Reason reason) {
|
||||
return new LocAndVal(
|
||||
bytesPiece.getVarInternal(space, offset, size, reason),
|
||||
locationPiece.getVarInternal(space, offset, size, reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Register, LocAndVal> getRegisterValues() {
|
||||
Map<Register, LocAndVal> result = new HashMap<>();
|
||||
for (Entry<Register, byte[]> entry : bytesPiece.getRegisterValues().entrySet()) {
|
||||
Register reg = entry.getKey();
|
||||
AddressSpace space = reg.getAddressSpace();
|
||||
long offset = reg.getAddress().getOffset();
|
||||
int size = reg.getNumBytes();
|
||||
result.put(reg, new LocAndVal(
|
||||
entry.getValue(),
|
||||
locationPiece.getVar(space, offset, size, false, Reason.INSPECT)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
|
||||
return bytesPiece.getConcreteBuffer(address, purpose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
bytesPiece.clear();
|
||||
locationPiece.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static class LocAndValPcodeExecutorState
|
||||
extends AbstractPcodeExecutorState<byte[], LocAndVal> {
|
||||
|
||||
public LocAndValPcodeExecutorState(PcodeExecutorStatePiece<byte[], LocAndVal> piece) {
|
||||
super(piece);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] extractAddress(LocAndVal value) {
|
||||
return value.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocAndValPcodeExecutorState fork(PcodeStateCallbacks cb) {
|
||||
return new LocAndValPcodeExecutorState(piece.fork(cb));
|
||||
}
|
||||
}
|
||||
|
||||
public record WriterAndState(Writer writer, LocAndValPcodeExecutorState state) {}
|
||||
|
||||
public static WriterAndState buildState(ServiceProvider provider,
|
||||
DebuggerCoordinates coordinates) {
|
||||
PcodeTraceDataAccess data = new DefaultPcodeTraceAccess(coordinates.getPlatform(),
|
||||
coordinates.getViewSnap(), coordinates.getSnap())
|
||||
.getDataForThreadState(coordinates.getThread(), coordinates.getFrame());
|
||||
// Seems weird, but RO is in terms of the Target. RO writes the Trace.
|
||||
DebuggerPcodeUtils.WriterAndState ws =
|
||||
DebuggerPcodeUtils.executorStateForCoordinates(provider, coordinates, Mode.RO);
|
||||
return new WriterAndState(ws.writer(),
|
||||
new LocAndValPcodeExecutorState(new LocAndValPcodeExecutorStatePiece(ws.state(),
|
||||
new LocationPcodeExecutorStatePiece(data.getLanguage()))));
|
||||
}
|
||||
|
||||
public record WriterAndExecutor(Writer writer, PcodeExecutor<LocAndVal> executor) {}
|
||||
|
||||
public static WriterAndExecutor buildExecutor(ServiceProvider provider,
|
||||
DebuggerCoordinates coordinates) {
|
||||
TracePlatform platform = coordinates.getPlatform();
|
||||
Language language = platform.getLanguage();
|
||||
if (!(language instanceof SleighLanguage slang)) {
|
||||
throw new IllegalArgumentException("Emulation requires a Sleigh language");
|
||||
}
|
||||
WriterAndState ws = buildState(provider, coordinates);
|
||||
return new WriterAndExecutor(ws.writer,
|
||||
new PcodeExecutor<>(slang, ws.state.getArithmetic(), ws.state, Reason.INSPECT));
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.emulation;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public abstract class LongInputVerifier extends InputVerifier {
|
||||
@Override
|
||||
public boolean verify(JComponent input) {
|
||||
if (!(input instanceof JTextField text)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Only JTextField is supported. Got %s".formatted(input.getClass()));
|
||||
}
|
||||
String str = text.getText();
|
||||
try {
|
||||
BigInteger value = new BigInteger(str);
|
||||
long l = value.longValueExact();
|
||||
if (!verifyLong(l)) {
|
||||
reject("Invalid value: %s".formatted(str));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
reject("%s while parsing '%s'".formatted(e.getMessage(), str));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract boolean verifyLong(long value);
|
||||
|
||||
protected abstract void reject(String message);
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.table.TableCellEditor;
|
||||
|
||||
import docking.widgets.table.CustomToStringCellRenderer;
|
||||
import docking.widgets.table.EnumeratedTableColumn;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
enum OutputColumn implements EnumeratedTableColumn<OutputColumn, OutputRow> {
|
||||
NAME("Name", String.class, OutputRow::getName),
|
||||
STORAGE("Storage", VarStorage.class, OutputRow::getStorage),
|
||||
VALUE("Value", String.class, OutputRow::getValueStr) {
|
||||
@Override
|
||||
public GColumnRenderer<?> getRenderer() {
|
||||
return CustomToStringCellRenderer.MONO_OBJECT;
|
||||
}
|
||||
},
|
||||
TYPE("Type", DataType.class, OutputRow::getType, OutputRow::setType) {
|
||||
@Override
|
||||
public TableCellEditor getEditor() {
|
||||
return VarDataTypeEditor.INSTANCE;
|
||||
}
|
||||
},
|
||||
REPR("Repr", String.class, OutputRow::getRepr);
|
||||
|
||||
final String header;
|
||||
final Class<?> cls;
|
||||
final Function<OutputRow, Object> getter;
|
||||
final BiConsumer<OutputRow, Object> setter;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> OutputColumn(String header, Class<T> cls, Function<OutputRow, T> getter,
|
||||
BiConsumer<OutputRow, T> setter) {
|
||||
this.header = header;
|
||||
this.cls = cls;
|
||||
this.getter = (Function<OutputRow, Object>) getter;
|
||||
this.setter = (BiConsumer<OutputRow, Object>) setter;
|
||||
}
|
||||
|
||||
private <T> OutputColumn(String header, Class<T> cls, Function<OutputRow, T> getter) {
|
||||
this(header, cls, getter, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueClass() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueOf(OutputRow row) {
|
||||
return getter.apply(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable(OutputRow row) {
|
||||
return setter != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueOf(OutputRow row, Object value) {
|
||||
setter.accept(row, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.lang.CompilerSpec;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.listing.Variable;
|
||||
|
||||
class OutputRow extends VarRow {
|
||||
static OutputRow fromVariable(Variable v, CompilerSpec cSpec) {
|
||||
return VarRow.fromVariable(OutputRow::new, v, cSpec);
|
||||
}
|
||||
|
||||
OutputRow(Language language, String name, VarStorage storage, DataType type) {
|
||||
super(language, name, storage, type);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
|
||||
public class OutputsTableModel extends VarTableModel<OutputColumn, OutputRow> {
|
||||
public OutputsTableModel(ServiceProvider tool, DebuggerEmulateFunctionDialog dialog) {
|
||||
super(tool, "Outputs", OutputColumn.class, dialog);
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.util.NumericUtilities;
|
||||
|
||||
// LATER: Move this into some utilities and have DefaultWatchRow use it, too?
|
||||
enum RawStyle {
|
||||
INTEGER {
|
||||
@Override
|
||||
String toString(byte[] value, Language language) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
BigInteger asInt =
|
||||
Utils.bytesToBigInteger(value, value.length, language.isBigEndian(), false);
|
||||
return "0x" + asInt.toString(16);
|
||||
}
|
||||
|
||||
@Override
|
||||
byte[] fromString(String string, int length, Language language) {
|
||||
string = string.trim();
|
||||
final BigInteger asInt = string.startsWith("0x")
|
||||
? new BigInteger(string.substring(2), 16)
|
||||
: new BigInteger(string, 10);
|
||||
return Utils.bigIntegerToBytes(asInt, length, language.isBigEndian());
|
||||
}
|
||||
},
|
||||
BYTES {
|
||||
@Override
|
||||
String toString(byte[] value, Language language) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return "{ %s }".formatted(NumericUtilities.convertBytesToString(value, " "));
|
||||
}
|
||||
|
||||
@Override
|
||||
byte[] fromString(String string, int length, Language language) {
|
||||
string = string.trim();
|
||||
if (!string.startsWith("{") && string.endsWith("}")) {
|
||||
throw new IllegalArgumentException(string);
|
||||
}
|
||||
string = string.substring(1, string.length() - 1);
|
||||
byte[] data = NumericUtilities.convertStringToBytes(string);
|
||||
if (data.length == length) {
|
||||
return data;
|
||||
}
|
||||
return Arrays.copyOf(data, length);
|
||||
}
|
||||
};
|
||||
|
||||
static RawStyle defaultForSpace(AddressSpace space) {
|
||||
return space.isMemorySpace() ? BYTES : INTEGER;
|
||||
}
|
||||
|
||||
static RawStyle fromString(String string) {
|
||||
return string.trim().startsWith("{") ? BYTES : INTEGER;
|
||||
}
|
||||
|
||||
abstract String toString(byte[] value, Language language);
|
||||
|
||||
abstract byte[] fromString(String string, int length, Language language);
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
import db.Transaction;
|
||||
import ghidra.app.services.DataTypeManagerService;
|
||||
import ghidra.base.widgets.table.AbstractDataTypeTableCellEditor;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
class VarDataTypeEditor extends AbstractDataTypeTableCellEditor {
|
||||
static final VarDataTypeEditor INSTANCE = new VarDataTypeEditor();
|
||||
|
||||
@Override
|
||||
protected boolean validateSelection(DataType dataType, TableModel model) {
|
||||
if (!(model instanceof VarTableModel<?, ?> vModel)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VarRow row = vModel.getModelData().get(table.getEditingRow());
|
||||
if (row == null) {
|
||||
return false;
|
||||
}
|
||||
int dtLength = dataType.getLength();
|
||||
if (dtLength != -1 && dtLength != row.length) {
|
||||
vModel.getDialog()
|
||||
.setStatusText("Invalid DataType %s. Length must be %d.".formatted(dataType,
|
||||
row.length));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataTypeManagerService getService(TableModel model) {
|
||||
if (!(model instanceof VarTableModel<?, ?> vModel)) {
|
||||
return null;
|
||||
}
|
||||
return vModel.getServiceProvider().getService(DataTypeManagerService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataType resolveSelection(DataType dataType, TableModel model) {
|
||||
if (dataType == null || !(model instanceof VarTableModel<?, ?> vModel)) {
|
||||
return null;
|
||||
}
|
||||
Program program = vModel.getProgram();
|
||||
try (Transaction tx = program.openTransaction("Resolve DataTye")) {
|
||||
return program.getDataTypeManager().resolve(dataType, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.docking.settings.SettingsImpl;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.lang.CompilerSpec;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.listing.Variable;
|
||||
import ghidra.program.model.listing.VariableStorage;
|
||||
import ghidra.program.model.mem.ByteMemBufferImpl;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
abstract class VarRow {
|
||||
interface VarRowFactory<T extends VarRow> {
|
||||
T create(Language language, String name, VarStorage storage, DataType type);
|
||||
}
|
||||
|
||||
static <T extends VarRow> T fromVariable(VarRowFactory<T> cons, Variable v,
|
||||
CompilerSpec cSpec) {
|
||||
VariableStorage storage = v.getVariableStorage();
|
||||
return cons.create(cSpec.getLanguage(), v.getName(),
|
||||
VarStorage.fromVariableStorage(storage, cSpec), v.getDataType());
|
||||
}
|
||||
|
||||
final Language language;
|
||||
final String name;
|
||||
final VarStorage storage;
|
||||
final int length;
|
||||
final Address address;
|
||||
final SettingsImpl settings = new SettingsImpl();
|
||||
|
||||
byte[] value;
|
||||
String repr;
|
||||
DataType type;
|
||||
|
||||
RawStyle style;
|
||||
|
||||
VarRow(Language language, String name, VarStorage storage, DataType type) {
|
||||
this.language = language;
|
||||
this.name = name;
|
||||
this.storage = storage;
|
||||
this.length = storage.size();
|
||||
this.address = storage.address();
|
||||
|
||||
this.value = new byte[storage.size()];
|
||||
this.type = type;
|
||||
this.style = RawStyle.defaultForSpace(address.getAddressSpace());
|
||||
|
||||
decodeValue();
|
||||
}
|
||||
|
||||
void decodeValue() {
|
||||
try {
|
||||
if (type == null) {
|
||||
repr = null;
|
||||
return;
|
||||
}
|
||||
MemBuffer buf = new ByteMemBufferImpl(address, value, language.isBigEndian()) {
|
||||
@Override
|
||||
public Language getLanguage() {
|
||||
return language;
|
||||
}
|
||||
};
|
||||
repr = type.getRepresentation(buf, settings, length);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, e.getMessage());
|
||||
repr = null;
|
||||
}
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
VarStorage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
void setType(DataType type) {
|
||||
this.type = type;
|
||||
decodeValue();
|
||||
}
|
||||
|
||||
DataType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
SettingsImpl getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
void settingsChanged() {
|
||||
decodeValue();
|
||||
}
|
||||
|
||||
void setValue(byte[] value) {
|
||||
if (value.length != storage.size()) {
|
||||
throw new IllegalArgumentException("Length mismatch");
|
||||
}
|
||||
this.value = value;
|
||||
decodeValue();
|
||||
}
|
||||
|
||||
byte[] getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
String getValueStr() {
|
||||
return style.toString(value, language);
|
||||
}
|
||||
|
||||
String getRepr() {
|
||||
return Objects.requireNonNullElse(repr, "");
|
||||
}
|
||||
}
|
||||
+77
@@ -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.emulation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.CompilerSpec;
|
||||
import ghidra.program.model.listing.VariableStorage;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
|
||||
record VarStorage(List<VarStorageNode> nodes, int size) {
|
||||
static VarStorage fromPieces(Varnode[] pieces, CompilerSpec cSpec) {
|
||||
return new VarStorage(Stream.of(pieces)
|
||||
.map(vn -> VarStorageNode.fromVarnode(vn, cSpec))
|
||||
.toList());
|
||||
}
|
||||
|
||||
static VarStorage fromVariableStorage(VariableStorage vs, CompilerSpec cSpec) {
|
||||
return fromPieces(vs.getVarnodes(), cSpec);
|
||||
}
|
||||
|
||||
static VarStorage fromExpression(SleighLanguage language, String expression) {
|
||||
// TODO: Could break ValueLocation down into its Varnodes, but what's that buy?
|
||||
return new VarStorage(List.of(VarStorageNode.fromExpression(language, expression)));
|
||||
}
|
||||
|
||||
VarStorage(List<VarStorageNode> nodes) {
|
||||
this(nodes, nodes.stream().mapToInt(VarStorageNode::size).sum());
|
||||
}
|
||||
|
||||
Address address() {
|
||||
return nodes.getFirst().address();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
if (nodes.size() == 1) {
|
||||
return nodes.getFirst().toString();
|
||||
}
|
||||
return nodes.toString();
|
||||
}
|
||||
|
||||
public VarStorage deref(SleighLanguage language, AddressSpace space, int length) {
|
||||
if (nodes.size() != 1) {
|
||||
throw new UnsupportedOperationException("Deref of multi-node storage not supported");
|
||||
}
|
||||
return new VarStorage(List.of(nodes.getFirst().deref(language, space, length)), length);
|
||||
}
|
||||
|
||||
public VarStorage deref(SleighLanguage language, AddressSpace space, int offset, int length) {
|
||||
if (offset == 0) {
|
||||
return deref(language, space, length);
|
||||
}
|
||||
if (nodes.size() != 1) {
|
||||
throw new UnsupportedOperationException("Deref of multi-node storage not supported");
|
||||
}
|
||||
return new VarStorage(List.of(nodes.getFirst().deref(language, space, offset, length)),
|
||||
length);
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.emulation.LocAndVal.LocAndValPcodeExecutorState;
|
||||
import ghidra.app.plugin.core.debug.gui.emulation.LocAndVal.LocAndValPcodeExecutorStatePiece;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.CompilerSpec;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
|
||||
// TODO: Consider using Varnode with defining op
|
||||
// TODO: Consider compiling in the constructor? Would need language.
|
||||
record VarStorageNode(String expr, Address address, int size) {
|
||||
static VarStorageNode fromVarnode(Varnode vn, CompilerSpec cSpec) {
|
||||
Address address = vn.getAddress();
|
||||
if (address.isStackAddress()) {
|
||||
return new VarStorageNode("*:%d (%s + 0x%x)".formatted(vn.getSize(),
|
||||
cSpec.getStackPointer(), address.getOffset()),
|
||||
cSpec.getStackBaseSpace().getAddress(0), vn.getSize());
|
||||
}
|
||||
if (address.isMemoryAddress()) {
|
||||
return new VarStorageNode("*:%d 0x%08x".formatted(vn.getSize(), vn.getOffset()),
|
||||
address, vn.getSize());
|
||||
}
|
||||
if (address.isRegisterAddress()) {
|
||||
return new VarStorageNode(vn.toString(cSpec.getLanguage()), address, vn.getSize());
|
||||
}
|
||||
if (address.isUniqueAddress()) {
|
||||
return new VarStorageNode("$Unique", address, vn.getSize());
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
static VarStorageNode fromExpression(SleighLanguage language, String expression) {
|
||||
PcodeExpression expr = SleighProgramCompiler.compileExpression(language, expression);
|
||||
LocAndValPcodeExecutorState state =
|
||||
new LocAndValPcodeExecutorState(new LocAndValPcodeExecutorStatePiece(
|
||||
new BytesPcodeExecutorState(language, PcodeStateCallbacks.NONE),
|
||||
new LocationPcodeExecutorStatePiece(language)));
|
||||
PcodeExecutor<LocAndVal> exec = new PcodeExecutor<LocAndVal>(state, Reason.INSPECT);
|
||||
ValueLocation loc = expr.evaluate(exec).loc();
|
||||
return new VarStorageNode(expression, loc.getAddress(), loc.size());
|
||||
}
|
||||
|
||||
PcodeExpression compile(SleighLanguage language) {
|
||||
return SleighProgramCompiler.compileExpression(language, expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return expr;
|
||||
}
|
||||
|
||||
public VarStorageNode deref(SleighLanguage language, AddressSpace space, int length) {
|
||||
String derefExpr = space == language.getDefaultDataSpace()
|
||||
? "*:%d (%s)".formatted(length, expr)
|
||||
: "*[%s]:%d (%s)".formatted(space.getName(), length, expr);
|
||||
return new VarStorageNode(derefExpr, space.getAddress(0), length);
|
||||
}
|
||||
|
||||
public VarStorageNode deref(SleighLanguage language, AddressSpace space, int offset,
|
||||
int length) {
|
||||
String derefExpr = space == language.getDefaultDataSpace()
|
||||
? "*:%d ((%s)+%d)".formatted(length, expr, offset)
|
||||
: "*[%s]:%d ((%s)+%d)".formatted(space.getName(), length, expr, offset);
|
||||
return new VarStorageNode(derefExpr, space.getAddress(0), length);
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel;
|
||||
import docking.widgets.table.EnumeratedTableColumn;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.data.DataTypeManager;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.MessageType;
|
||||
|
||||
class VarTableModel<C extends java.lang.Enum<C> & EnumeratedTableColumn<C, R>, R extends VarRow>
|
||||
extends DefaultEnumeratedColumnTableModel<C, R> {
|
||||
|
||||
protected final DebuggerEmulateFunctionDialog dialog;
|
||||
protected final Function function;
|
||||
|
||||
public VarTableModel(ServiceProvider tool, String name, Class<C> colType,
|
||||
DebuggerEmulateFunctionDialog dialog) {
|
||||
super(tool, name, colType);
|
||||
this.dialog = dialog;
|
||||
this.function = dialog.function;
|
||||
}
|
||||
|
||||
ServiceProvider getServiceProvider() {
|
||||
return serviceProvider;
|
||||
}
|
||||
|
||||
Function getFunction() {
|
||||
return function;
|
||||
}
|
||||
|
||||
Program getProgram() {
|
||||
return function.getProgram();
|
||||
}
|
||||
|
||||
DataTypeManager getDataTypeManager() {
|
||||
return function.getProgram().getDataTypeManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
||||
try {
|
||||
super.setValueAt(aValue, rowIndex, columnIndex);
|
||||
dialog.clearStatusText();
|
||||
}
|
||||
catch (Exception e) {
|
||||
dialog.setStatusText(e.getMessage(), MessageType.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
DebuggerEmulateFunctionDialog getDialog() {
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -61,7 +61,7 @@ public class DefaultWatchRow implements WatchRow {
|
||||
private String expression;
|
||||
private String typePath;
|
||||
private DataType dataType;
|
||||
private SettingsImpl settings = new SettingsImpl();
|
||||
private final SettingsImpl settings = new SettingsImpl();
|
||||
private SavedSettings savedSettings = new SavedSettings(settings);
|
||||
private String comment;
|
||||
|
||||
|
||||
+26
-3
@@ -48,7 +48,7 @@ public class ProgramBreakpoint {
|
||||
private static final Gson GSON = new GsonBuilder().create();
|
||||
|
||||
/**
|
||||
* A class for (de)serializing breakoint properties in the bookmark's comments
|
||||
* A class for (de)serializing breakpoint properties in the bookmark's comments
|
||||
*/
|
||||
static class BreakpointProperties {
|
||||
public String name;
|
||||
@@ -106,6 +106,25 @@ public class ProgramBreakpoint {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a breakpoint from its bookmark (utility).
|
||||
* <p>
|
||||
* The breakpoint manager does not ordinarily use this, as the path to total construction spans
|
||||
* discovering and indexing of logical breakpoints, and then adding into them information
|
||||
* available from program bookmarks and trace breakpoints. This utility eases the processing of
|
||||
* program-specified bookmarks without depending on the breakpoint service.
|
||||
*
|
||||
* @param program the program containing the bookmark
|
||||
* @param bm the bookmark describing the breakpoint
|
||||
* @return the breakpoint
|
||||
*/
|
||||
public static ProgramBreakpoint fromBookmark(Program program, Bookmark bm) {
|
||||
ProgramBreakpoint brk = new ProgramBreakpoint(program,
|
||||
bm.getAddress(), lengthFromBookmark(bm), kindsFromBookmark(bm));
|
||||
brk.add(bm);
|
||||
return brk;
|
||||
}
|
||||
|
||||
private final Program program;
|
||||
private final Address address;
|
||||
private final ProgramLocation location;
|
||||
@@ -399,10 +418,14 @@ public class ProgramBreakpoint {
|
||||
*/
|
||||
public Bookmark getBookmark() {
|
||||
Bookmark eBookmark = this.eBookmark;
|
||||
if (eBookmark != null) {
|
||||
if (eBookmark != null && !eBookmark.isDeleted()) {
|
||||
return eBookmark;
|
||||
}
|
||||
return dBookmark;
|
||||
Bookmark dBookmark = this.dBookmark;
|
||||
if (dBookmark != null && !dBookmark.isDeleted()) {
|
||||
return dBookmark;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getComment() {
|
||||
|
||||
+34
-13
@@ -23,7 +23,8 @@ import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.trace.TraceEmulationIntegration;
|
||||
import ghidra.pcode.exec.trace.TraceEmulationIntegration.*;
|
||||
import ghidra.pcode.exec.trace.data.*;
|
||||
import ghidra.pcode.exec.trace.data.PcodeTraceAccess;
|
||||
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
@@ -73,13 +74,41 @@ public enum DebuggerEmulationIntegration {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create state callbacks that lazily load data and immediately write changes to the given
|
||||
* access shim.
|
||||
* Create state callbacks that lazily load data and writes changes to the given access shim.
|
||||
* <p>
|
||||
* Reads may be redirected to the target.
|
||||
* <p>
|
||||
* Use this instead of {@link #bytesImmediateWriteTarget(PcodeDebuggerAccess)} when interfacing
|
||||
* directly with a {@link PcodeExecutorState} vice a {@link PcodeEmulator}.
|
||||
*
|
||||
* @see TraceEmulationIntegration#bytesImmediateWrite(PcodeTraceAccess, TraceThread, int)
|
||||
* @param access the access shim for loads and stores
|
||||
* @param thread the trace thread for register accesses
|
||||
* @param frame the frame for register accesses, usually 0
|
||||
* @param mode determines whether or not writes affect the target
|
||||
* @return the callbacks
|
||||
*/
|
||||
public static Writer bytesWriteMode(PcodeDebuggerAccess access,
|
||||
TraceThread thread, int frame, Mode mode) {
|
||||
Writer writer = new TraceWriter(access) {
|
||||
@Override
|
||||
protected PcodeTraceDataAccess getDataAccess(PcodeTraceAccess access,
|
||||
AddressSpace space, PcodeThread<?> ignored) {
|
||||
return space.isRegisterSpace()
|
||||
? access.getDataForLocalState(thread, frame)
|
||||
: access.getDataForSharedState();
|
||||
}
|
||||
};
|
||||
writer.putHandler(new TargetBytesPieceHandler(mode));
|
||||
return writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create state callbacks that lazily load data and immediately writes changes to the given
|
||||
* access shim.
|
||||
* <p>
|
||||
* Reads may be redirected to the target. If redirected, writes are immediately sent to the
|
||||
* target and presumably stored into the trace at the same snapshot as state is sourced.
|
||||
*
|
||||
* <p>
|
||||
* Use this instead of {@link #bytesImmediateWriteTarget(PcodeDebuggerAccess)} when interfacing
|
||||
* directly with a {@link PcodeExecutorState} vice a {@link PcodeEmulator}.
|
||||
@@ -92,15 +121,7 @@ public enum DebuggerEmulationIntegration {
|
||||
*/
|
||||
public static PcodeStateCallbacks bytesImmediateWriteTarget(PcodeDebuggerAccess access,
|
||||
TraceThread thread, int frame) {
|
||||
PcodeDebuggerRegistersAccess regAcc = access.getDataForLocalState(thread, frame);
|
||||
Writer writer = new TraceWriter(access) {
|
||||
@Override
|
||||
protected PcodeTraceRegistersAccess getRegAccess(PcodeThread<?> ignored) {
|
||||
return regAcc;
|
||||
}
|
||||
};
|
||||
writer.putHandler(new TargetBytesPieceHandler(Mode.RW));
|
||||
return writer.wrapFor(null);
|
||||
return bytesWriteMode(access, thread, frame, Mode.RW).wrapFor(null);
|
||||
}
|
||||
|
||||
protected static <T> T waitTimeout(CompletableFuture<T> future) {
|
||||
|
||||
+54
@@ -41,6 +41,7 @@ import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.emulation.DebuggerEmulateFunctionDialog;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.async.AsyncLazyMap;
|
||||
@@ -62,6 +63,7 @@ import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer;
|
||||
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess;
|
||||
import ghidra.pcode.exec.trace.data.PcodeTraceAccess;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.*;
|
||||
@@ -130,6 +132,27 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
||||
}
|
||||
}
|
||||
|
||||
interface EmulateFunctionAction {
|
||||
String NAME = "Emulate Function";
|
||||
String DESCRIPTION = "Emulate the current function in a new trace";
|
||||
Icon ICON = DebuggerResources.ICON_EMULATE;
|
||||
String GROUP = DebuggerResources.GROUP_GENERAL;
|
||||
String HELP_ANCHOR = "emulate_function";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(DebuggerPluginPackage.NAME, NAME)
|
||||
.menuIcon(ICON)
|
||||
.menuGroup(GROUP)
|
||||
.popupMenuPath(NAME)
|
||||
.popupMenuIcon(ICON)
|
||||
.popupMenuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface EmulateAddThreadAction {
|
||||
String NAME = "Add Emulated Thread to Trace";
|
||||
String DESCRIPTION = "Add an emulated thread to the current trace starting here";
|
||||
@@ -374,6 +397,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
||||
private AutoService.Wiring autoServiceWiring;
|
||||
|
||||
DockingAction actionEmulateProgram;
|
||||
DockingAction actionEmulateFunction;
|
||||
DockingAction actionEmulateAddThread;
|
||||
DockingAction actionInvalidateCache;
|
||||
Map<Class<? extends EmulatorFactory>, ToggleDockingAction> actionsChooseEmulatorFactory =
|
||||
@@ -399,6 +423,12 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
||||
.popupWhen(this::emulateProgramEnabled)
|
||||
.onAction(this::emulateProgramActivated)
|
||||
.buildAndInstall(tool);
|
||||
actionEmulateFunction = EmulateFunctionAction.builder(this)
|
||||
.withContext(ProgramLocationActionContext.class)
|
||||
.enabledWhen(this::emulateFunctionEnabled)
|
||||
.popupWhen(this::emulateFunctionEnabled)
|
||||
.onAction(this::emulateFunctionActivated)
|
||||
.buildAndInstall(tool);
|
||||
actionEmulateAddThread = EmulateAddThreadAction.builder(this)
|
||||
.withContext(ProgramLocationActionContext.class)
|
||||
.enabledWhen(this::emulateAddThreadEnabled)
|
||||
@@ -487,6 +517,30 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
||||
}
|
||||
}
|
||||
|
||||
private boolean emulateFunctionEnabled(ProgramLocationActionContext ctx) {
|
||||
Program program = ctx.getProgram();
|
||||
if (program == null || program instanceof TraceProgramView) {
|
||||
return false;
|
||||
}
|
||||
Function function = program.getFunctionManager().getFunctionContaining(ctx.getAddress());
|
||||
if (function == null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void emulateFunctionActivated(ProgramLocationActionContext ctx) {
|
||||
Program program = ctx.getProgram();
|
||||
if (program == null) {
|
||||
return;
|
||||
}
|
||||
Function function = program.getFunctionManager().getFunctionContaining(ctx.getAddress());
|
||||
if (function == null) {
|
||||
return;
|
||||
}
|
||||
tool.showDialog(new DebuggerEmulateFunctionDialog(tool, function));
|
||||
}
|
||||
|
||||
private boolean emulateAddThreadEnabled(ProgramLocationActionContext ctx) {
|
||||
Program programOrView = ctx.getProgram();
|
||||
if (programOrView instanceof TraceProgramView view) {
|
||||
|
||||
+5
-12
@@ -17,7 +17,6 @@ package ghidra.app.plugin.core.debug.service.emulation.data;
|
||||
|
||||
import ghidra.debug.api.emulation.*;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.pcode.exec.trace.data.AbstractPcodeTraceAccess;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
|
||||
@@ -27,41 +26,35 @@ import ghidra.trace.model.guest.TracePlatform;
|
||||
* @param <S> the type of shared data-access shims provided
|
||||
* @param <L> the type of thread-local data-access shims provided
|
||||
*/
|
||||
public abstract class AbstractPcodeDebuggerAccess<S extends PcodeDebuggerMemoryAccess, L extends PcodeDebuggerRegistersAccess>
|
||||
extends AbstractPcodeTraceAccess<S, L>
|
||||
public abstract class AbstractPcodeDebuggerAccess<S extends PcodeDebuggerMemoryAccess,
|
||||
L extends PcodeDebuggerRegistersAccess> extends AbstractPcodeTraceAccess<S, L>
|
||||
implements PcodeDebuggerAccess {
|
||||
|
||||
protected final ServiceProvider provider;
|
||||
protected final Target target;
|
||||
|
||||
/**
|
||||
* Construct a shim
|
||||
*
|
||||
* @param provider the service provider (usually the tool)
|
||||
* @param target the target
|
||||
* @param platform the associated platform, having the same trace as the recorder
|
||||
* @param snap the associated snap
|
||||
*/
|
||||
public AbstractPcodeDebuggerAccess(ServiceProvider provider, Target target,
|
||||
TracePlatform platform, long snap) {
|
||||
public AbstractPcodeDebuggerAccess(Target target, TracePlatform platform, long snap) {
|
||||
super(platform, snap);
|
||||
this.provider = provider;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a shim
|
||||
*
|
||||
* @param provider the service provider (usually the tool)
|
||||
* @param target the target
|
||||
* @param platform the associated platform, having the same trace as the recorder
|
||||
* @param snap the associated snap
|
||||
* @param threadsSnap the snap to use when finding associated threads between trace and emulator
|
||||
*/
|
||||
public AbstractPcodeDebuggerAccess(ServiceProvider provider, Target target,
|
||||
TracePlatform platform, long snap, long threadsSnap) {
|
||||
public AbstractPcodeDebuggerAccess(Target target, TracePlatform platform, long snap,
|
||||
long threadsSnap) {
|
||||
super(platform, snap, threadsSnap);
|
||||
this.provider = provider;
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-7
@@ -25,9 +25,10 @@ import ghidra.trace.model.thread.TraceThread;
|
||||
/**
|
||||
* The default target-and-trace access shim for a session
|
||||
*/
|
||||
public class DefaultPcodeDebuggerAccess extends
|
||||
AbstractPcodeDebuggerAccess //
|
||||
<DefaultPcodeDebuggerMemoryAccess, DefaultPcodeDebuggerRegistersAccess> {
|
||||
public class DefaultPcodeDebuggerAccess extends AbstractPcodeDebuggerAccess<
|
||||
DefaultPcodeDebuggerMemoryAccess, DefaultPcodeDebuggerRegistersAccess> {
|
||||
|
||||
protected final ServiceProvider provider;
|
||||
|
||||
/**
|
||||
* Construct a shim
|
||||
@@ -39,7 +40,8 @@ public class DefaultPcodeDebuggerAccess extends
|
||||
*/
|
||||
public DefaultPcodeDebuggerAccess(ServiceProvider provider, Target target,
|
||||
TracePlatform platform, long snap) {
|
||||
super(provider, target, platform, snap);
|
||||
super(target, platform, snap);
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,7 +55,8 @@ public class DefaultPcodeDebuggerAccess extends
|
||||
*/
|
||||
public DefaultPcodeDebuggerAccess(ServiceProvider provider, Target target,
|
||||
TracePlatform platform, long snap, long threadsSnap) {
|
||||
super(provider, target, platform, snap, threadsSnap);
|
||||
super(target, platform, snap, threadsSnap);
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +78,7 @@ public class DefaultPcodeDebuggerAccess extends
|
||||
@Override
|
||||
protected DefaultPcodeDebuggerRegistersAccess newDataForLocalState(TraceThread thread,
|
||||
int frame) {
|
||||
return new DefaultPcodeDebuggerRegistersAccess(provider, target, platform, snap, thread,
|
||||
frame, viewport);
|
||||
return new DefaultPcodeDebuggerRegistersAccess(target, platform, snap, thread, frame,
|
||||
viewport);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-102
@@ -16,39 +16,25 @@
|
||||
package ghidra.app.plugin.core.debug.service.emulation.data;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.utils.AbstractMappedMemoryBytesVisitor;
|
||||
import ghidra.app.services.DebuggerStaticMappingService;
|
||||
import ghidra.debug.api.emulation.PcodeDebuggerMemoryAccess;
|
||||
import ghidra.debug.api.modules.MappedAddressRange;
|
||||
import ghidra.debug.api.modules.DebuggerAddressTranslator;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece;
|
||||
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceMemoryAccess;
|
||||
import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.trace.model.TraceTimeViewport;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* The default data-and-debugger-access shim for session memory
|
||||
*/
|
||||
public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAccess
|
||||
implements PcodeDebuggerMemoryAccess, InternalPcodeDebuggerDataAccess {
|
||||
public class DefaultPcodeDebuggerMemoryAccess extends TranslatedPcodeDebuggerMemoryAccess {
|
||||
|
||||
protected final ServiceProvider provider;
|
||||
protected final Target target;
|
||||
|
||||
/**
|
||||
* Construct a shim
|
||||
*
|
||||
* @param provider the service provider (usually the tool)
|
||||
* @param provider the service provider (usually the tool) to get the static mapping service
|
||||
* @param target the target
|
||||
* @param platform the associated platform, having the same trace as the recorder
|
||||
* @param snap the associated snap
|
||||
@@ -56,94 +42,12 @@ public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAcc
|
||||
*/
|
||||
protected DefaultPcodeDebuggerMemoryAccess(ServiceProvider provider, Target target,
|
||||
TracePlatform platform, long snap, TraceTimeViewport viewport) {
|
||||
super(platform, snap, viewport);
|
||||
super(target, platform, snap, viewport);
|
||||
this.provider = Objects.requireNonNull(provider);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLive() {
|
||||
return InternalPcodeDebuggerDataAccess.super.isLive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceProvider getServiceProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Target getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> readFromTargetMemory(AddressSetView guestView) {
|
||||
if (!isLive()) {
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
AddressSetView hostView = platform.mapGuestToHost(guestView);
|
||||
return target.readMemoryAsync(hostView, TaskMonitor.DUMMY).thenApply(__ -> true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> writeTargetMemory(Address address, byte[] data) {
|
||||
if (!isLive()) {
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
return target.writeMemoryAsync(address, data).thenApply(__ -> true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSetView readFromStaticImages(PcodeExecutorStatePiece<byte[], byte[]> piece,
|
||||
AddressSetView guestView) {
|
||||
// NOTE: If we expand to block, DON'T OVERWRITE KNOWN!
|
||||
DebuggerStaticMappingService mappingService =
|
||||
provider.getService(DebuggerStaticMappingService.class);
|
||||
if (mappingService == null) {
|
||||
return guestView;
|
||||
}
|
||||
|
||||
AddressSet remains = new AddressSet(guestView);
|
||||
try {
|
||||
boolean result = new AbstractMappedMemoryBytesVisitor(mappingService, new byte[4096]) {
|
||||
@Override
|
||||
protected int read(Memory memory, Address addr, byte[] dest, int size)
|
||||
throws MemoryAccessException {
|
||||
int read = super.read(memory, addr, dest, size);
|
||||
if (read < size) {
|
||||
Msg.warn(this,
|
||||
String.format(" Partial read of %s. Wanted %d bytes. Got %d.",
|
||||
addr, size, read));
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean visitRange(Program program, AddressRange progRng,
|
||||
MappedAddressRange mappedRng) throws MemoryAccessException {
|
||||
Msg.debug(this,
|
||||
"Filling in unknown trace memory in emulator using mapped image: " +
|
||||
program + ": " + progRng);
|
||||
return super.visitRange(program, progRng, mappedRng);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void visitData(Address hostAddr, byte[] data, int size) {
|
||||
Address guestAddr = platform.mapHostToGuest(hostAddr);
|
||||
piece.setVarInternal(guestAddr.getAddressSpace(), guestAddr.getOffset(), size,
|
||||
data);
|
||||
remains.delete(guestAddr, guestAddr.add(size));
|
||||
}
|
||||
}.visit(platform.getTrace(), snap, platform.mapGuestToHost(guestView));
|
||||
return result ? remains : guestView;
|
||||
}
|
||||
catch (MemoryAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
|
||||
return new DefaultPcodeDebuggerPropertyAccess<>(this, name, type);
|
||||
public DebuggerAddressTranslator getAddressTranslator() {
|
||||
return provider.getService(DebuggerStaticMappingService.class);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-7
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.emulation.data;
|
||||
|
||||
import ghidra.app.services.DebuggerStaticMappingService;
|
||||
import ghidra.debug.api.modules.DebuggerAddressTranslator;
|
||||
import ghidra.pcode.exec.trace.data.DefaultPcodeTracePropertyAccess;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.util.PropertyMap;
|
||||
@@ -52,12 +52,11 @@ public class DefaultPcodeDebuggerPropertyAccess<T>
|
||||
|
||||
@Override
|
||||
protected T whenNull(Address hostAddress) {
|
||||
DebuggerStaticMappingService mappingService =
|
||||
data.getServiceProvider().getService(DebuggerStaticMappingService.class);
|
||||
if (mappingService == null) {
|
||||
DebuggerAddressTranslator translator = data.getAddressTranslator();
|
||||
if (translator == null) {
|
||||
return super.whenNull(hostAddress);
|
||||
}
|
||||
ProgramLocation progLoc = mappingService.getOpenMappedLocation(new DefaultTraceLocation(
|
||||
ProgramLocation progLoc = translator.getOpenMappedLocation(new DefaultTraceLocation(
|
||||
data.getPlatform().getTrace(), null, Lifespan.at(data.getSnap()), hostAddress));
|
||||
if (progLoc == null) {
|
||||
return super.whenNull(hostAddress);
|
||||
|
||||
+7
-11
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -18,8 +18,8 @@ package ghidra.app.plugin.core.debug.service.emulation.data;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.debug.api.emulation.PcodeDebuggerRegistersAccess;
|
||||
import ghidra.debug.api.modules.DebuggerAddressTranslator;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceRegistersAccess;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
@@ -33,13 +33,11 @@ import ghidra.trace.model.thread.TraceThread;
|
||||
public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegistersAccess
|
||||
implements PcodeDebuggerRegistersAccess, InternalPcodeDebuggerDataAccess {
|
||||
|
||||
protected final ServiceProvider provider;
|
||||
protected final Target target;
|
||||
|
||||
/**
|
||||
* Construct a shim
|
||||
*
|
||||
* @param provider the service provider (usually the tool)
|
||||
* @param target the target
|
||||
* @param platform the associated platform, having the same trace as the recorder
|
||||
* @param snap the associated snap
|
||||
@@ -47,11 +45,9 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist
|
||||
* @param frame the associated frame, or 0 if not applicable
|
||||
* @param viewport the viewport, set to the same snapshot
|
||||
*/
|
||||
protected DefaultPcodeDebuggerRegistersAccess(ServiceProvider provider, Target target,
|
||||
TracePlatform platform, long snap, TraceThread thread, int frame,
|
||||
TraceTimeViewport viewport) {
|
||||
protected DefaultPcodeDebuggerRegistersAccess(Target target, TracePlatform platform, long snap,
|
||||
TraceThread thread, int frame, TraceTimeViewport viewport) {
|
||||
super(platform, snap, thread, frame, viewport);
|
||||
this.provider = provider;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@@ -61,8 +57,8 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceProvider getServiceProvider() {
|
||||
return provider;
|
||||
public DebuggerAddressTranslator getAddressTranslator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+20
-4
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -15,18 +15,34 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.emulation.data;
|
||||
|
||||
import ghidra.debug.api.modules.DebuggerAddressTranslator;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.pcode.exec.trace.data.InternalPcodeTraceDataAccess;
|
||||
import ghidra.trace.model.TraceTimeViewport;
|
||||
|
||||
@Internal
|
||||
public interface InternalPcodeDebuggerDataAccess extends InternalPcodeTraceDataAccess {
|
||||
ServiceProvider getServiceProvider();
|
||||
/**
|
||||
* {@return the address translator or null. If null is returned, then bytes cannot be loaded
|
||||
* from mapped images.}
|
||||
*/
|
||||
DebuggerAddressTranslator getAddressTranslator();
|
||||
|
||||
/**
|
||||
* {@return the target or null}
|
||||
*/
|
||||
Target getTarget();
|
||||
|
||||
/**
|
||||
* Check if the associated trace represents a live target
|
||||
* <p>
|
||||
* To be live, there must be a non-null target, that target must still be valid, i.e., connected
|
||||
* to a live debugger, and the current snapshot must have a viewport incorporating the target's
|
||||
* current snapshot.
|
||||
*
|
||||
* @return true if alive or false if dead/offline
|
||||
*/
|
||||
default boolean isLive() {
|
||||
Target target = getTarget();
|
||||
if (target == null || !target.isValid()) {
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/* ###
|
||||
* 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.service.emulation.data;
|
||||
|
||||
import ghidra.debug.api.modules.DebuggerAddressTranslator;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess;
|
||||
import ghidra.pcode.exec.trace.data.PcodeTraceAccess;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* The default target-and-trace access shim for a session with a provided
|
||||
* {@linkplain DebuggerAddressTranslator address translator}.
|
||||
*/
|
||||
public abstract class TranslatedPcodeDebuggerAccess extends AbstractPcodeDebuggerAccess<
|
||||
TranslatedPcodeDebuggerMemoryAccess, DefaultPcodeDebuggerRegistersAccess> {
|
||||
|
||||
/**
|
||||
* Construct a shim
|
||||
*
|
||||
* @param target the target
|
||||
* @param platform the associated platform, having the same trace as the recorder
|
||||
* @param snap the associated snap
|
||||
*/
|
||||
public TranslatedPcodeDebuggerAccess(Target target,
|
||||
TracePlatform platform, long snap) {
|
||||
super(target, platform, snap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a shim
|
||||
*
|
||||
* @param target the target
|
||||
* @param platform the associated platform, having the same trace as the recorder
|
||||
* @param snap the associated snap
|
||||
* @param threadsSnap the snap to use when finding associated threads between trace and emulator
|
||||
*/
|
||||
public TranslatedPcodeDebuggerAccess(Target target, TracePlatform platform, long snap,
|
||||
long threadsSnap) {
|
||||
super(target, platform, snap, threadsSnap);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the address translator or null. If null is returned, then bytes cannot be loaded
|
||||
* from mapped images.}
|
||||
*
|
||||
* @see InternalPcodeDebuggerDataAccess#getAddressTranslator()
|
||||
*/
|
||||
public abstract DebuggerAddressTranslator getAddressTranslator();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @implNote This does <em>not</em> return a Debugger access shim, but a Trace one, since we
|
||||
* never expect a delayed write to affect the target.
|
||||
*/
|
||||
@Override
|
||||
public PcodeTraceAccess deriveForWrite(long snap) {
|
||||
return new DefaultPcodeTraceAccess(platform, snap, threadsSnap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TranslatedPcodeDebuggerMemoryAccess newDataForSharedState() {
|
||||
return new TranslatedPcodeDebuggerMemoryAccess(target, platform, snap, viewport) {
|
||||
@Override
|
||||
public DebuggerAddressTranslator getAddressTranslator() {
|
||||
return TranslatedPcodeDebuggerAccess.this.getAddressTranslator();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DefaultPcodeDebuggerRegistersAccess newDataForLocalState(TraceThread thread,
|
||||
int frame) {
|
||||
return new DefaultPcodeDebuggerRegistersAccess(target, platform, snap, thread, frame,
|
||||
viewport);
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
/* ###
|
||||
* 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.service.emulation.data;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.utils.AbstractMappedMemoryBytesVisitor;
|
||||
import ghidra.debug.api.emulation.PcodeDebuggerMemoryAccess;
|
||||
import ghidra.debug.api.modules.DebuggerAddressTranslator;
|
||||
import ghidra.debug.api.modules.MappedAddressRange;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece;
|
||||
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceMemoryAccess;
|
||||
import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.trace.model.TraceTimeViewport;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A data-and-debugger-access shim for memory with a provided {@linkplain DebuggerAddressTranslator
|
||||
* address translator}.
|
||||
*/
|
||||
public abstract class TranslatedPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAccess
|
||||
implements PcodeDebuggerMemoryAccess, InternalPcodeDebuggerDataAccess {
|
||||
|
||||
protected final Target target;
|
||||
|
||||
/**
|
||||
* Construct a shim
|
||||
*
|
||||
* @param target the target
|
||||
* @param platform the associated platform, having the same trace as the recorder
|
||||
* @param snap the associated snap
|
||||
* @param viewport the viewport, set to the same snapshot
|
||||
*/
|
||||
protected TranslatedPcodeDebuggerMemoryAccess(Target target, TracePlatform platform, long snap,
|
||||
TraceTimeViewport viewport) {
|
||||
super(platform, snap, viewport);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLive() {
|
||||
return InternalPcodeDebuggerDataAccess.super.isLive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Target getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> readFromTargetMemory(AddressSetView guestView) {
|
||||
if (!isLive()) {
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
AddressSetView hostView = platform.mapGuestToHost(guestView);
|
||||
return target.readMemoryAsync(hostView, TaskMonitor.DUMMY).thenApply(__ -> true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> writeTargetMemory(Address address, byte[] data) {
|
||||
if (!isLive()) {
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
return target.writeMemoryAsync(address, data).thenApply(__ -> true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSetView readFromStaticImages(PcodeExecutorStatePiece<byte[], byte[]> piece,
|
||||
AddressSetView guestView) {
|
||||
// NOTE: If we expand to block, DON'T OVERWRITE KNOWN!
|
||||
DebuggerAddressTranslator translator = getAddressTranslator();
|
||||
if (translator == null) {
|
||||
return guestView;
|
||||
}
|
||||
|
||||
AddressSet remains = new AddressSet(guestView);
|
||||
try {
|
||||
boolean result = new AbstractMappedMemoryBytesVisitor(translator, new byte[4096]) {
|
||||
@Override
|
||||
protected int read(Memory memory, Address addr, byte[] dest, int size)
|
||||
throws MemoryAccessException {
|
||||
int read = super.read(memory, addr, dest, size);
|
||||
if (read < size) {
|
||||
Msg.warn(this,
|
||||
String.format(" Partial read of %s. Wanted %d bytes. Got %d.",
|
||||
addr, size, read));
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean visitRange(Program program, AddressRange progRng,
|
||||
MappedAddressRange mappedRng) throws MemoryAccessException {
|
||||
Msg.debug(this,
|
||||
"Filling in unknown trace memory in emulator using mapped image: " +
|
||||
program + ": " + progRng);
|
||||
return super.visitRange(program, progRng, mappedRng);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void visitData(Address hostAddr, byte[] data, int size) {
|
||||
Address guestAddr = platform.mapHostToGuest(hostAddr);
|
||||
piece.setVarInternal(guestAddr.getAddressSpace(), guestAddr.getOffset(), size,
|
||||
data);
|
||||
remains.delete(guestAddr, guestAddr.add(size));
|
||||
}
|
||||
}.visit(platform.getTrace(), snap, platform.mapGuestToHost(guestView));
|
||||
return result ? remains : guestView;
|
||||
}
|
||||
catch (MemoryAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
|
||||
return new DefaultPcodeDebuggerPropertyAccess<>(this, name, type);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -33,7 +33,7 @@ import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
public class DebuggerStaticMappingContext implements DebuggerAddressTranslator {
|
||||
|
||||
record ChangeCollector(DebuggerStaticMappingContext ctx, Set<Trace> traces,
|
||||
public record ChangeCollector(DebuggerStaticMappingContext ctx, Set<Trace> traces,
|
||||
Set<Program> programs) implements AutoCloseable {
|
||||
|
||||
static <T> Set<T> subtract(Set<T> a, Set<T> b) {
|
||||
|
||||
+8
-1
@@ -406,8 +406,15 @@ public class SymPcodeExecutor extends PcodeExecutor<Sym> {
|
||||
// This should always end a basic block, so just do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This could be a {@link PcodeOp#RETURN return} and the slaspec does not always set PC
|
||||
* explicitly, so do it here, but don't perform any actual control transfer.
|
||||
*/
|
||||
@Override
|
||||
protected void doExecuteIndirectBranch(PcodeOp op, PcodeFrame frame) {
|
||||
// This should always end a basic block, so just do nothing
|
||||
Sym offset = state.getVar(getIndirectBranchTarget(op), reason);
|
||||
branchToOffset(op, offset, frame);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -240,9 +240,10 @@ public class SymPcodeExecutorState implements PcodeExecutorState<Sym> {
|
||||
* @return the address (stack offset or register) of the return address
|
||||
*/
|
||||
public Address computeAddressOfReturn() {
|
||||
return switch (getVar(language.getProgramCounter(), Reason.INSPECT)) {
|
||||
Register pc = language.getProgramCounter();
|
||||
return switch (getVar(pc, Reason.INSPECT)) {
|
||||
case StackDerefSym stackVar -> cSpec.getStackSpace().getAddress(stackVar.offset());
|
||||
case RegisterSym regVar -> regVar.register().getAddress();
|
||||
case RegisterSym regVar when regVar.register() != pc -> regVar.register().getAddress();
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
+6
-6
@@ -19,7 +19,7 @@ import java.util.Collection;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.app.services.DebuggerStaticMappingService;
|
||||
import ghidra.debug.api.modules.DebuggerAddressTranslator;
|
||||
import ghidra.debug.api.modules.MappedAddressRange;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
@@ -39,20 +39,20 @@ import ghidra.util.MathUtilities;
|
||||
* bytes from the mapped programs, along with the trace address where they apply.
|
||||
*/
|
||||
public abstract class AbstractMappedMemoryBytesVisitor {
|
||||
private final DebuggerStaticMappingService mappingService;
|
||||
private final DebuggerAddressTranslator translator;
|
||||
private final byte[] buffer;
|
||||
|
||||
/**
|
||||
* Construct a visitor object
|
||||
*
|
||||
* @param mappingService the mapping service
|
||||
* @param translator the address translator
|
||||
* @param buffer a buffer for the data. This is passed directly into
|
||||
* {@link #visitData(Address, byte[], int)}. If a mapped range exceeds the buffer
|
||||
* size, the range is broken down into smaller pieces.
|
||||
*/
|
||||
public AbstractMappedMemoryBytesVisitor(DebuggerStaticMappingService mappingService,
|
||||
public AbstractMappedMemoryBytesVisitor(DebuggerAddressTranslator translator,
|
||||
byte[] buffer) {
|
||||
this.mappingService = Objects.requireNonNull(mappingService);
|
||||
this.translator = Objects.requireNonNull(translator);
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public abstract class AbstractMappedMemoryBytesVisitor {
|
||||
public boolean visit(Trace trace, long snap, AddressSetView hostView)
|
||||
throws MemoryAccessException {
|
||||
boolean result = false;
|
||||
for (Entry<Program, Collection<MappedAddressRange>> ent : mappingService
|
||||
for (Entry<Program, Collection<MappedAddressRange>> ent : translator
|
||||
.getOpenMappedViews(trace, hostView, snap)
|
||||
.entrySet()) {
|
||||
result |= visitProgram(ent.getKey(), ent.getValue());
|
||||
|
||||
+19
-12
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -23,12 +23,13 @@ import ghidra.framework.model.DomainObject;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utility.function.ExceptionalFunction;
|
||||
|
||||
public class ManagedDomainObject implements AutoCloseable {
|
||||
public class ManagedDomainObject<T extends DomainObject> implements AutoCloseable {
|
||||
public static final Cleaner CLEANER = Cleaner.create();
|
||||
|
||||
private static class ObjectState implements Runnable {
|
||||
private DomainObject obj;
|
||||
private static class ObjectState<T extends DomainObject> implements Runnable {
|
||||
private T obj;
|
||||
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
@@ -37,7 +38,7 @@ public class ManagedDomainObject implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized DomainObject get() {
|
||||
public synchronized T get() {
|
||||
if (!obj.getConsumerList().contains(this)) {
|
||||
throw new IllegalStateException("Domain object is closed");
|
||||
}
|
||||
@@ -45,20 +46,26 @@ public class ManagedDomainObject implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private final ObjectState state = new ObjectState();
|
||||
private final ObjectState<T> state = new ObjectState<>();
|
||||
|
||||
public ManagedDomainObject(DomainFile file, boolean okToUpgrade, boolean okToRecover,
|
||||
TaskMonitor monitor) throws VersionException, CancelledException, IOException {
|
||||
state.obj = file.getDomainObject(state, okToUpgrade, okToRecover, monitor);
|
||||
public ManagedDomainObject(DomainFile file, Class<T> type, TaskMonitor monitor)
|
||||
throws VersionException, CancelledException, IOException {
|
||||
state.obj = type.cast(file.getDomainObject(state, false, false, monitor));
|
||||
CLEANER.register(this, state);
|
||||
}
|
||||
|
||||
public <E extends Exception> ManagedDomainObject(ExceptionalFunction<Object, T, E> supplier)
|
||||
throws E {
|
||||
state.obj = supplier.apply(state);
|
||||
CLEANER.register(this, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
public void close() {
|
||||
state.run();
|
||||
}
|
||||
|
||||
public DomainObject get() {
|
||||
public T get() {
|
||||
return state.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.antlr.runtime.tree.CommonTreeNodeStream;
|
||||
|
||||
import ghidra.app.nav.NavigationUtils;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationIntegration;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.Mode;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighException;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
@@ -36,6 +37,7 @@ import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.pcode.exec.SleighProgramCompiler.ErrorCollectingPcodeParser;
|
||||
import ghidra.pcode.exec.SleighUtils.LitIdMode;
|
||||
import ghidra.pcode.exec.trace.*;
|
||||
import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer;
|
||||
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess;
|
||||
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
@@ -320,6 +322,39 @@ public enum DebuggerPcodeUtils {
|
||||
return compileExpression(provider, coordinates, current, source, LitIdMode.NORMAL);
|
||||
}
|
||||
|
||||
public record WriterAndState(Writer writer, PcodeExecutorState<byte[]> state) {}
|
||||
|
||||
/**
|
||||
* Get a p-code executor state for the given coordinates
|
||||
*
|
||||
* <p>
|
||||
* If a thread is included, the executor state will have access to both the memory and registers
|
||||
* in the context of that thread. Otherwise, only memory access is permitted.
|
||||
*
|
||||
* @param provider the service provider (usually the tool)
|
||||
* @param coordinates the coordinates
|
||||
* @param mode determines whether or not writes affect the target
|
||||
* @return the state
|
||||
*/
|
||||
public static WriterAndState executorStateForCoordinates(ServiceProvider provider,
|
||||
DebuggerCoordinates coordinates, Mode mode) {
|
||||
Trace trace = coordinates.getTrace();
|
||||
if (trace == null) {
|
||||
throw new IllegalArgumentException("Coordinates have no trace");
|
||||
}
|
||||
TracePlatform platform = coordinates.getPlatform();
|
||||
if (!(platform.getLanguage() instanceof SleighLanguage language)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Given trace or platform does not use a Sleigh language");
|
||||
}
|
||||
DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(provider,
|
||||
coordinates.getTarget(), platform, coordinates.getViewSnap());
|
||||
Writer writer = DebuggerEmulationIntegration.bytesWriteMode(access,
|
||||
coordinates.getThread(), coordinates.getFrame(), mode);
|
||||
return new WriterAndState(writer,
|
||||
new BytesPcodeExecutorState(language, writer.wrapFor(null)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a p-code executor state for the given coordinates
|
||||
*
|
||||
@@ -333,20 +368,7 @@ public enum DebuggerPcodeUtils {
|
||||
*/
|
||||
public static PcodeExecutorState<byte[]> executorStateForCoordinates(ServiceProvider provider,
|
||||
DebuggerCoordinates coordinates) {
|
||||
Trace trace = coordinates.getTrace();
|
||||
if (trace == null) {
|
||||
throw new IllegalArgumentException("Coordinates have no trace");
|
||||
}
|
||||
TracePlatform platform = coordinates.getPlatform();
|
||||
if (!(platform.getLanguage() instanceof SleighLanguage language)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Given trace or platform does not use a Sleigh language");
|
||||
}
|
||||
DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(provider,
|
||||
coordinates.getTarget(), platform, coordinates.getViewSnap());
|
||||
PcodeStateCallbacks cb = DebuggerEmulationIntegration.bytesImmediateWriteTarget(access,
|
||||
coordinates.getThread(), coordinates.getFrame());
|
||||
return new BytesPcodeExecutorState(language, cb);
|
||||
return executorStateForCoordinates(provider, coordinates, Mode.RW).state;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/* ###
|
||||
* 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.service.emulation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import db.Transaction;
|
||||
import ghidra.app.plugin.core.debug.gui.emulation.DebuggerEmulateFunctionDialog;
|
||||
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.listing.Function.FunctionUpdateType;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.test.ToyProgramBuilder;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.ConsoleTaskMonitor;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import help.screenshot.GhidraScreenShotGenerator;
|
||||
|
||||
public class DebuggerEmulationServicePluginScreenShots extends GhidraScreenShotGenerator {
|
||||
private static final TaskMonitor MONITOR = new ConsoleTaskMonitor();
|
||||
|
||||
private static Address addr(Program program, long offset) {
|
||||
return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
|
||||
}
|
||||
|
||||
private static AddressSetView set(Program program, long min, long max) {
|
||||
return new AddressSet(addr(program, min), addr(program, max));
|
||||
}
|
||||
|
||||
ProgramManager programManager;
|
||||
DebuggerEmulationServicePlugin emuService;
|
||||
Function function;
|
||||
Program progam;
|
||||
DataTypeManager dtm;
|
||||
|
||||
DataType dtInt;
|
||||
DataType dtCharPtrPtr;
|
||||
DataType dtStructPtr;
|
||||
|
||||
@Before
|
||||
public void setUpMine() throws Throwable {
|
||||
programManager = addPlugin(tool, ProgramManagerPlugin.class);
|
||||
emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDownMine() {
|
||||
if (program != null) {
|
||||
program.release(this);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Propose this replace waitForProgram
|
||||
public static void waitForDomainObject(DomainObject object) {
|
||||
object.flushEvents();
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
protected void intoProject(DomainObject obj) {
|
||||
waitForDomainObject(obj);
|
||||
DomainFolder rootFolder = tool.getProject().getProjectData().getRootFolder();
|
||||
waitForCondition(() -> {
|
||||
try {
|
||||
rootFolder.createFile(obj.getName(), obj, MONITOR);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidNameException | CancelledException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// Usually "object is busy". Try again.
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T extends DataType> T resolve(T dt) {
|
||||
try (Transaction tx = program.openTransaction("Resolved %s".formatted(dt))) {
|
||||
DataTypeConflictHandler handler = DataTypeConflictHandler.DEFAULT_HANDLER;
|
||||
return (T) dtm.resolve(dt, handler);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaptureDebuggerEmulateFunctionDialog() throws Throwable {
|
||||
program = createDefaultProgram("game", ToyProgramBuilder._X64, this);
|
||||
intoProject(program);
|
||||
dtm = program.getDataTypeManager();
|
||||
try (Transaction tx = program.openTransaction("Create Function")) {
|
||||
program.getMemory()
|
||||
.createInitializedBlock(".text", addr(program, 0x00400000), 0x10000, (byte) 0,
|
||||
MONITOR, false);
|
||||
function = program.getFunctionManager()
|
||||
.createFunction("parse_opts", addr(program, 0x00401000),
|
||||
set(program, 0x00401000, 0x00401100), SourceType.USER_DEFINED);
|
||||
|
||||
dtInt = resolve(IntegerDataType.dataType);
|
||||
dtCharPtrPtr = resolve(new PointerDataType(new PointerDataType(CharDataType.dataType)));
|
||||
|
||||
Structure st = new StructureDataType("Options", 0, dtm);
|
||||
st.add(dtInt, "width", "");
|
||||
st.add(dtInt, "height", "");
|
||||
dtStructPtr = resolve(new PointerDataType(st));
|
||||
|
||||
Variable returnVar = new ReturnParameterImpl(dtStructPtr, program);
|
||||
Variable param1 = new ParameterImpl("argc", dtInt, program);
|
||||
Variable param2 = new ParameterImpl("argv", dtCharPtrPtr, program);
|
||||
|
||||
function.updateFunction("default", returnVar, List.of(param1, param2),
|
||||
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, false, SourceType.ANALYSIS);
|
||||
}
|
||||
programManager.openProgram(program);
|
||||
goTo(tool, progam, function.getEntryPoint());
|
||||
performAction(emuService.actionEmulateFunction, false);
|
||||
|
||||
DebuggerEmulateFunctionDialog dialog =
|
||||
waitForDialogComponent(DebuggerEmulateFunctionDialog.class);
|
||||
|
||||
runSwing(() -> dialog.getComponent().requestFocus());
|
||||
captureDialog(dialog);
|
||||
}
|
||||
}
|
||||
+45
-57
@@ -93,8 +93,11 @@ public enum TraceEmulationIntegration {
|
||||
TraceThread thread, int frame) {
|
||||
Writer writer = new TraceWriter(access) {
|
||||
@Override
|
||||
protected PcodeTraceRegistersAccess getRegAccess(PcodeThread<?> ignored) {
|
||||
return access.getDataForLocalState(thread, frame);
|
||||
protected PcodeTraceDataAccess getDataAccess(PcodeTraceAccess access,
|
||||
AddressSpace space, PcodeThread<?> ignored) {
|
||||
return space.isRegisterSpace()
|
||||
? access.getDataForLocalState(thread, frame)
|
||||
: access.getDataForSharedState();
|
||||
}
|
||||
};
|
||||
writer.putHandler(new ImmediateBytesPieceHandler());
|
||||
@@ -310,12 +313,13 @@ public enum TraceEmulationIntegration {
|
||||
* also have state assigned to abstract addresses. In such cases, it is up to the handler to
|
||||
* track what has been written.
|
||||
*
|
||||
* @param writer the writer
|
||||
* @param into the destination trace access
|
||||
* @param thread the thread associated with the piece's state
|
||||
* @param piece the source state piece
|
||||
* @param written the portion that is known to have been written
|
||||
*/
|
||||
void writeDown(PcodeTraceDataAccess into, PcodeThread<?> thread,
|
||||
void writeDown(TraceWriter writer, PcodeTraceAccess into, PcodeThread<?> thread,
|
||||
PcodeExecutorStatePiece<A, T> piece, AddressSetView written);
|
||||
}
|
||||
|
||||
@@ -346,7 +350,7 @@ public enum TraceEmulationIntegration {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDown(PcodeTraceDataAccess into, PcodeThread<?> thread,
|
||||
public void writeDown(TraceWriter writer, PcodeTraceAccess into, PcodeThread<?> thread,
|
||||
PcodeExecutorStatePiece<Void, Void> piece, AddressSetView written) {
|
||||
}
|
||||
}
|
||||
@@ -398,20 +402,21 @@ public enum TraceEmulationIntegration {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDown(PcodeTraceDataAccess into, PcodeThread<?> thread,
|
||||
public void writeDown(TraceWriter writer, PcodeTraceAccess into, PcodeThread<?> thread,
|
||||
PcodeExecutorStatePiece<byte[], byte[]> piece, AddressSetView written) {
|
||||
for (AddressRange range : written) {
|
||||
AddressSpace space = range.getAddressSpace();
|
||||
if (space.isUniqueSpace()) {
|
||||
continue;
|
||||
}
|
||||
PcodeTraceDataAccess acc = writer.getDataAccess(into, space, thread);
|
||||
long lower = range.getMinAddress().getOffset();
|
||||
long fullLen = range.getLength();
|
||||
while (fullLen > 0) {
|
||||
int len = MathUtilities.unsignedMin(CHUNK_SIZE, fullLen);
|
||||
// NOTE: Would prefer less copying and less heap garbage....
|
||||
byte[] bytes = piece.getVarInternal(space, lower, len, Reason.INSPECT);
|
||||
into.putBytes(space.getAddress(lower), ByteBuffer.wrap(bytes));
|
||||
acc.putBytes(space.getAddress(lower), ByteBuffer.wrap(bytes));
|
||||
|
||||
lower += bytes.length;
|
||||
fullLen -= bytes.length;
|
||||
@@ -564,14 +569,15 @@ public enum TraceEmulationIntegration {
|
||||
* portion.
|
||||
*/
|
||||
@Override
|
||||
public void writeDown(PcodeTraceDataAccess into, PcodeThread<?> thread,
|
||||
public void writeDown(TraceWriter writer, PcodeTraceAccess into, PcodeThread<?> thread,
|
||||
PcodeExecutorStatePiece<A, T> piece, AddressSetView written) {
|
||||
PcodeTracePropertyAccess<P> property =
|
||||
into.getPropertyAccess(getPropertyName(), getPropertyType());
|
||||
AddressSet remains = new AddressSet(written);
|
||||
while (!remains.isEmpty()) {
|
||||
Address cur = remains.getMinAddress();
|
||||
AddressSpace space = cur.getAddressSpace();
|
||||
PcodeTraceDataAccess acc = writer.getDataAccess(into, space, thread);
|
||||
PcodeTracePropertyAccess<P> property =
|
||||
acc.getPropertyAccess(getPropertyName(), getPropertyType());
|
||||
Entry<Long, T> entry = piece.getNextEntryInternal(space, cur.getOffset());
|
||||
if (entry == null) {
|
||||
remains.delete(space.getMinAddress(), space.getMaxAddress());
|
||||
@@ -662,18 +668,19 @@ public enum TraceEmulationIntegration {
|
||||
protected final PcodeTraceMemoryAccess memAccess;
|
||||
protected final Map<PcodeThread<?>, PcodeTraceRegistersAccess> regAccess = new HashMap<>();
|
||||
|
||||
record PieceInfo(PcodeThread<?> thread, AddressSet written) {}
|
||||
|
||||
/**
|
||||
* An address set to track what has actually been written. It's not enough to just use the
|
||||
* {@link SemisparseByteArray}'s initialized set, as that may be caching bytes from the
|
||||
* trace which are still {@link TraceMemoryState#UNKNOWN}.
|
||||
*/
|
||||
protected final AddressSet memWritten = new AddressSet();
|
||||
protected final Map<PcodeThread<?>, AddressSet> regsWritten = new HashMap<>();
|
||||
//protected final AddressSet memWritten = new AddressSet();
|
||||
//protected final Map<PcodeThread<?>, AddressSet> regsWritten = new HashMap<>();
|
||||
protected final Map<PcodeExecutorStatePiece<?, ?>, PieceInfo> pieces = new HashMap<>();
|
||||
|
||||
protected final Map<PieceType<?, ?>, PieceHandler<?, ?>> handlers = new HashMap<>();
|
||||
|
||||
private PcodeMachine<?> emulator;
|
||||
|
||||
/**
|
||||
* Construct a writer which sources state from the given access shim
|
||||
*
|
||||
@@ -689,11 +696,6 @@ public enum TraceEmulationIntegration {
|
||||
handlers.put(PieceType.forHandler(handler), handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emulatorCreated(PcodeMachine<Object> emulator) {
|
||||
this.emulator = emulator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void threadCreated(PcodeThread<Object> thread) {
|
||||
access.getDataForLocalState(thread, 0).initializeThreadContext(thread);
|
||||
@@ -715,28 +717,17 @@ public enum TraceEmulationIntegration {
|
||||
* @param piece the piece
|
||||
* @param written the logged portions written
|
||||
*/
|
||||
protected <B, U> void writePieceDown(PcodeTraceDataAccess into, PcodeThread<?> thread,
|
||||
protected <B, U> void writePieceDown(PcodeTraceAccess into, PcodeThread<?> thread,
|
||||
PcodeExecutorStatePiece<B, U> piece, AddressSetView written) {
|
||||
PieceHandler<B, U> handler = handlerFor(piece);
|
||||
handler.writeDown(into, thread, piece, written);
|
||||
handler.writeDown(this, into, thread, piece, written);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDown(PcodeTraceAccess into) {
|
||||
PcodeTraceMemoryAccess memInto = into.getDataForSharedState();
|
||||
for (PcodeExecutorStatePiece<?, ?> piece : emulator.getSharedState()
|
||||
.streamPieces()
|
||||
.toList()) {
|
||||
writePieceDown(memInto, null, piece, memWritten);
|
||||
}
|
||||
for (PcodeThread<?> thread : emulator.getAllThreads()) {
|
||||
PcodeTraceRegistersAccess regInto = into.getDataForLocalState(thread, 0);
|
||||
AddressSetView written = regsWritten.getOrDefault(thread, new AddressSet());
|
||||
for (PcodeExecutorStatePiece<?, ?> piece : thread.getState()
|
||||
.streamPieces()
|
||||
.toList()) {
|
||||
writePieceDown(regInto, thread, piece, written);
|
||||
}
|
||||
for (Entry<PcodeExecutorStatePiece<?, ?>, PieceInfo> ent : pieces.entrySet()) {
|
||||
PieceInfo info = ent.getValue();
|
||||
writePieceDown(into, info.thread, ent.getKey(), info.written);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,22 +736,20 @@ public enum TraceEmulationIntegration {
|
||||
writeDown(access.deriveForWrite(snap));
|
||||
}
|
||||
|
||||
protected PcodeTraceRegistersAccess getRegAccess(PcodeThread<?> thread) {
|
||||
// Always use frame 0
|
||||
return regAccess.computeIfAbsent(thread, t -> access.getDataForLocalState(t, 0));
|
||||
protected PcodeTraceDataAccess getDataAccess(PcodeTraceAccess access, AddressSpace space,
|
||||
PcodeThread<?> thread) {
|
||||
return space.isRegisterSpace()
|
||||
? access.getDataForLocalState(thread, 0)
|
||||
: access.getDataForSharedState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B, U> void dataWritten(PcodeThread<Object> thread,
|
||||
PcodeExecutorStatePiece<B, U> piece,
|
||||
Address address, int length, U value) {
|
||||
PcodeTraceDataAccess acc = address.isRegisterAddress()
|
||||
? getRegAccess(thread)
|
||||
: memAccess;
|
||||
AddressSet written = address.isRegisterAddress()
|
||||
? regsWritten.computeIfAbsent(thread, t -> new AddressSet())
|
||||
: memWritten;
|
||||
if (handlerFor(piece).dataWritten(acc, written, thread, piece, address, length,
|
||||
PcodeExecutorStatePiece<B, U> piece, Address address, int length, U value) {
|
||||
PcodeTraceDataAccess acc = getDataAccess(access, address.getAddressSpace(), thread);
|
||||
PieceInfo info =
|
||||
pieces.computeIfAbsent(piece, p -> new PieceInfo(thread, new AddressSet()));
|
||||
if (handlerFor(piece).dataWritten(acc, info.written, thread, piece, address, length,
|
||||
value)) {
|
||||
return;
|
||||
}
|
||||
@@ -769,12 +758,12 @@ public enum TraceEmulationIntegration {
|
||||
}
|
||||
Address end = address.addWrap(length - 1);
|
||||
if (address.compareTo(end) <= 0) {
|
||||
written.add(address, end);
|
||||
info.written.add(address, end);
|
||||
}
|
||||
else {
|
||||
AddressSpace space = address.getAddressSpace();
|
||||
written.add(address, space.getMaxAddress());
|
||||
written.add(space.getMinAddress(), end);
|
||||
info.written.add(address, space.getMaxAddress());
|
||||
info.written.add(space.getMinAddress(), end);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,19 +771,18 @@ public enum TraceEmulationIntegration {
|
||||
public <B, U> void dataWritten(PcodeThread<Object> thread,
|
||||
PcodeExecutorStatePiece<B, U> piece, AddressSpace space, B offset, int length,
|
||||
U value) {
|
||||
PcodeTraceDataAccess acc = space.isRegisterSpace() ? getRegAccess(thread) : memAccess;
|
||||
AddressSet written = space.isRegisterSpace()
|
||||
? regsWritten.computeIfAbsent(thread, t -> new AddressSet())
|
||||
: memWritten;
|
||||
handlerFor(piece).abstractWritten(acc, written, thread, piece, space, offset, length,
|
||||
value);
|
||||
PcodeTraceDataAccess acc = getDataAccess(access, space, thread);
|
||||
PieceInfo info =
|
||||
pieces.computeIfAbsent(piece, p -> new PieceInfo(thread, new AddressSet()));
|
||||
handlerFor(piece).abstractWritten(acc, info.written, thread, piece, space, offset,
|
||||
length, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B, U> int readUninitialized(PcodeThread<Object> thread,
|
||||
PcodeExecutorStatePiece<B, U> piece, AddressSpace space, B offset, int length,
|
||||
Reason reason) {
|
||||
PcodeTraceDataAccess acc = space.isRegisterSpace() ? getRegAccess(thread) : memAccess;
|
||||
PcodeTraceDataAccess acc = getDataAccess(access, space, thread);
|
||||
return handlerFor(piece).abstractReadUninit(acc, thread, piece, space, offset, length,
|
||||
reason);
|
||||
}
|
||||
@@ -809,7 +797,7 @@ public enum TraceEmulationIntegration {
|
||||
if (space.isUniqueSpace()) {
|
||||
return set;
|
||||
}
|
||||
PcodeTraceDataAccess acc = space.isRegisterSpace() ? getRegAccess(thread) : memAccess;
|
||||
PcodeTraceDataAccess acc = getDataAccess(access, space, thread);
|
||||
return handlerFor(piece).readUninitialized(acc, thread, piece, set);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -16,6 +16,8 @@
|
||||
package ghidra.pcode.exec.trace.data;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
@@ -36,6 +38,8 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
|
||||
|
||||
protected final TraceMemoryManager mm;
|
||||
|
||||
protected final Map<String, PcodeTracePropertyAccess<?>> properties = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Construct a shim
|
||||
*
|
||||
@@ -204,7 +208,11 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
|
||||
return new DefaultPcodeTracePropertyAccess<>(this, name, type);
|
||||
synchronized (properties) {
|
||||
return (PcodeTracePropertyAccess<T>) properties.computeIfAbsent(name,
|
||||
n -> new DefaultPcodeTracePropertyAccess<>(this, n, type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -26,6 +26,7 @@ import java.util.List;
|
||||
import org.junit.Test;
|
||||
|
||||
import db.Transaction;
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.assembler.*;
|
||||
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock;
|
||||
import ghidra.pcode.emu.PcodeEmulator;
|
||||
@@ -68,7 +69,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest
|
||||
|
||||
emu.getSharedState().setVar(tb.addr(0x00400000), 0, false, tb.arr());
|
||||
TraceWriter tw = (TraceWriter) writer;
|
||||
assertEquals(tb.set(), tw.memWritten);
|
||||
assertEquals(tb.set(), Unique.assertOne(tw.pieces.entrySet()).getValue().written());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user