mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 01:39:19 +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();
|
createActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void cleanConnectionNode(TraceRmiConnectionNode node) {
|
||||||
|
rootNode.cleanConnectionNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
private void buildMainPanel() {
|
private void buildMainPanel() {
|
||||||
mainPanel = new JPanel(new BorderLayout());
|
mainPanel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
|
|||||||
+14
-3
@@ -42,7 +42,13 @@ public class TraceRmiConnectionNode extends AbstractTraceRmiManagerNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayText() {
|
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
|
@Override
|
||||||
@@ -52,8 +58,13 @@ public class TraceRmiConnectionNode extends AbstractTraceRmiManagerNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getToolTip() {
|
public String getToolTip() {
|
||||||
return "Trace RMI Connection to " + connection.getDescription() + " at " +
|
try {
|
||||||
connection.getRemoteAddress();
|
return "Trace RMI Connection to " + connection.getDescription() + " at " +
|
||||||
|
connection.getRemoteAddress();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return "Error: " + e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+10
@@ -25,6 +25,7 @@ import ghidra.debug.api.target.Target;
|
|||||||
import ghidra.debug.api.target.TargetPublicationListener;
|
import ghidra.debug.api.target.TargetPublicationListener;
|
||||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||||
import ghidra.debug.api.tracermi.*;
|
import ghidra.debug.api.tracermi.*;
|
||||||
|
import ghidra.lifecycle.Internal;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.Swing;
|
||||||
|
|
||||||
@@ -83,6 +84,15 @@ public class TraceRmiServiceNode extends AbstractTraceRmiManagerNode
|
|||||||
removeNode(node);
|
removeNode(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
public void cleanConnectionNode(TraceRmiConnectionNode node) {
|
||||||
|
TraceRmiConnection connection = node.getConnection();
|
||||||
|
synchronized (connectionNodes) {
|
||||||
|
connectionNodes.remove(connection);
|
||||||
|
}
|
||||||
|
removeNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
private TraceRmiAcceptorNode newAcceptorNode(TraceRmiAcceptor acceptor) {
|
private TraceRmiAcceptorNode newAcceptorNode(TraceRmiAcceptor acceptor) {
|
||||||
return new TraceRmiAcceptorNode(provider, 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/DebuggerCopyActionsPlugin/images/DebuggerCopyIntoProgramDialog.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerDisassemblerPlugin/DebuggerDisassemblerPlugin.html||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/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/DebuggerListingPlugin.html||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerListingPlugin.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
|
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>
|
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>
|
<H3><A name="configure_emulator"></A> Configure Emulator</H3>
|
||||||
|
|
||||||
<P>This action is always available. It lists emulators available for configuration. Selecting
|
<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,
|
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
|
the effects of your change may not appear, since the trace manager may recall a cached snapshot
|
||||||
instead of actually emulating.</P>
|
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>
|
</BODY>
|
||||||
</HTML>
|
</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=
|
debugger. This failure can be overcome by enabling <A href=
|
||||||
"help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html">Force Full View</A>.</P>
|
"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>
|
<UL>
|
||||||
<LI><CODE>00401234</CODE> — A constant address</LI>
|
<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));
|
AddressSet toRead = new AddressSet(quantize(12, visible));
|
||||||
for (Lifespan span : coordinates.getView().getViewport().getOrderedSpans()) {
|
for (Lifespan span : coordinates.getView().getViewport().getOrderedSpans()) {
|
||||||
AddressSetView alreadyKnown =
|
AddressSetView alreadyKnown =
|
||||||
mm.getAddressesWithState(span.lmin(), visible, StatePredicate.IS_KNOWN);
|
mm.getAddressesWithState(span, toRead, StatePredicate.IS_KNOWN);
|
||||||
toRead.delete(alreadyKnown);
|
toRead.delete(alreadyKnown);
|
||||||
if (span.lmax() != span.lmin() || toRead.isEmpty()) {
|
if (span.lmax() != span.lmin() || toRead.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
|
|||||||
+9
-31
@@ -1339,38 +1339,16 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAllInvolvedTracesUsingEmulatedBreakpoints(ActionContext ctx) {
|
private boolean isAtLeastOneBreakpoint(ActionContext ctx) {
|
||||||
if (controlService == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Set<Trace> traces = new HashSet<>();
|
|
||||||
Collection<LogicalBreakpoint> breakpoints = getLogicalBreakpoints(ctx);
|
Collection<LogicalBreakpoint> breakpoints = getLogicalBreakpoints(ctx);
|
||||||
if (breakpoints != null) {
|
if (breakpoints != null && !breakpoints.isEmpty()) {
|
||||||
if (breakpoints.isEmpty()) {
|
return true;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (LogicalBreakpoint lb : breakpoints) {
|
|
||||||
traces.addAll(lb.getParticipatingTraces());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (ctx instanceof DebuggerBreakpointLocationsActionContext locCtx) {
|
if (ctx instanceof DebuggerBreakpointLocationsActionContext locCtx &&
|
||||||
Collection<TraceBreakpointLocation> locations = locCtx.getLocations();
|
!locCtx.getLocations().isEmpty()) {
|
||||||
if (locations.isEmpty()) {
|
return true;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (TraceBreakpointLocation loc : locations) {
|
|
||||||
traces.add(loc.getTrace());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (Trace trace : traces) {
|
|
||||||
if (!controlService.getCurrentMode(trace).useEmulatedBreakpoints()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Set<TraceBreakpointKind> EXECUTE_KINDS =
|
private static final Set<TraceBreakpointKind> EXECUTE_KINDS =
|
||||||
@@ -1400,11 +1378,11 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPopupSetCondition(ActionContext ctx) {
|
private boolean isPopupSetCondition(ActionContext ctx) {
|
||||||
return isAllInvolvedTracesUsingEmulatedBreakpoints(ctx) && isAllBreakpointsExecution(ctx);
|
return isAtLeastOneBreakpoint(ctx) && isAllBreakpointsExecution(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPopupSetInjection(ActionContext ctx) {
|
private boolean isPopupSetInjection(ActionContext ctx) {
|
||||||
return isAllInvolvedTracesUsingEmulatedBreakpoints(ctx) && isAllBreakpointsExecution(ctx);
|
return isAtLeastOneBreakpoint(ctx) && isAllBreakpointsExecution(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String deriveCurrentSleigh(ActionContext 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 expression;
|
||||||
private String typePath;
|
private String typePath;
|
||||||
private DataType dataType;
|
private DataType dataType;
|
||||||
private SettingsImpl settings = new SettingsImpl();
|
private final SettingsImpl settings = new SettingsImpl();
|
||||||
private SavedSettings savedSettings = new SavedSettings(settings);
|
private SavedSettings savedSettings = new SavedSettings(settings);
|
||||||
private String comment;
|
private String comment;
|
||||||
|
|
||||||
|
|||||||
+26
-3
@@ -48,7 +48,7 @@ public class ProgramBreakpoint {
|
|||||||
private static final Gson GSON = new GsonBuilder().create();
|
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 {
|
static class BreakpointProperties {
|
||||||
public String name;
|
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 Program program;
|
||||||
private final Address address;
|
private final Address address;
|
||||||
private final ProgramLocation location;
|
private final ProgramLocation location;
|
||||||
@@ -399,10 +418,14 @@ public class ProgramBreakpoint {
|
|||||||
*/
|
*/
|
||||||
public Bookmark getBookmark() {
|
public Bookmark getBookmark() {
|
||||||
Bookmark eBookmark = this.eBookmark;
|
Bookmark eBookmark = this.eBookmark;
|
||||||
if (eBookmark != null) {
|
if (eBookmark != null && !eBookmark.isDeleted()) {
|
||||||
return eBookmark;
|
return eBookmark;
|
||||||
}
|
}
|
||||||
return dBookmark;
|
Bookmark dBookmark = this.dBookmark;
|
||||||
|
if (dBookmark != null && !dBookmark.isDeleted()) {
|
||||||
|
return dBookmark;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getComment() {
|
protected String getComment() {
|
||||||
|
|||||||
+34
-13
@@ -23,7 +23,8 @@ import ghidra.pcode.emu.PcodeThread;
|
|||||||
import ghidra.pcode.exec.*;
|
import ghidra.pcode.exec.*;
|
||||||
import ghidra.pcode.exec.trace.TraceEmulationIntegration;
|
import ghidra.pcode.exec.trace.TraceEmulationIntegration;
|
||||||
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.program.model.address.*;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
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
|
* Create state callbacks that lazily load data and writes changes to the given access shim.
|
||||||
* 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>
|
* <p>
|
||||||
* Reads may be redirected to the target. If redirected, writes are immediately sent to the
|
* 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.
|
* target and presumably stored into the trace at the same snapshot as state is sourced.
|
||||||
*
|
|
||||||
* <p>
|
* <p>
|
||||||
* Use this instead of {@link #bytesImmediateWriteTarget(PcodeDebuggerAccess)} when interfacing
|
* Use this instead of {@link #bytesImmediateWriteTarget(PcodeDebuggerAccess)} when interfacing
|
||||||
* directly with a {@link PcodeExecutorState} vice a {@link PcodeEmulator}.
|
* directly with a {@link PcodeExecutorState} vice a {@link PcodeEmulator}.
|
||||||
@@ -92,15 +121,7 @@ public enum DebuggerEmulationIntegration {
|
|||||||
*/
|
*/
|
||||||
public static PcodeStateCallbacks bytesImmediateWriteTarget(PcodeDebuggerAccess access,
|
public static PcodeStateCallbacks bytesImmediateWriteTarget(PcodeDebuggerAccess access,
|
||||||
TraceThread thread, int frame) {
|
TraceThread thread, int frame) {
|
||||||
PcodeDebuggerRegistersAccess regAcc = access.getDataForLocalState(thread, frame);
|
return bytesWriteMode(access, thread, frame, Mode.RW).wrapFor(null);
|
||||||
Writer writer = new TraceWriter(access) {
|
|
||||||
@Override
|
|
||||||
protected PcodeTraceRegistersAccess getRegAccess(PcodeThread<?> ignored) {
|
|
||||||
return regAcc;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
writer.putHandler(new TargetBytesPieceHandler(Mode.RW));
|
|
||||||
return writer.wrapFor(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static <T> T waitTimeout(CompletableFuture<T> future) {
|
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.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
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.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.async.AsyncLazyMap;
|
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.DefaultPcodeTraceAccess;
|
||||||
import ghidra.pcode.exec.trace.data.PcodeTraceAccess;
|
import ghidra.pcode.exec.trace.data.PcodeTraceAccess;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.model.*;
|
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 {
|
interface EmulateAddThreadAction {
|
||||||
String NAME = "Add Emulated Thread to Trace";
|
String NAME = "Add Emulated Thread to Trace";
|
||||||
String DESCRIPTION = "Add an emulated thread to the current trace starting here";
|
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;
|
private AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
DockingAction actionEmulateProgram;
|
DockingAction actionEmulateProgram;
|
||||||
|
DockingAction actionEmulateFunction;
|
||||||
DockingAction actionEmulateAddThread;
|
DockingAction actionEmulateAddThread;
|
||||||
DockingAction actionInvalidateCache;
|
DockingAction actionInvalidateCache;
|
||||||
Map<Class<? extends EmulatorFactory>, ToggleDockingAction> actionsChooseEmulatorFactory =
|
Map<Class<? extends EmulatorFactory>, ToggleDockingAction> actionsChooseEmulatorFactory =
|
||||||
@@ -399,6 +423,12 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||||||
.popupWhen(this::emulateProgramEnabled)
|
.popupWhen(this::emulateProgramEnabled)
|
||||||
.onAction(this::emulateProgramActivated)
|
.onAction(this::emulateProgramActivated)
|
||||||
.buildAndInstall(tool);
|
.buildAndInstall(tool);
|
||||||
|
actionEmulateFunction = EmulateFunctionAction.builder(this)
|
||||||
|
.withContext(ProgramLocationActionContext.class)
|
||||||
|
.enabledWhen(this::emulateFunctionEnabled)
|
||||||
|
.popupWhen(this::emulateFunctionEnabled)
|
||||||
|
.onAction(this::emulateFunctionActivated)
|
||||||
|
.buildAndInstall(tool);
|
||||||
actionEmulateAddThread = EmulateAddThreadAction.builder(this)
|
actionEmulateAddThread = EmulateAddThreadAction.builder(this)
|
||||||
.withContext(ProgramLocationActionContext.class)
|
.withContext(ProgramLocationActionContext.class)
|
||||||
.enabledWhen(this::emulateAddThreadEnabled)
|
.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) {
|
private boolean emulateAddThreadEnabled(ProgramLocationActionContext ctx) {
|
||||||
Program programOrView = ctx.getProgram();
|
Program programOrView = ctx.getProgram();
|
||||||
if (programOrView instanceof TraceProgramView view) {
|
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.emulation.*;
|
||||||
import ghidra.debug.api.target.Target;
|
import ghidra.debug.api.target.Target;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
|
||||||
import ghidra.pcode.exec.trace.data.AbstractPcodeTraceAccess;
|
import ghidra.pcode.exec.trace.data.AbstractPcodeTraceAccess;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
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 <S> the type of shared data-access shims provided
|
||||||
* @param <L> the type of thread-local 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>
|
public abstract class AbstractPcodeDebuggerAccess<S extends PcodeDebuggerMemoryAccess,
|
||||||
extends AbstractPcodeTraceAccess<S, L>
|
L extends PcodeDebuggerRegistersAccess> extends AbstractPcodeTraceAccess<S, L>
|
||||||
implements PcodeDebuggerAccess {
|
implements PcodeDebuggerAccess {
|
||||||
|
|
||||||
protected final ServiceProvider provider;
|
|
||||||
protected final Target target;
|
protected final Target target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a shim
|
* Construct a shim
|
||||||
*
|
*
|
||||||
* @param provider the service provider (usually the tool)
|
|
||||||
* @param target the target
|
* @param target the target
|
||||||
* @param platform the associated platform, having the same trace as the recorder
|
* @param platform the associated platform, having the same trace as the recorder
|
||||||
* @param snap the associated snap
|
* @param snap the associated snap
|
||||||
*/
|
*/
|
||||||
public AbstractPcodeDebuggerAccess(ServiceProvider provider, Target target,
|
public AbstractPcodeDebuggerAccess(Target target, TracePlatform platform, long snap) {
|
||||||
TracePlatform platform, long snap) {
|
|
||||||
super(platform, snap);
|
super(platform, snap);
|
||||||
this.provider = provider;
|
|
||||||
this.target = target;
|
this.target = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a shim
|
* Construct a shim
|
||||||
*
|
*
|
||||||
* @param provider the service provider (usually the tool)
|
|
||||||
* @param target the target
|
* @param target the target
|
||||||
* @param platform the associated platform, having the same trace as the recorder
|
* @param platform the associated platform, having the same trace as the recorder
|
||||||
* @param snap the associated snap
|
* @param snap the associated snap
|
||||||
* @param threadsSnap the snap to use when finding associated threads between trace and emulator
|
* @param threadsSnap the snap to use when finding associated threads between trace and emulator
|
||||||
*/
|
*/
|
||||||
public AbstractPcodeDebuggerAccess(ServiceProvider provider, Target target,
|
public AbstractPcodeDebuggerAccess(Target target, TracePlatform platform, long snap,
|
||||||
TracePlatform platform, long snap, long threadsSnap) {
|
long threadsSnap) {
|
||||||
super(platform, snap, threadsSnap);
|
super(platform, snap, threadsSnap);
|
||||||
this.provider = provider;
|
|
||||||
this.target = target;
|
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
|
* The default target-and-trace access shim for a session
|
||||||
*/
|
*/
|
||||||
public class DefaultPcodeDebuggerAccess extends
|
public class DefaultPcodeDebuggerAccess extends AbstractPcodeDebuggerAccess<
|
||||||
AbstractPcodeDebuggerAccess //
|
DefaultPcodeDebuggerMemoryAccess, DefaultPcodeDebuggerRegistersAccess> {
|
||||||
<DefaultPcodeDebuggerMemoryAccess, DefaultPcodeDebuggerRegistersAccess> {
|
|
||||||
|
protected final ServiceProvider provider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a shim
|
* Construct a shim
|
||||||
@@ -39,7 +40,8 @@ public class DefaultPcodeDebuggerAccess extends
|
|||||||
*/
|
*/
|
||||||
public DefaultPcodeDebuggerAccess(ServiceProvider provider, Target target,
|
public DefaultPcodeDebuggerAccess(ServiceProvider provider, Target target,
|
||||||
TracePlatform platform, long snap) {
|
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,
|
public DefaultPcodeDebuggerAccess(ServiceProvider provider, Target target,
|
||||||
TracePlatform platform, long snap, long threadsSnap) {
|
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
|
@Override
|
||||||
protected DefaultPcodeDebuggerRegistersAccess newDataForLocalState(TraceThread thread,
|
protected DefaultPcodeDebuggerRegistersAccess newDataForLocalState(TraceThread thread,
|
||||||
int frame) {
|
int frame) {
|
||||||
return new DefaultPcodeDebuggerRegistersAccess(provider, target, platform, snap, thread,
|
return new DefaultPcodeDebuggerRegistersAccess(target, platform, snap, thread, frame,
|
||||||
frame, viewport);
|
viewport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-102
@@ -16,39 +16,25 @@
|
|||||||
package ghidra.app.plugin.core.debug.service.emulation.data;
|
package ghidra.app.plugin.core.debug.service.emulation.data;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.utils.AbstractMappedMemoryBytesVisitor;
|
|
||||||
import ghidra.app.services.DebuggerStaticMappingService;
|
import ghidra.app.services.DebuggerStaticMappingService;
|
||||||
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.debug.api.target.Target;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
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.TraceTimeViewport;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
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
|
* The default data-and-debugger-access shim for session memory
|
||||||
*/
|
*/
|
||||||
public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAccess
|
public class DefaultPcodeDebuggerMemoryAccess extends TranslatedPcodeDebuggerMemoryAccess {
|
||||||
implements PcodeDebuggerMemoryAccess, InternalPcodeDebuggerDataAccess {
|
|
||||||
|
|
||||||
protected final ServiceProvider provider;
|
protected final ServiceProvider provider;
|
||||||
protected final Target target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a shim
|
* 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 target the target
|
||||||
* @param platform the associated platform, having the same trace as the recorder
|
* @param platform the associated platform, having the same trace as the recorder
|
||||||
* @param snap the associated snap
|
* @param snap the associated snap
|
||||||
@@ -56,94 +42,12 @@ public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAcc
|
|||||||
*/
|
*/
|
||||||
protected DefaultPcodeDebuggerMemoryAccess(ServiceProvider provider, Target target,
|
protected DefaultPcodeDebuggerMemoryAccess(ServiceProvider provider, Target target,
|
||||||
TracePlatform platform, long snap, TraceTimeViewport viewport) {
|
TracePlatform platform, long snap, TraceTimeViewport viewport) {
|
||||||
super(platform, snap, viewport);
|
super(target, platform, snap, viewport);
|
||||||
this.provider = Objects.requireNonNull(provider);
|
this.provider = Objects.requireNonNull(provider);
|
||||||
this.target = target;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLive() {
|
public DebuggerAddressTranslator getAddressTranslator() {
|
||||||
return InternalPcodeDebuggerDataAccess.super.isLive();
|
return provider.getService(DebuggerStaticMappingService.class);
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-7
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.service.emulation.data;
|
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.pcode.exec.trace.data.DefaultPcodeTracePropertyAccess;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.util.PropertyMap;
|
import ghidra.program.model.util.PropertyMap;
|
||||||
@@ -52,12 +52,11 @@ public class DefaultPcodeDebuggerPropertyAccess<T>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected T whenNull(Address hostAddress) {
|
protected T whenNull(Address hostAddress) {
|
||||||
DebuggerStaticMappingService mappingService =
|
DebuggerAddressTranslator translator = data.getAddressTranslator();
|
||||||
data.getServiceProvider().getService(DebuggerStaticMappingService.class);
|
if (translator == null) {
|
||||||
if (mappingService == null) {
|
|
||||||
return super.whenNull(hostAddress);
|
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));
|
data.getPlatform().getTrace(), null, Lifespan.at(data.getSnap()), hostAddress));
|
||||||
if (progLoc == null) {
|
if (progLoc == null) {
|
||||||
return super.whenNull(hostAddress);
|
return super.whenNull(hostAddress);
|
||||||
|
|||||||
+7
-11
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* 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 java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import ghidra.debug.api.emulation.PcodeDebuggerRegistersAccess;
|
import ghidra.debug.api.emulation.PcodeDebuggerRegistersAccess;
|
||||||
|
import ghidra.debug.api.modules.DebuggerAddressTranslator;
|
||||||
import ghidra.debug.api.target.Target;
|
import ghidra.debug.api.target.Target;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
|
||||||
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceRegistersAccess;
|
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceRegistersAccess;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressSetView;
|
import ghidra.program.model.address.AddressSetView;
|
||||||
@@ -33,13 +33,11 @@ import ghidra.trace.model.thread.TraceThread;
|
|||||||
public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegistersAccess
|
public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegistersAccess
|
||||||
implements PcodeDebuggerRegistersAccess, InternalPcodeDebuggerDataAccess {
|
implements PcodeDebuggerRegistersAccess, InternalPcodeDebuggerDataAccess {
|
||||||
|
|
||||||
protected final ServiceProvider provider;
|
|
||||||
protected final Target target;
|
protected final Target target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a shim
|
* Construct a shim
|
||||||
*
|
*
|
||||||
* @param provider the service provider (usually the tool)
|
|
||||||
* @param target the target
|
* @param target the target
|
||||||
* @param platform the associated platform, having the same trace as the recorder
|
* @param platform the associated platform, having the same trace as the recorder
|
||||||
* @param snap the associated snap
|
* @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 frame the associated frame, or 0 if not applicable
|
||||||
* @param viewport the viewport, set to the same snapshot
|
* @param viewport the viewport, set to the same snapshot
|
||||||
*/
|
*/
|
||||||
protected DefaultPcodeDebuggerRegistersAccess(ServiceProvider provider, Target target,
|
protected DefaultPcodeDebuggerRegistersAccess(Target target, TracePlatform platform, long snap,
|
||||||
TracePlatform platform, long snap, TraceThread thread, int frame,
|
TraceThread thread, int frame, TraceTimeViewport viewport) {
|
||||||
TraceTimeViewport viewport) {
|
|
||||||
super(platform, snap, thread, frame, viewport);
|
super(platform, snap, thread, frame, viewport);
|
||||||
this.provider = provider;
|
|
||||||
this.target = target;
|
this.target = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,8 +57,8 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ServiceProvider getServiceProvider() {
|
public DebuggerAddressTranslator getAddressTranslator() {
|
||||||
return provider;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+20
-4
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -15,18 +15,34 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.service.emulation.data;
|
package ghidra.app.plugin.core.debug.service.emulation.data;
|
||||||
|
|
||||||
|
import ghidra.debug.api.modules.DebuggerAddressTranslator;
|
||||||
import ghidra.debug.api.target.Target;
|
import ghidra.debug.api.target.Target;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
|
||||||
import ghidra.lifecycle.Internal;
|
import ghidra.lifecycle.Internal;
|
||||||
import ghidra.pcode.exec.trace.data.InternalPcodeTraceDataAccess;
|
import ghidra.pcode.exec.trace.data.InternalPcodeTraceDataAccess;
|
||||||
import ghidra.trace.model.TraceTimeViewport;
|
import ghidra.trace.model.TraceTimeViewport;
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
public interface InternalPcodeDebuggerDataAccess extends InternalPcodeTraceDataAccess {
|
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();
|
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() {
|
default boolean isLive() {
|
||||||
Target target = getTarget();
|
Target target = getTarget();
|
||||||
if (target == null || !target.isValid()) {
|
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 {
|
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 {
|
Set<Program> programs) implements AutoCloseable {
|
||||||
|
|
||||||
static <T> Set<T> subtract(Set<T> a, Set<T> b) {
|
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
|
// 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
|
@Override
|
||||||
protected void doExecuteIndirectBranch(PcodeOp op, PcodeFrame frame) {
|
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
|
* @return the address (stack offset or register) of the return address
|
||||||
*/
|
*/
|
||||||
public Address computeAddressOfReturn() {
|
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 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;
|
default -> null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -19,7 +19,7 @@ import java.util.Collection;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import ghidra.app.services.DebuggerStaticMappingService;
|
import ghidra.debug.api.modules.DebuggerAddressTranslator;
|
||||||
import ghidra.debug.api.modules.MappedAddressRange;
|
import ghidra.debug.api.modules.MappedAddressRange;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.listing.Program;
|
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.
|
* bytes from the mapped programs, along with the trace address where they apply.
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractMappedMemoryBytesVisitor {
|
public abstract class AbstractMappedMemoryBytesVisitor {
|
||||||
private final DebuggerStaticMappingService mappingService;
|
private final DebuggerAddressTranslator translator;
|
||||||
private final byte[] buffer;
|
private final byte[] buffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a visitor object
|
* 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
|
* @param buffer a buffer for the data. This is passed directly into
|
||||||
* {@link #visitData(Address, byte[], int)}. If a mapped range exceeds the buffer
|
* {@link #visitData(Address, byte[], int)}. If a mapped range exceeds the buffer
|
||||||
* size, the range is broken down into smaller pieces.
|
* size, the range is broken down into smaller pieces.
|
||||||
*/
|
*/
|
||||||
public AbstractMappedMemoryBytesVisitor(DebuggerStaticMappingService mappingService,
|
public AbstractMappedMemoryBytesVisitor(DebuggerAddressTranslator translator,
|
||||||
byte[] buffer) {
|
byte[] buffer) {
|
||||||
this.mappingService = Objects.requireNonNull(mappingService);
|
this.translator = Objects.requireNonNull(translator);
|
||||||
this.buffer = buffer;
|
this.buffer = buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ public abstract class AbstractMappedMemoryBytesVisitor {
|
|||||||
public boolean visit(Trace trace, long snap, AddressSetView hostView)
|
public boolean visit(Trace trace, long snap, AddressSetView hostView)
|
||||||
throws MemoryAccessException {
|
throws MemoryAccessException {
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
for (Entry<Program, Collection<MappedAddressRange>> ent : mappingService
|
for (Entry<Program, Collection<MappedAddressRange>> ent : translator
|
||||||
.getOpenMappedViews(trace, hostView, snap)
|
.getOpenMappedViews(trace, hostView, snap)
|
||||||
.entrySet()) {
|
.entrySet()) {
|
||||||
result |= visitProgram(ent.getKey(), ent.getValue());
|
result |= visitProgram(ent.getKey(), ent.getValue());
|
||||||
|
|||||||
+19
-12
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* 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.CancelledException;
|
||||||
import ghidra.util.exception.VersionException;
|
import ghidra.util.exception.VersionException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
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();
|
public static final Cleaner CLEANER = Cleaner.create();
|
||||||
|
|
||||||
private static class ObjectState implements Runnable {
|
private static class ObjectState<T extends DomainObject> implements Runnable {
|
||||||
private DomainObject obj;
|
private T obj;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void run() {
|
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)) {
|
if (!obj.getConsumerList().contains(this)) {
|
||||||
throw new IllegalStateException("Domain object is closed");
|
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,
|
public ManagedDomainObject(DomainFile file, Class<T> type, TaskMonitor monitor)
|
||||||
TaskMonitor monitor) throws VersionException, CancelledException, IOException {
|
throws VersionException, CancelledException, IOException {
|
||||||
state.obj = file.getDomainObject(state, okToUpgrade, okToRecover, monitor);
|
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);
|
CLEANER.register(this, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws Exception {
|
public void close() {
|
||||||
state.run();
|
state.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DomainObject get() {
|
public T get() {
|
||||||
return state.get();
|
return state.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import org.antlr.runtime.tree.CommonTreeNodeStream;
|
|||||||
|
|
||||||
import ghidra.app.nav.NavigationUtils;
|
import ghidra.app.nav.NavigationUtils;
|
||||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationIntegration;
|
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.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
|
||||||
import ghidra.app.plugin.processors.sleigh.SleighException;
|
import ghidra.app.plugin.processors.sleigh.SleighException;
|
||||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
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.SleighProgramCompiler.ErrorCollectingPcodeParser;
|
||||||
import ghidra.pcode.exec.SleighUtils.LitIdMode;
|
import ghidra.pcode.exec.SleighUtils.LitIdMode;
|
||||||
import ghidra.pcode.exec.trace.*;
|
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.DefaultPcodeTraceAccess;
|
||||||
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
|
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
|
||||||
import ghidra.pcode.utils.Utils;
|
import ghidra.pcode.utils.Utils;
|
||||||
@@ -320,6 +322,39 @@ public enum DebuggerPcodeUtils {
|
|||||||
return compileExpression(provider, coordinates, current, source, LitIdMode.NORMAL);
|
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
|
* Get a p-code executor state for the given coordinates
|
||||||
*
|
*
|
||||||
@@ -333,20 +368,7 @@ public enum DebuggerPcodeUtils {
|
|||||||
*/
|
*/
|
||||||
public static PcodeExecutorState<byte[]> executorStateForCoordinates(ServiceProvider provider,
|
public static PcodeExecutorState<byte[]> executorStateForCoordinates(ServiceProvider provider,
|
||||||
DebuggerCoordinates coordinates) {
|
DebuggerCoordinates coordinates) {
|
||||||
Trace trace = coordinates.getTrace();
|
return executorStateForCoordinates(provider, coordinates, Mode.RW).state;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+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) {
|
TraceThread thread, int frame) {
|
||||||
Writer writer = new TraceWriter(access) {
|
Writer writer = new TraceWriter(access) {
|
||||||
@Override
|
@Override
|
||||||
protected PcodeTraceRegistersAccess getRegAccess(PcodeThread<?> ignored) {
|
protected PcodeTraceDataAccess getDataAccess(PcodeTraceAccess access,
|
||||||
return access.getDataForLocalState(thread, frame);
|
AddressSpace space, PcodeThread<?> ignored) {
|
||||||
|
return space.isRegisterSpace()
|
||||||
|
? access.getDataForLocalState(thread, frame)
|
||||||
|
: access.getDataForSharedState();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
writer.putHandler(new ImmediateBytesPieceHandler());
|
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
|
* also have state assigned to abstract addresses. In such cases, it is up to the handler to
|
||||||
* track what has been written.
|
* track what has been written.
|
||||||
*
|
*
|
||||||
|
* @param writer the writer
|
||||||
* @param into the destination trace access
|
* @param into the destination trace access
|
||||||
* @param thread the thread associated with the piece's state
|
* @param thread the thread associated with the piece's state
|
||||||
* @param piece the source state piece
|
* @param piece the source state piece
|
||||||
* @param written the portion that is known to have been written
|
* @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);
|
PcodeExecutorStatePiece<A, T> piece, AddressSetView written);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +350,7 @@ public enum TraceEmulationIntegration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeDown(PcodeTraceDataAccess into, PcodeThread<?> thread,
|
public void writeDown(TraceWriter writer, PcodeTraceAccess into, PcodeThread<?> thread,
|
||||||
PcodeExecutorStatePiece<Void, Void> piece, AddressSetView written) {
|
PcodeExecutorStatePiece<Void, Void> piece, AddressSetView written) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -398,20 +402,21 @@ public enum TraceEmulationIntegration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeDown(PcodeTraceDataAccess into, PcodeThread<?> thread,
|
public void writeDown(TraceWriter writer, PcodeTraceAccess into, PcodeThread<?> thread,
|
||||||
PcodeExecutorStatePiece<byte[], byte[]> piece, AddressSetView written) {
|
PcodeExecutorStatePiece<byte[], byte[]> piece, AddressSetView written) {
|
||||||
for (AddressRange range : written) {
|
for (AddressRange range : written) {
|
||||||
AddressSpace space = range.getAddressSpace();
|
AddressSpace space = range.getAddressSpace();
|
||||||
if (space.isUniqueSpace()) {
|
if (space.isUniqueSpace()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
PcodeTraceDataAccess acc = writer.getDataAccess(into, space, thread);
|
||||||
long lower = range.getMinAddress().getOffset();
|
long lower = range.getMinAddress().getOffset();
|
||||||
long fullLen = range.getLength();
|
long fullLen = range.getLength();
|
||||||
while (fullLen > 0) {
|
while (fullLen > 0) {
|
||||||
int len = MathUtilities.unsignedMin(CHUNK_SIZE, fullLen);
|
int len = MathUtilities.unsignedMin(CHUNK_SIZE, fullLen);
|
||||||
// NOTE: Would prefer less copying and less heap garbage....
|
// NOTE: Would prefer less copying and less heap garbage....
|
||||||
byte[] bytes = piece.getVarInternal(space, lower, len, Reason.INSPECT);
|
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;
|
lower += bytes.length;
|
||||||
fullLen -= bytes.length;
|
fullLen -= bytes.length;
|
||||||
@@ -564,14 +569,15 @@ public enum TraceEmulationIntegration {
|
|||||||
* portion.
|
* portion.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void writeDown(PcodeTraceDataAccess into, PcodeThread<?> thread,
|
public void writeDown(TraceWriter writer, PcodeTraceAccess into, PcodeThread<?> thread,
|
||||||
PcodeExecutorStatePiece<A, T> piece, AddressSetView written) {
|
PcodeExecutorStatePiece<A, T> piece, AddressSetView written) {
|
||||||
PcodeTracePropertyAccess<P> property =
|
|
||||||
into.getPropertyAccess(getPropertyName(), getPropertyType());
|
|
||||||
AddressSet remains = new AddressSet(written);
|
AddressSet remains = new AddressSet(written);
|
||||||
while (!remains.isEmpty()) {
|
while (!remains.isEmpty()) {
|
||||||
Address cur = remains.getMinAddress();
|
Address cur = remains.getMinAddress();
|
||||||
AddressSpace space = cur.getAddressSpace();
|
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());
|
Entry<Long, T> entry = piece.getNextEntryInternal(space, cur.getOffset());
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
remains.delete(space.getMinAddress(), space.getMaxAddress());
|
remains.delete(space.getMinAddress(), space.getMaxAddress());
|
||||||
@@ -662,18 +668,19 @@ public enum TraceEmulationIntegration {
|
|||||||
protected final PcodeTraceMemoryAccess memAccess;
|
protected final PcodeTraceMemoryAccess memAccess;
|
||||||
protected final Map<PcodeThread<?>, PcodeTraceRegistersAccess> regAccess = new HashMap<>();
|
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
|
* 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
|
* {@link SemisparseByteArray}'s initialized set, as that may be caching bytes from the
|
||||||
* trace which are still {@link TraceMemoryState#UNKNOWN}.
|
* trace which are still {@link TraceMemoryState#UNKNOWN}.
|
||||||
*/
|
*/
|
||||||
protected final AddressSet memWritten = new AddressSet();
|
//protected final AddressSet memWritten = new AddressSet();
|
||||||
protected final Map<PcodeThread<?>, AddressSet> regsWritten = new HashMap<>();
|
//protected final Map<PcodeThread<?>, AddressSet> regsWritten = new HashMap<>();
|
||||||
|
protected final Map<PcodeExecutorStatePiece<?, ?>, PieceInfo> pieces = new HashMap<>();
|
||||||
|
|
||||||
protected final Map<PieceType<?, ?>, PieceHandler<?, ?>> handlers = new HashMap<>();
|
protected final Map<PieceType<?, ?>, PieceHandler<?, ?>> handlers = new HashMap<>();
|
||||||
|
|
||||||
private PcodeMachine<?> emulator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a writer which sources state from the given access shim
|
* Construct a writer which sources state from the given access shim
|
||||||
*
|
*
|
||||||
@@ -689,11 +696,6 @@ public enum TraceEmulationIntegration {
|
|||||||
handlers.put(PieceType.forHandler(handler), handler);
|
handlers.put(PieceType.forHandler(handler), handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void emulatorCreated(PcodeMachine<Object> emulator) {
|
|
||||||
this.emulator = emulator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void threadCreated(PcodeThread<Object> thread) {
|
public void threadCreated(PcodeThread<Object> thread) {
|
||||||
access.getDataForLocalState(thread, 0).initializeThreadContext(thread);
|
access.getDataForLocalState(thread, 0).initializeThreadContext(thread);
|
||||||
@@ -715,28 +717,17 @@ public enum TraceEmulationIntegration {
|
|||||||
* @param piece the piece
|
* @param piece the piece
|
||||||
* @param written the logged portions written
|
* @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) {
|
PcodeExecutorStatePiece<B, U> piece, AddressSetView written) {
|
||||||
PieceHandler<B, U> handler = handlerFor(piece);
|
PieceHandler<B, U> handler = handlerFor(piece);
|
||||||
handler.writeDown(into, thread, piece, written);
|
handler.writeDown(this, into, thread, piece, written);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeDown(PcodeTraceAccess into) {
|
public void writeDown(PcodeTraceAccess into) {
|
||||||
PcodeTraceMemoryAccess memInto = into.getDataForSharedState();
|
for (Entry<PcodeExecutorStatePiece<?, ?>, PieceInfo> ent : pieces.entrySet()) {
|
||||||
for (PcodeExecutorStatePiece<?, ?> piece : emulator.getSharedState()
|
PieceInfo info = ent.getValue();
|
||||||
.streamPieces()
|
writePieceDown(into, info.thread, ent.getKey(), info.written);
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -745,22 +736,20 @@ public enum TraceEmulationIntegration {
|
|||||||
writeDown(access.deriveForWrite(snap));
|
writeDown(access.deriveForWrite(snap));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PcodeTraceRegistersAccess getRegAccess(PcodeThread<?> thread) {
|
protected PcodeTraceDataAccess getDataAccess(PcodeTraceAccess access, AddressSpace space,
|
||||||
// Always use frame 0
|
PcodeThread<?> thread) {
|
||||||
return regAccess.computeIfAbsent(thread, t -> access.getDataForLocalState(t, 0));
|
return space.isRegisterSpace()
|
||||||
|
? access.getDataForLocalState(thread, 0)
|
||||||
|
: access.getDataForSharedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <B, U> void dataWritten(PcodeThread<Object> thread,
|
public <B, U> void dataWritten(PcodeThread<Object> thread,
|
||||||
PcodeExecutorStatePiece<B, U> piece,
|
PcodeExecutorStatePiece<B, U> piece, Address address, int length, U value) {
|
||||||
Address address, int length, U value) {
|
PcodeTraceDataAccess acc = getDataAccess(access, address.getAddressSpace(), thread);
|
||||||
PcodeTraceDataAccess acc = address.isRegisterAddress()
|
PieceInfo info =
|
||||||
? getRegAccess(thread)
|
pieces.computeIfAbsent(piece, p -> new PieceInfo(thread, new AddressSet()));
|
||||||
: memAccess;
|
if (handlerFor(piece).dataWritten(acc, info.written, thread, piece, address, length,
|
||||||
AddressSet written = address.isRegisterAddress()
|
|
||||||
? regsWritten.computeIfAbsent(thread, t -> new AddressSet())
|
|
||||||
: memWritten;
|
|
||||||
if (handlerFor(piece).dataWritten(acc, written, thread, piece, address, length,
|
|
||||||
value)) {
|
value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -769,12 +758,12 @@ public enum TraceEmulationIntegration {
|
|||||||
}
|
}
|
||||||
Address end = address.addWrap(length - 1);
|
Address end = address.addWrap(length - 1);
|
||||||
if (address.compareTo(end) <= 0) {
|
if (address.compareTo(end) <= 0) {
|
||||||
written.add(address, end);
|
info.written.add(address, end);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
AddressSpace space = address.getAddressSpace();
|
AddressSpace space = address.getAddressSpace();
|
||||||
written.add(address, space.getMaxAddress());
|
info.written.add(address, space.getMaxAddress());
|
||||||
written.add(space.getMinAddress(), end);
|
info.written.add(space.getMinAddress(), end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,19 +771,18 @@ public enum TraceEmulationIntegration {
|
|||||||
public <B, U> void dataWritten(PcodeThread<Object> thread,
|
public <B, U> void dataWritten(PcodeThread<Object> thread,
|
||||||
PcodeExecutorStatePiece<B, U> piece, AddressSpace space, B offset, int length,
|
PcodeExecutorStatePiece<B, U> piece, AddressSpace space, B offset, int length,
|
||||||
U value) {
|
U value) {
|
||||||
PcodeTraceDataAccess acc = space.isRegisterSpace() ? getRegAccess(thread) : memAccess;
|
PcodeTraceDataAccess acc = getDataAccess(access, space, thread);
|
||||||
AddressSet written = space.isRegisterSpace()
|
PieceInfo info =
|
||||||
? regsWritten.computeIfAbsent(thread, t -> new AddressSet())
|
pieces.computeIfAbsent(piece, p -> new PieceInfo(thread, new AddressSet()));
|
||||||
: memWritten;
|
handlerFor(piece).abstractWritten(acc, info.written, thread, piece, space, offset,
|
||||||
handlerFor(piece).abstractWritten(acc, written, thread, piece, space, offset, length,
|
length, value);
|
||||||
value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <B, U> int readUninitialized(PcodeThread<Object> thread,
|
public <B, U> int readUninitialized(PcodeThread<Object> thread,
|
||||||
PcodeExecutorStatePiece<B, U> piece, AddressSpace space, B offset, int length,
|
PcodeExecutorStatePiece<B, U> piece, AddressSpace space, B offset, int length,
|
||||||
Reason reason) {
|
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,
|
return handlerFor(piece).abstractReadUninit(acc, thread, piece, space, offset, length,
|
||||||
reason);
|
reason);
|
||||||
}
|
}
|
||||||
@@ -809,7 +797,7 @@ public enum TraceEmulationIntegration {
|
|||||||
if (space.isUniqueSpace()) {
|
if (space.isUniqueSpace()) {
|
||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
PcodeTraceDataAccess acc = space.isRegisterSpace() ? getRegAccess(thread) : memAccess;
|
PcodeTraceDataAccess acc = getDataAccess(access, space, thread);
|
||||||
return handlerFor(piece).readUninitialized(acc, thread, piece, set);
|
return handlerFor(piece).readUninitialized(acc, thread, piece, set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -16,6 +16,8 @@
|
|||||||
package ghidra.pcode.exec.trace.data;
|
package ghidra.pcode.exec.trace.data;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.lang.Language;
|
import ghidra.program.model.lang.Language;
|
||||||
@@ -36,6 +38,8 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
|
|||||||
|
|
||||||
protected final TraceMemoryManager mm;
|
protected final TraceMemoryManager mm;
|
||||||
|
|
||||||
|
protected final Map<String, PcodeTracePropertyAccess<?>> properties = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a shim
|
* Construct a shim
|
||||||
*
|
*
|
||||||
@@ -204,7 +208,11 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
|
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
|
||||||
return new DefaultPcodeTracePropertyAccess<>(this, name, type);
|
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 org.junit.Test;
|
||||||
|
|
||||||
import db.Transaction;
|
import db.Transaction;
|
||||||
|
import generic.Unique;
|
||||||
import ghidra.app.plugin.assembler.*;
|
import ghidra.app.plugin.assembler.*;
|
||||||
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock;
|
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock;
|
||||||
import ghidra.pcode.emu.PcodeEmulator;
|
import ghidra.pcode.emu.PcodeEmulator;
|
||||||
@@ -68,7 +69,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest
|
|||||||
|
|
||||||
emu.getSharedState().setVar(tb.addr(0x00400000), 0, false, tb.arr());
|
emu.getSharedState().setVar(tb.addr(0x00400000), 0, false, tb.arr());
|
||||||
TraceWriter tw = (TraceWriter) writer;
|
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