GP-6787: Add 'Emulate Function' action and dialog.

This commit is contained in:
Dan
2026-07-01 18:28:31 +00:00
parent 9ea285b77a
commit 427d857d51
98 changed files with 5384 additions and 1159 deletions
@@ -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();
}
}
@@ -239,6 +239,10 @@ public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter
createActions();
}
public void cleanConnectionNode(TraceRmiConnectionNode node) {
rootNode.cleanConnectionNode(node);
}
private void buildMainPanel() {
mainPanel = new JPanel(new BorderLayout());
@@ -42,7 +42,13 @@ public class TraceRmiConnectionNode extends AbstractTraceRmiManagerNode {
@Override
public String getDisplayText() {
return connection.getDescription() + " at " + connection.getRemoteAddress();
try {
return connection.getDescription() + " at " + connection.getRemoteAddress();
}
catch (Exception e) {
provider.cleanConnectionNode(this);
return "Error: " + e;
}
}
@Override
@@ -52,8 +58,13 @@ public class TraceRmiConnectionNode extends AbstractTraceRmiManagerNode {
@Override
public String getToolTip() {
return "Trace RMI Connection to " + connection.getDescription() + " at " +
connection.getRemoteAddress();
try {
return "Trace RMI Connection to " + connection.getDescription() + " at " +
connection.getRemoteAddress();
}
catch (Exception e) {
return "Error: " + e;
}
}
@Override
@@ -25,6 +25,7 @@ import ghidra.debug.api.target.Target;
import ghidra.debug.api.target.TargetPublicationListener;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.debug.api.tracermi.*;
import ghidra.lifecycle.Internal;
import ghidra.util.Msg;
import ghidra.util.Swing;
@@ -83,6 +84,15 @@ public class TraceRmiServiceNode extends AbstractTraceRmiManagerNode
removeNode(node);
}
@Internal
public void cleanConnectionNode(TraceRmiConnectionNode node) {
TraceRmiConnection connection = node.getConnection();
synchronized (connectionNodes) {
connectionNodes.remove(connection);
}
removeNode(node);
}
private TraceRmiAcceptorNode newAcceptorNode(TraceRmiAcceptor acceptor) {
return new TraceRmiAcceptorNode(provider, acceptor);
}
@@ -26,6 +26,7 @@ src/main/help/help/topics/DebuggerCopyActionsPlugin/DebuggerCopyActionsPlugin.ht
src/main/help/help/topics/DebuggerCopyActionsPlugin/images/DebuggerCopyIntoProgramDialog.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerDisassemblerPlugin/DebuggerDisassemblerPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerEmulationServicePlugin/DebuggerEmulationServicePlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerEmulationServicePlugin/images/DebuggerEmulateFunctionDialog.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerListingPlugin.png||GHIDRA||||END|
@@ -0,0 +1,38 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import ghidra.app.script.GhidraScript;
import ghidra.debug.flatapi.FlatDebuggerAPI;
import ghidra.program.model.address.AddressSetView;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
//List all address ranges that are "known" at the current snapshot
//This script must be run from the Debugger tool, or another tool with the required plugins.
//@category Debugger
//@keybinding
//@menupath
//@toolbar
public class ListAllKnownMemoryScript extends GhidraScript implements FlatDebuggerAPI {
@Override
protected void run() throws Exception {
Trace trace = requireCurrentTrace();
long snap = getCurrentSnap();
AddressSetView addresses =
trace.getMemoryManager().getAddressesWithState(snap, StatePredicate.IS_KNOWN);
show("Known Memory at %d".formatted(snap), addresses);
}
}
@@ -48,6 +48,12 @@
Optionally, other registers can be initialized via the UI or a script. The new thread is
activated so that control actions will affect it by default.</P>
<H3><A name="emulate_function"></A> Emulate Function</H3>
<P>This action is available whenever the cursor is within a function in the Static Listing. It
displays a <A href="#emulate_function_dialog">dialog</A> for harnessing and emulating the
current function.</P>
<H3><A name="configure_emulator"></A> Configure Emulator</H3>
<P>This action is always available. It lists emulators available for configuration. Selecting
@@ -62,5 +68,188 @@
emulated breakpoint, or when you patch the trace database. If you do not invalidate the cache,
the effects of your change may not appear, since the trace manager may recall a cached snapshot
instead of actually emulating.</P>
<H2><A name="emulate_function_dialog"></A> Emulate Function Dialog</H2>
<P>This dialog provides a means of harnessing and emulating a target function. Depending on the
size and scope of the function and the configuration of the emulator, the emulation may or may
not complete. See further below for options and actions in the dialog.</P>
<DIV class="image">
<IMG alt="" src="images/DebuggerEmulateFunctionDialog.png">
</DIV>
<P>The dialog supports the addition of vararg arguments, custom initializations, and heap
initializations. All of these inputs are handled in the top table. The "Run" button will
perform the emulation and capture the outputs into the bottom table. Analogous to the inputs,
the Outputs table can be configured with custom variables and pointer dereferences. The
emulation session can optionally be captured into a trace, which is opened automatically in
your tool, usually the Debugger or Emulator. Multiple sessions can be run from the same dialog,
allowing trial-and-error runs while also iterating on the target function's markup.</P>
<H3>Option: Ending Return Address</H3>
<P>In order to detect the proper completion of the target function, the harness places a
breakpoint at a "sentinel" address. That sentinel address must be placed where the function
expects the return address. This option allows specification of the sentinel address.
Generally, anything easily recognizable that does not conflict with a real address should work.
<B>NOTE:</B> You cannot specify the location of the return address. This is determined
automatically through static analysis, but you can place the sentinel address through a custom
input, if the analysis fails or is incorrect.</P>
<H3>Option: Snapshot Period</H3>
<P>By default, the harness captures a snapshot of the emulator's state into a trace after every
instruction step. This feature can be disabled entirely by setting this option to 0 (zero), in
which case no trace is captured or opened in your tool. Only the outputs are captured.
Otherwise, the period indicates how many instructions are executed between consecutive
snapshots. If only a starting and ending snapshot are desired, set this to a large number. The
initial and final snapshots are always captured, no matter the (non-zero) period.</P>
<H3><A name="next_alloc"></A> Option: Next Allocation</H3>
<P>Set this to the location of the heap, if applicable. Any address not already used by the
program with plenty of space above should suffice. It may help if the upper digits of the
address are easily recognizable. Several of the below actions on the Inputs table will
automatically initialize pointers to the start of an "allocated block". This field is then
automatically incremented by the size of that allocation. You may adjust this field at any time
to "undo" allocations, establish a second heap, etc.</P>
<H3>Inputs table</H3>
<P>The Inputs table lists of all the configured inputs. The types and values of the inputs are
specified in this table. <B>NOTE:</B> No actual initializations are performed until right
before emulation. If two inputs happen to be at the same address, that conflict will not be
discovered until clicking "Run." No two inputs can have the same name. Adding an input with a
duplicate name replaces the existing one.</P>
<P>The default inputs are derived from the target function's parameters. Adjusting the type of
an input <EM>does not</EM> edit or update the target function in the program database. The
columns are:</P>
<UL>
<LI>Name &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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>&lt;Shift&gt;</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>&lt;Ctrl&gt;</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 &mdash; this will automatically adjust the storage location of the pointed-to variable
&mdash; or delete and re-allocate the string input.</P>
<H3>Action: Refresh Inputs</H3>
<P>This re-adds (likely replacing) all the inputs derived from the function's parameters. This
is especially useful when trying to derive a function's signature by trial and error. Clicking
this button after editing a function's signature will update the Parameter inputs accordingly.
<B>NOTE:</B> This will not <EM>remove</EM> any Parameter inputs.</P>
<H3>Action: Type Settings</H3>
<P>This action is available in the right-click context menu on rows with an assigned type. It
controls the settings on the chosen datatype, e.g., radix for integer types, or encodings for
string types.</P>
<H3>Outputs table</H3>
<P>The Outputs table is analogous to the Inputs table, except that it displays values captured
from the last successful emulation. The default output is derived from the target function's
return type. It has the same columns and similar actions with the following exceptions:</P>
<UL>
<LI>There is no <B>Add Vararg Output</B> action.</LI>
<LI>Only the Type column can be edited.</LI>
<LI>Adding Pointed-to outputs does not "allocate," since the pointer is expected to be set by
the target function.</LI>
<LI>When adding pointed-to strings, the prompt is for the number of bytes to capture. The
string decoder should only use what it needs, so just pick a reasonable maximum expected
length. Adjust the type and settings using other actions afterward.</LI>
</UL>
<H3>Probe Outputs</H3>
<P>The Outputs table also supports "Probe" outputs. These are outputs automatically generated
during the emulation of the target function via the <CODE>emu_probe</CODE> userop. These are
configured using the <A href=
"help/topics/DebuggerBreakpointsPlugin/DebuggerBreakpointsPlugin.html#set_injection">Set
Injection</A> action on a breakpoint. The breakpoint need only be enabled. Even if
"ineffective,"" it will be installed by the harness. The <CODE>emu_probe</CODE> userop accepts
exactly one argument. All Probe outputs are cleared at the start of emulation. Each time the
userop is executed, its argument is captured with its location and value <EM>at the time of
invocation</EM>. These are then added as Probe outputs, counting 1-up by invocation. If the
userop is invoked within a loop of the target code, each run-time invocation gets a distinct
row. Types can be applied as usual. <B>NOTE:</B> If the userop argument is anything other than
a simple varnode, the location will be "$Unique."</P>
</BODY>
</HTML>
@@ -191,7 +191,7 @@
debugger. This failure can be overcome by enabling <A href=
"help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html">Force Full View</A>.</P>
<P>Some examples (in <b>Hex</b> mode):</P>
<P>Some examples (in <B>Hex</B> mode):</P>
<UL>
<LI><CODE>00401234</CODE> &mdash; A constant address</LI>
@@ -172,7 +172,7 @@ public enum BasicAutoReadMemorySpec implements AutoReadMemorySpec {
AddressSet toRead = new AddressSet(quantize(12, visible));
for (Lifespan span : coordinates.getView().getViewport().getOrderedSpans()) {
AddressSetView alreadyKnown =
mm.getAddressesWithState(span.lmin(), visible, StatePredicate.IS_KNOWN);
mm.getAddressesWithState(span, toRead, StatePredicate.IS_KNOWN);
toRead.delete(alreadyKnown);
if (span.lmax() != span.lmin() || toRead.isEmpty()) {
break;
@@ -1339,38 +1339,16 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
return null;
}
private boolean isAllInvolvedTracesUsingEmulatedBreakpoints(ActionContext ctx) {
if (controlService == null) {
return false;
}
Set<Trace> traces = new HashSet<>();
private boolean isAtLeastOneBreakpoint(ActionContext ctx) {
Collection<LogicalBreakpoint> breakpoints = getLogicalBreakpoints(ctx);
if (breakpoints != null) {
if (breakpoints.isEmpty()) {
return false;
}
for (LogicalBreakpoint lb : breakpoints) {
traces.addAll(lb.getParticipatingTraces());
}
if (breakpoints != null && !breakpoints.isEmpty()) {
return true;
}
else if (ctx instanceof DebuggerBreakpointLocationsActionContext locCtx) {
Collection<TraceBreakpointLocation> locations = locCtx.getLocations();
if (locations.isEmpty()) {
return false;
}
for (TraceBreakpointLocation loc : locations) {
traces.add(loc.getTrace());
}
if (ctx instanceof DebuggerBreakpointLocationsActionContext locCtx &&
!locCtx.getLocations().isEmpty()) {
return true;
}
else {
return false;
}
for (Trace trace : traces) {
if (!controlService.getCurrentMode(trace).useEmulatedBreakpoints()) {
return false;
}
}
return true;
return false;
}
private static final Set<TraceBreakpointKind> EXECUTE_KINDS =
@@ -1400,11 +1378,11 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
}
private boolean isPopupSetCondition(ActionContext ctx) {
return isAllInvolvedTracesUsingEmulatedBreakpoints(ctx) && isAllBreakpointsExecution(ctx);
return isAtLeastOneBreakpoint(ctx) && isAllBreakpointsExecution(ctx);
}
private boolean isPopupSetInjection(ActionContext ctx) {
return isAllInvolvedTracesUsingEmulatedBreakpoints(ctx) && isAllBreakpointsExecution(ctx);
return isAtLeastOneBreakpoint(ctx) && isAllBreakpointsExecution(ctx);
}
private String deriveCurrentSleigh(ActionContext ctx) {
@@ -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);
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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));
}
}
@@ -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);
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
@@ -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);
}
}
}
@@ -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, "");
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -61,7 +61,7 @@ public class DefaultWatchRow implements WatchRow {
private String expression;
private String typePath;
private DataType dataType;
private SettingsImpl settings = new SettingsImpl();
private final SettingsImpl settings = new SettingsImpl();
private SavedSettings savedSettings = new SavedSettings(settings);
private String comment;
@@ -48,7 +48,7 @@ public class ProgramBreakpoint {
private static final Gson GSON = new GsonBuilder().create();
/**
* A class for (de)serializing breakoint properties in the bookmark's comments
* A class for (de)serializing breakpoint properties in the bookmark's comments
*/
static class BreakpointProperties {
public String name;
@@ -106,6 +106,25 @@ public class ProgramBreakpoint {
}
}
/**
* Produce a breakpoint from its bookmark (utility).
* <p>
* The breakpoint manager does not ordinarily use this, as the path to total construction spans
* discovering and indexing of logical breakpoints, and then adding into them information
* available from program bookmarks and trace breakpoints. This utility eases the processing of
* program-specified bookmarks without depending on the breakpoint service.
*
* @param program the program containing the bookmark
* @param bm the bookmark describing the breakpoint
* @return the breakpoint
*/
public static ProgramBreakpoint fromBookmark(Program program, Bookmark bm) {
ProgramBreakpoint brk = new ProgramBreakpoint(program,
bm.getAddress(), lengthFromBookmark(bm), kindsFromBookmark(bm));
brk.add(bm);
return brk;
}
private final Program program;
private final Address address;
private final ProgramLocation location;
@@ -399,10 +418,14 @@ public class ProgramBreakpoint {
*/
public Bookmark getBookmark() {
Bookmark eBookmark = this.eBookmark;
if (eBookmark != null) {
if (eBookmark != null && !eBookmark.isDeleted()) {
return eBookmark;
}
return dBookmark;
Bookmark dBookmark = this.dBookmark;
if (dBookmark != null && !dBookmark.isDeleted()) {
return dBookmark;
}
return null;
}
protected String getComment() {
@@ -23,7 +23,8 @@ import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.trace.TraceEmulationIntegration;
import ghidra.pcode.exec.trace.TraceEmulationIntegration.*;
import ghidra.pcode.exec.trace.data.*;
import ghidra.pcode.exec.trace.data.PcodeTraceAccess;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*;
import ghidra.trace.model.thread.TraceThread;
@@ -73,13 +74,41 @@ public enum DebuggerEmulationIntegration {
}
/**
* Create state callbacks that lazily load data and immediately write changes to the given
* access shim.
* Create state callbacks that lazily load data and writes changes to the given access shim.
* <p>
* Reads may be redirected to the target.
* <p>
* Use this instead of {@link #bytesImmediateWriteTarget(PcodeDebuggerAccess)} when interfacing
* directly with a {@link PcodeExecutorState} vice a {@link PcodeEmulator}.
*
* @see TraceEmulationIntegration#bytesImmediateWrite(PcodeTraceAccess, TraceThread, int)
* @param access the access shim for loads and stores
* @param thread the trace thread for register accesses
* @param frame the frame for register accesses, usually 0
* @param mode determines whether or not writes affect the target
* @return the callbacks
*/
public static Writer bytesWriteMode(PcodeDebuggerAccess access,
TraceThread thread, int frame, Mode mode) {
Writer writer = new TraceWriter(access) {
@Override
protected PcodeTraceDataAccess getDataAccess(PcodeTraceAccess access,
AddressSpace space, PcodeThread<?> ignored) {
return space.isRegisterSpace()
? access.getDataForLocalState(thread, frame)
: access.getDataForSharedState();
}
};
writer.putHandler(new TargetBytesPieceHandler(mode));
return writer;
}
/**
* Create state callbacks that lazily load data and immediately writes changes to the given
* access shim.
* <p>
* Reads may be redirected to the target. If redirected, writes are immediately sent to the
* target and presumably stored into the trace at the same snapshot as state is sourced.
*
* <p>
* Use this instead of {@link #bytesImmediateWriteTarget(PcodeDebuggerAccess)} when interfacing
* directly with a {@link PcodeExecutorState} vice a {@link PcodeEmulator}.
@@ -92,15 +121,7 @@ public enum DebuggerEmulationIntegration {
*/
public static PcodeStateCallbacks bytesImmediateWriteTarget(PcodeDebuggerAccess access,
TraceThread thread, int frame) {
PcodeDebuggerRegistersAccess regAcc = access.getDataForLocalState(thread, frame);
Writer writer = new TraceWriter(access) {
@Override
protected PcodeTraceRegistersAccess getRegAccess(PcodeThread<?> ignored) {
return regAcc;
}
};
writer.putHandler(new TargetBytesPieceHandler(Mode.RW));
return writer.wrapFor(null);
return bytesWriteMode(access, thread, frame, Mode.RW).wrapFor(null);
}
protected static <T> T waitTimeout(CompletableFuture<T> future) {
@@ -41,6 +41,7 @@ import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.emulation.DebuggerEmulateFunctionDialog;
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
import ghidra.app.services.*;
import ghidra.async.AsyncLazyMap;
@@ -62,6 +63,7 @@ import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer;
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess;
import ghidra.pcode.exec.trace.data.PcodeTraceAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
@@ -130,6 +132,27 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
}
}
interface EmulateFunctionAction {
String NAME = "Emulate Function";
String DESCRIPTION = "Emulate the current function in a new trace";
Icon ICON = DebuggerResources.ICON_EMULATE;
String GROUP = DebuggerResources.GROUP_GENERAL;
String HELP_ANCHOR = "emulate_function";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.menuPath(DebuggerPluginPackage.NAME, NAME)
.menuIcon(ICON)
.menuGroup(GROUP)
.popupMenuPath(NAME)
.popupMenuIcon(ICON)
.popupMenuGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface EmulateAddThreadAction {
String NAME = "Add Emulated Thread to Trace";
String DESCRIPTION = "Add an emulated thread to the current trace starting here";
@@ -374,6 +397,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
private AutoService.Wiring autoServiceWiring;
DockingAction actionEmulateProgram;
DockingAction actionEmulateFunction;
DockingAction actionEmulateAddThread;
DockingAction actionInvalidateCache;
Map<Class<? extends EmulatorFactory>, ToggleDockingAction> actionsChooseEmulatorFactory =
@@ -399,6 +423,12 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
.popupWhen(this::emulateProgramEnabled)
.onAction(this::emulateProgramActivated)
.buildAndInstall(tool);
actionEmulateFunction = EmulateFunctionAction.builder(this)
.withContext(ProgramLocationActionContext.class)
.enabledWhen(this::emulateFunctionEnabled)
.popupWhen(this::emulateFunctionEnabled)
.onAction(this::emulateFunctionActivated)
.buildAndInstall(tool);
actionEmulateAddThread = EmulateAddThreadAction.builder(this)
.withContext(ProgramLocationActionContext.class)
.enabledWhen(this::emulateAddThreadEnabled)
@@ -487,6 +517,30 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
}
}
private boolean emulateFunctionEnabled(ProgramLocationActionContext ctx) {
Program program = ctx.getProgram();
if (program == null || program instanceof TraceProgramView) {
return false;
}
Function function = program.getFunctionManager().getFunctionContaining(ctx.getAddress());
if (function == null) {
return false;
}
return true;
}
private void emulateFunctionActivated(ProgramLocationActionContext ctx) {
Program program = ctx.getProgram();
if (program == null) {
return;
}
Function function = program.getFunctionManager().getFunctionContaining(ctx.getAddress());
if (function == null) {
return;
}
tool.showDialog(new DebuggerEmulateFunctionDialog(tool, function));
}
private boolean emulateAddThreadEnabled(ProgramLocationActionContext ctx) {
Program programOrView = ctx.getProgram();
if (programOrView instanceof TraceProgramView view) {
@@ -17,7 +17,6 @@ package ghidra.app.plugin.core.debug.service.emulation.data;
import ghidra.debug.api.emulation.*;
import ghidra.debug.api.target.Target;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.pcode.exec.trace.data.AbstractPcodeTraceAccess;
import ghidra.trace.model.guest.TracePlatform;
@@ -27,41 +26,35 @@ import ghidra.trace.model.guest.TracePlatform;
* @param <S> the type of shared data-access shims provided
* @param <L> the type of thread-local data-access shims provided
*/
public abstract class AbstractPcodeDebuggerAccess<S extends PcodeDebuggerMemoryAccess, L extends PcodeDebuggerRegistersAccess>
extends AbstractPcodeTraceAccess<S, L>
public abstract class AbstractPcodeDebuggerAccess<S extends PcodeDebuggerMemoryAccess,
L extends PcodeDebuggerRegistersAccess> extends AbstractPcodeTraceAccess<S, L>
implements PcodeDebuggerAccess {
protected final ServiceProvider provider;
protected final Target target;
/**
* Construct a shim
*
* @param provider the service provider (usually the tool)
* @param target the target
* @param platform the associated platform, having the same trace as the recorder
* @param snap the associated snap
*/
public AbstractPcodeDebuggerAccess(ServiceProvider provider, Target target,
TracePlatform platform, long snap) {
public AbstractPcodeDebuggerAccess(Target target, TracePlatform platform, long snap) {
super(platform, snap);
this.provider = provider;
this.target = target;
}
/**
* Construct a shim
*
* @param provider the service provider (usually the tool)
* @param target the target
* @param platform the associated platform, having the same trace as the recorder
* @param snap the associated snap
* @param threadsSnap the snap to use when finding associated threads between trace and emulator
*/
public AbstractPcodeDebuggerAccess(ServiceProvider provider, Target target,
TracePlatform platform, long snap, long threadsSnap) {
public AbstractPcodeDebuggerAccess(Target target, TracePlatform platform, long snap,
long threadsSnap) {
super(platform, snap, threadsSnap);
this.provider = provider;
this.target = target;
}
}
@@ -25,9 +25,10 @@ import ghidra.trace.model.thread.TraceThread;
/**
* The default target-and-trace access shim for a session
*/
public class DefaultPcodeDebuggerAccess extends
AbstractPcodeDebuggerAccess //
<DefaultPcodeDebuggerMemoryAccess, DefaultPcodeDebuggerRegistersAccess> {
public class DefaultPcodeDebuggerAccess extends AbstractPcodeDebuggerAccess<
DefaultPcodeDebuggerMemoryAccess, DefaultPcodeDebuggerRegistersAccess> {
protected final ServiceProvider provider;
/**
* Construct a shim
@@ -39,7 +40,8 @@ public class DefaultPcodeDebuggerAccess extends
*/
public DefaultPcodeDebuggerAccess(ServiceProvider provider, Target target,
TracePlatform platform, long snap) {
super(provider, target, platform, snap);
super(target, platform, snap);
this.provider = provider;
}
/**
@@ -53,7 +55,8 @@ public class DefaultPcodeDebuggerAccess extends
*/
public DefaultPcodeDebuggerAccess(ServiceProvider provider, Target target,
TracePlatform platform, long snap, long threadsSnap) {
super(provider, target, platform, snap, threadsSnap);
super(target, platform, snap, threadsSnap);
this.provider = provider;
}
/**
@@ -75,7 +78,7 @@ public class DefaultPcodeDebuggerAccess extends
@Override
protected DefaultPcodeDebuggerRegistersAccess newDataForLocalState(TraceThread thread,
int frame) {
return new DefaultPcodeDebuggerRegistersAccess(provider, target, platform, snap, thread,
frame, viewport);
return new DefaultPcodeDebuggerRegistersAccess(target, platform, snap, thread, frame,
viewport);
}
}
@@ -16,39 +16,25 @@
package ghidra.app.plugin.core.debug.service.emulation.data;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.utils.AbstractMappedMemoryBytesVisitor;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.debug.api.emulation.PcodeDebuggerMemoryAccess;
import ghidra.debug.api.modules.MappedAddressRange;
import ghidra.debug.api.modules.DebuggerAddressTranslator;
import ghidra.debug.api.target.Target;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.pcode.exec.PcodeExecutorStatePiece;
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceMemoryAccess;
import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
/**
* The default data-and-debugger-access shim for session memory
*/
public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAccess
implements PcodeDebuggerMemoryAccess, InternalPcodeDebuggerDataAccess {
public class DefaultPcodeDebuggerMemoryAccess extends TranslatedPcodeDebuggerMemoryAccess {
protected final ServiceProvider provider;
protected final Target target;
/**
* Construct a shim
*
* @param provider the service provider (usually the tool)
* @param provider the service provider (usually the tool) to get the static mapping service
* @param target the target
* @param platform the associated platform, having the same trace as the recorder
* @param snap the associated snap
@@ -56,94 +42,12 @@ public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAcc
*/
protected DefaultPcodeDebuggerMemoryAccess(ServiceProvider provider, Target target,
TracePlatform platform, long snap, TraceTimeViewport viewport) {
super(platform, snap, viewport);
super(target, platform, snap, viewport);
this.provider = Objects.requireNonNull(provider);
this.target = target;
}
@Override
public boolean isLive() {
return InternalPcodeDebuggerDataAccess.super.isLive();
}
@Override
public ServiceProvider getServiceProvider() {
return provider;
}
@Override
public Target getTarget() {
return target;
}
@Override
public CompletableFuture<Boolean> readFromTargetMemory(AddressSetView guestView) {
if (!isLive()) {
return CompletableFuture.completedFuture(false);
}
AddressSetView hostView = platform.mapGuestToHost(guestView);
return target.readMemoryAsync(hostView, TaskMonitor.DUMMY).thenApply(__ -> true);
}
@Override
public CompletableFuture<Boolean> writeTargetMemory(Address address, byte[] data) {
if (!isLive()) {
return CompletableFuture.completedFuture(false);
}
return target.writeMemoryAsync(address, data).thenApply(__ -> true);
}
@Override
public AddressSetView readFromStaticImages(PcodeExecutorStatePiece<byte[], byte[]> piece,
AddressSetView guestView) {
// NOTE: If we expand to block, DON'T OVERWRITE KNOWN!
DebuggerStaticMappingService mappingService =
provider.getService(DebuggerStaticMappingService.class);
if (mappingService == null) {
return guestView;
}
AddressSet remains = new AddressSet(guestView);
try {
boolean result = new AbstractMappedMemoryBytesVisitor(mappingService, new byte[4096]) {
@Override
protected int read(Memory memory, Address addr, byte[] dest, int size)
throws MemoryAccessException {
int read = super.read(memory, addr, dest, size);
if (read < size) {
Msg.warn(this,
String.format(" Partial read of %s. Wanted %d bytes. Got %d.",
addr, size, read));
}
return read;
}
@Override
protected boolean visitRange(Program program, AddressRange progRng,
MappedAddressRange mappedRng) throws MemoryAccessException {
Msg.debug(this,
"Filling in unknown trace memory in emulator using mapped image: " +
program + ": " + progRng);
return super.visitRange(program, progRng, mappedRng);
}
@Override
protected void visitData(Address hostAddr, byte[] data, int size) {
Address guestAddr = platform.mapHostToGuest(hostAddr);
piece.setVarInternal(guestAddr.getAddressSpace(), guestAddr.getOffset(), size,
data);
remains.delete(guestAddr, guestAddr.add(size));
}
}.visit(platform.getTrace(), snap, platform.mapGuestToHost(guestView));
return result ? remains : guestView;
}
catch (MemoryAccessException e) {
throw new AssertionError(e);
}
}
@Override
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
return new DefaultPcodeDebuggerPropertyAccess<>(this, name, type);
public DebuggerAddressTranslator getAddressTranslator() {
return provider.getService(DebuggerStaticMappingService.class);
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.debug.service.emulation.data;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.debug.api.modules.DebuggerAddressTranslator;
import ghidra.pcode.exec.trace.data.DefaultPcodeTracePropertyAccess;
import ghidra.program.model.address.Address;
import ghidra.program.model.util.PropertyMap;
@@ -52,12 +52,11 @@ public class DefaultPcodeDebuggerPropertyAccess<T>
@Override
protected T whenNull(Address hostAddress) {
DebuggerStaticMappingService mappingService =
data.getServiceProvider().getService(DebuggerStaticMappingService.class);
if (mappingService == null) {
DebuggerAddressTranslator translator = data.getAddressTranslator();
if (translator == null) {
return super.whenNull(hostAddress);
}
ProgramLocation progLoc = mappingService.getOpenMappedLocation(new DefaultTraceLocation(
ProgramLocation progLoc = translator.getOpenMappedLocation(new DefaultTraceLocation(
data.getPlatform().getTrace(), null, Lifespan.at(data.getSnap()), hostAddress));
if (progLoc == null) {
return super.whenNull(hostAddress);
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -18,8 +18,8 @@ package ghidra.app.plugin.core.debug.service.emulation.data;
import java.util.concurrent.CompletableFuture;
import ghidra.debug.api.emulation.PcodeDebuggerRegistersAccess;
import ghidra.debug.api.modules.DebuggerAddressTranslator;
import ghidra.debug.api.target.Target;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceRegistersAccess;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
@@ -33,13 +33,11 @@ import ghidra.trace.model.thread.TraceThread;
public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegistersAccess
implements PcodeDebuggerRegistersAccess, InternalPcodeDebuggerDataAccess {
protected final ServiceProvider provider;
protected final Target target;
/**
* Construct a shim
*
* @param provider the service provider (usually the tool)
* @param target the target
* @param platform the associated platform, having the same trace as the recorder
* @param snap the associated snap
@@ -47,11 +45,9 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist
* @param frame the associated frame, or 0 if not applicable
* @param viewport the viewport, set to the same snapshot
*/
protected DefaultPcodeDebuggerRegistersAccess(ServiceProvider provider, Target target,
TracePlatform platform, long snap, TraceThread thread, int frame,
TraceTimeViewport viewport) {
protected DefaultPcodeDebuggerRegistersAccess(Target target, TracePlatform platform, long snap,
TraceThread thread, int frame, TraceTimeViewport viewport) {
super(platform, snap, thread, frame, viewport);
this.provider = provider;
this.target = target;
}
@@ -61,8 +57,8 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist
}
@Override
public ServiceProvider getServiceProvider() {
return provider;
public DebuggerAddressTranslator getAddressTranslator() {
return null;
}
@Override
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,18 +15,34 @@
*/
package ghidra.app.plugin.core.debug.service.emulation.data;
import ghidra.debug.api.modules.DebuggerAddressTranslator;
import ghidra.debug.api.target.Target;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.lifecycle.Internal;
import ghidra.pcode.exec.trace.data.InternalPcodeTraceDataAccess;
import ghidra.trace.model.TraceTimeViewport;
@Internal
public interface InternalPcodeDebuggerDataAccess extends InternalPcodeTraceDataAccess {
ServiceProvider getServiceProvider();
/**
* {@return the address translator or null. If null is returned, then bytes cannot be loaded
* from mapped images.}
*/
DebuggerAddressTranslator getAddressTranslator();
/**
* {@return the target or null}
*/
Target getTarget();
/**
* Check if the associated trace represents a live target
* <p>
* To be live, there must be a non-null target, that target must still be valid, i.e., connected
* to a live debugger, and the current snapshot must have a viewport incorporating the target's
* current snapshot.
*
* @return true if alive or false if dead/offline
*/
default boolean isLive() {
Target target = getTarget();
if (target == null || !target.isValid()) {
@@ -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);
}
}
@@ -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);
}
}
@@ -33,7 +33,7 @@ import ghidra.util.datastruct.ListenerSet;
public class DebuggerStaticMappingContext implements DebuggerAddressTranslator {
record ChangeCollector(DebuggerStaticMappingContext ctx, Set<Trace> traces,
public record ChangeCollector(DebuggerStaticMappingContext ctx, Set<Trace> traces,
Set<Program> programs) implements AutoCloseable {
static <T> Set<T> subtract(Set<T> a, Set<T> b) {
@@ -406,8 +406,15 @@ public class SymPcodeExecutor extends PcodeExecutor<Sym> {
// This should always end a basic block, so just do nothing
}
/**
* {@inheritDoc}
* <p>
* This could be a {@link PcodeOp#RETURN return} and the slaspec does not always set PC
* explicitly, so do it here, but don't perform any actual control transfer.
*/
@Override
protected void doExecuteIndirectBranch(PcodeOp op, PcodeFrame frame) {
// This should always end a basic block, so just do nothing
Sym offset = state.getVar(getIndirectBranchTarget(op), reason);
branchToOffset(op, offset, frame);
}
}
@@ -240,9 +240,10 @@ public class SymPcodeExecutorState implements PcodeExecutorState<Sym> {
* @return the address (stack offset or register) of the return address
*/
public Address computeAddressOfReturn() {
return switch (getVar(language.getProgramCounter(), Reason.INSPECT)) {
Register pc = language.getProgramCounter();
return switch (getVar(pc, Reason.INSPECT)) {
case StackDerefSym stackVar -> cSpec.getStackSpace().getAddress(stackVar.offset());
case RegisterSym regVar -> regVar.register().getAddress();
case RegisterSym regVar when regVar.register() != pc -> regVar.register().getAddress();
default -> null;
};
}
@@ -19,7 +19,7 @@ import java.util.Collection;
import java.util.Map.Entry;
import java.util.Objects;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.debug.api.modules.DebuggerAddressTranslator;
import ghidra.debug.api.modules.MappedAddressRange;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
@@ -39,20 +39,20 @@ import ghidra.util.MathUtilities;
* bytes from the mapped programs, along with the trace address where they apply.
*/
public abstract class AbstractMappedMemoryBytesVisitor {
private final DebuggerStaticMappingService mappingService;
private final DebuggerAddressTranslator translator;
private final byte[] buffer;
/**
* Construct a visitor object
*
* @param mappingService the mapping service
* @param translator the address translator
* @param buffer a buffer for the data. This is passed directly into
* {@link #visitData(Address, byte[], int)}. If a mapped range exceeds the buffer
* size, the range is broken down into smaller pieces.
*/
public AbstractMappedMemoryBytesVisitor(DebuggerStaticMappingService mappingService,
public AbstractMappedMemoryBytesVisitor(DebuggerAddressTranslator translator,
byte[] buffer) {
this.mappingService = Objects.requireNonNull(mappingService);
this.translator = Objects.requireNonNull(translator);
this.buffer = buffer;
}
@@ -100,7 +100,7 @@ public abstract class AbstractMappedMemoryBytesVisitor {
public boolean visit(Trace trace, long snap, AddressSetView hostView)
throws MemoryAccessException {
boolean result = false;
for (Entry<Program, Collection<MappedAddressRange>> ent : mappingService
for (Entry<Program, Collection<MappedAddressRange>> ent : translator
.getOpenMappedViews(trace, hostView, snap)
.entrySet()) {
result |= visitProgram(ent.getKey(), ent.getValue());
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -23,12 +23,13 @@ import ghidra.framework.model.DomainObject;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import utility.function.ExceptionalFunction;
public class ManagedDomainObject implements AutoCloseable {
public class ManagedDomainObject<T extends DomainObject> implements AutoCloseable {
public static final Cleaner CLEANER = Cleaner.create();
private static class ObjectState implements Runnable {
private DomainObject obj;
private static class ObjectState<T extends DomainObject> implements Runnable {
private T obj;
@Override
public synchronized void run() {
@@ -37,7 +38,7 @@ public class ManagedDomainObject implements AutoCloseable {
}
}
public synchronized DomainObject get() {
public synchronized T get() {
if (!obj.getConsumerList().contains(this)) {
throw new IllegalStateException("Domain object is closed");
}
@@ -45,20 +46,26 @@ public class ManagedDomainObject implements AutoCloseable {
}
}
private final ObjectState state = new ObjectState();
private final ObjectState<T> state = new ObjectState<>();
public ManagedDomainObject(DomainFile file, boolean okToUpgrade, boolean okToRecover,
TaskMonitor monitor) throws VersionException, CancelledException, IOException {
state.obj = file.getDomainObject(state, okToUpgrade, okToRecover, monitor);
public ManagedDomainObject(DomainFile file, Class<T> type, TaskMonitor monitor)
throws VersionException, CancelledException, IOException {
state.obj = type.cast(file.getDomainObject(state, false, false, monitor));
CLEANER.register(this, state);
}
public <E extends Exception> ManagedDomainObject(ExceptionalFunction<Object, T, E> supplier)
throws E {
state.obj = supplier.apply(state);
CLEANER.register(this, state);
}
@Override
public void close() throws Exception {
public void close() {
state.run();
}
public DomainObject get() {
public T get() {
return state.get();
}
}
@@ -25,6 +25,7 @@ import org.antlr.runtime.tree.CommonTreeNodeStream;
import ghidra.app.nav.NavigationUtils;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationIntegration;
import ghidra.app.plugin.core.debug.service.emulation.Mode;
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
import ghidra.app.plugin.processors.sleigh.SleighException;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
@@ -36,6 +37,7 @@ import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.SleighProgramCompiler.ErrorCollectingPcodeParser;
import ghidra.pcode.exec.SleighUtils.LitIdMode;
import ghidra.pcode.exec.trace.*;
import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer;
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.pcode.utils.Utils;
@@ -320,6 +322,39 @@ public enum DebuggerPcodeUtils {
return compileExpression(provider, coordinates, current, source, LitIdMode.NORMAL);
}
public record WriterAndState(Writer writer, PcodeExecutorState<byte[]> state) {}
/**
* Get a p-code executor state for the given coordinates
*
* <p>
* If a thread is included, the executor state will have access to both the memory and registers
* in the context of that thread. Otherwise, only memory access is permitted.
*
* @param provider the service provider (usually the tool)
* @param coordinates the coordinates
* @param mode determines whether or not writes affect the target
* @return the state
*/
public static WriterAndState executorStateForCoordinates(ServiceProvider provider,
DebuggerCoordinates coordinates, Mode mode) {
Trace trace = coordinates.getTrace();
if (trace == null) {
throw new IllegalArgumentException("Coordinates have no trace");
}
TracePlatform platform = coordinates.getPlatform();
if (!(platform.getLanguage() instanceof SleighLanguage language)) {
throw new IllegalArgumentException(
"Given trace or platform does not use a Sleigh language");
}
DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(provider,
coordinates.getTarget(), platform, coordinates.getViewSnap());
Writer writer = DebuggerEmulationIntegration.bytesWriteMode(access,
coordinates.getThread(), coordinates.getFrame(), mode);
return new WriterAndState(writer,
new BytesPcodeExecutorState(language, writer.wrapFor(null)));
}
/**
* Get a p-code executor state for the given coordinates
*
@@ -333,20 +368,7 @@ public enum DebuggerPcodeUtils {
*/
public static PcodeExecutorState<byte[]> executorStateForCoordinates(ServiceProvider provider,
DebuggerCoordinates coordinates) {
Trace trace = coordinates.getTrace();
if (trace == null) {
throw new IllegalArgumentException("Coordinates have no trace");
}
TracePlatform platform = coordinates.getPlatform();
if (!(platform.getLanguage() instanceof SleighLanguage language)) {
throw new IllegalArgumentException(
"Given trace or platform does not use a Sleigh language");
}
DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(provider,
coordinates.getTarget(), platform, coordinates.getViewSnap());
PcodeStateCallbacks cb = DebuggerEmulationIntegration.bytesImmediateWriteTarget(access,
coordinates.getThread(), coordinates.getFrame());
return new BytesPcodeExecutorState(language, cb);
return executorStateForCoordinates(provider, coordinates, Mode.RW).state;
}
/**
@@ -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);
}
}
@@ -93,8 +93,11 @@ public enum TraceEmulationIntegration {
TraceThread thread, int frame) {
Writer writer = new TraceWriter(access) {
@Override
protected PcodeTraceRegistersAccess getRegAccess(PcodeThread<?> ignored) {
return access.getDataForLocalState(thread, frame);
protected PcodeTraceDataAccess getDataAccess(PcodeTraceAccess access,
AddressSpace space, PcodeThread<?> ignored) {
return space.isRegisterSpace()
? access.getDataForLocalState(thread, frame)
: access.getDataForSharedState();
}
};
writer.putHandler(new ImmediateBytesPieceHandler());
@@ -310,12 +313,13 @@ public enum TraceEmulationIntegration {
* also have state assigned to abstract addresses. In such cases, it is up to the handler to
* track what has been written.
*
* @param writer the writer
* @param into the destination trace access
* @param thread the thread associated with the piece's state
* @param piece the source state piece
* @param written the portion that is known to have been written
*/
void writeDown(PcodeTraceDataAccess into, PcodeThread<?> thread,
void writeDown(TraceWriter writer, PcodeTraceAccess into, PcodeThread<?> thread,
PcodeExecutorStatePiece<A, T> piece, AddressSetView written);
}
@@ -346,7 +350,7 @@ public enum TraceEmulationIntegration {
}
@Override
public void writeDown(PcodeTraceDataAccess into, PcodeThread<?> thread,
public void writeDown(TraceWriter writer, PcodeTraceAccess into, PcodeThread<?> thread,
PcodeExecutorStatePiece<Void, Void> piece, AddressSetView written) {
}
}
@@ -398,20 +402,21 @@ public enum TraceEmulationIntegration {
}
@Override
public void writeDown(PcodeTraceDataAccess into, PcodeThread<?> thread,
public void writeDown(TraceWriter writer, PcodeTraceAccess into, PcodeThread<?> thread,
PcodeExecutorStatePiece<byte[], byte[]> piece, AddressSetView written) {
for (AddressRange range : written) {
AddressSpace space = range.getAddressSpace();
if (space.isUniqueSpace()) {
continue;
}
PcodeTraceDataAccess acc = writer.getDataAccess(into, space, thread);
long lower = range.getMinAddress().getOffset();
long fullLen = range.getLength();
while (fullLen > 0) {
int len = MathUtilities.unsignedMin(CHUNK_SIZE, fullLen);
// NOTE: Would prefer less copying and less heap garbage....
byte[] bytes = piece.getVarInternal(space, lower, len, Reason.INSPECT);
into.putBytes(space.getAddress(lower), ByteBuffer.wrap(bytes));
acc.putBytes(space.getAddress(lower), ByteBuffer.wrap(bytes));
lower += bytes.length;
fullLen -= bytes.length;
@@ -564,14 +569,15 @@ public enum TraceEmulationIntegration {
* portion.
*/
@Override
public void writeDown(PcodeTraceDataAccess into, PcodeThread<?> thread,
public void writeDown(TraceWriter writer, PcodeTraceAccess into, PcodeThread<?> thread,
PcodeExecutorStatePiece<A, T> piece, AddressSetView written) {
PcodeTracePropertyAccess<P> property =
into.getPropertyAccess(getPropertyName(), getPropertyType());
AddressSet remains = new AddressSet(written);
while (!remains.isEmpty()) {
Address cur = remains.getMinAddress();
AddressSpace space = cur.getAddressSpace();
PcodeTraceDataAccess acc = writer.getDataAccess(into, space, thread);
PcodeTracePropertyAccess<P> property =
acc.getPropertyAccess(getPropertyName(), getPropertyType());
Entry<Long, T> entry = piece.getNextEntryInternal(space, cur.getOffset());
if (entry == null) {
remains.delete(space.getMinAddress(), space.getMaxAddress());
@@ -662,18 +668,19 @@ public enum TraceEmulationIntegration {
protected final PcodeTraceMemoryAccess memAccess;
protected final Map<PcodeThread<?>, PcodeTraceRegistersAccess> regAccess = new HashMap<>();
record PieceInfo(PcodeThread<?> thread, AddressSet written) {}
/**
* An address set to track what has actually been written. It's not enough to just use the
* {@link SemisparseByteArray}'s initialized set, as that may be caching bytes from the
* trace which are still {@link TraceMemoryState#UNKNOWN}.
*/
protected final AddressSet memWritten = new AddressSet();
protected final Map<PcodeThread<?>, AddressSet> regsWritten = new HashMap<>();
//protected final AddressSet memWritten = new AddressSet();
//protected final Map<PcodeThread<?>, AddressSet> regsWritten = new HashMap<>();
protected final Map<PcodeExecutorStatePiece<?, ?>, PieceInfo> pieces = new HashMap<>();
protected final Map<PieceType<?, ?>, PieceHandler<?, ?>> handlers = new HashMap<>();
private PcodeMachine<?> emulator;
/**
* Construct a writer which sources state from the given access shim
*
@@ -689,11 +696,6 @@ public enum TraceEmulationIntegration {
handlers.put(PieceType.forHandler(handler), handler);
}
@Override
public void emulatorCreated(PcodeMachine<Object> emulator) {
this.emulator = emulator;
}
@Override
public void threadCreated(PcodeThread<Object> thread) {
access.getDataForLocalState(thread, 0).initializeThreadContext(thread);
@@ -715,28 +717,17 @@ public enum TraceEmulationIntegration {
* @param piece the piece
* @param written the logged portions written
*/
protected <B, U> void writePieceDown(PcodeTraceDataAccess into, PcodeThread<?> thread,
protected <B, U> void writePieceDown(PcodeTraceAccess into, PcodeThread<?> thread,
PcodeExecutorStatePiece<B, U> piece, AddressSetView written) {
PieceHandler<B, U> handler = handlerFor(piece);
handler.writeDown(into, thread, piece, written);
handler.writeDown(this, into, thread, piece, written);
}
@Override
public void writeDown(PcodeTraceAccess into) {
PcodeTraceMemoryAccess memInto = into.getDataForSharedState();
for (PcodeExecutorStatePiece<?, ?> piece : emulator.getSharedState()
.streamPieces()
.toList()) {
writePieceDown(memInto, null, piece, memWritten);
}
for (PcodeThread<?> thread : emulator.getAllThreads()) {
PcodeTraceRegistersAccess regInto = into.getDataForLocalState(thread, 0);
AddressSetView written = regsWritten.getOrDefault(thread, new AddressSet());
for (PcodeExecutorStatePiece<?, ?> piece : thread.getState()
.streamPieces()
.toList()) {
writePieceDown(regInto, thread, piece, written);
}
for (Entry<PcodeExecutorStatePiece<?, ?>, PieceInfo> ent : pieces.entrySet()) {
PieceInfo info = ent.getValue();
writePieceDown(into, info.thread, ent.getKey(), info.written);
}
}
@@ -745,22 +736,20 @@ public enum TraceEmulationIntegration {
writeDown(access.deriveForWrite(snap));
}
protected PcodeTraceRegistersAccess getRegAccess(PcodeThread<?> thread) {
// Always use frame 0
return regAccess.computeIfAbsent(thread, t -> access.getDataForLocalState(t, 0));
protected PcodeTraceDataAccess getDataAccess(PcodeTraceAccess access, AddressSpace space,
PcodeThread<?> thread) {
return space.isRegisterSpace()
? access.getDataForLocalState(thread, 0)
: access.getDataForSharedState();
}
@Override
public <B, U> void dataWritten(PcodeThread<Object> thread,
PcodeExecutorStatePiece<B, U> piece,
Address address, int length, U value) {
PcodeTraceDataAccess acc = address.isRegisterAddress()
? getRegAccess(thread)
: memAccess;
AddressSet written = address.isRegisterAddress()
? regsWritten.computeIfAbsent(thread, t -> new AddressSet())
: memWritten;
if (handlerFor(piece).dataWritten(acc, written, thread, piece, address, length,
PcodeExecutorStatePiece<B, U> piece, Address address, int length, U value) {
PcodeTraceDataAccess acc = getDataAccess(access, address.getAddressSpace(), thread);
PieceInfo info =
pieces.computeIfAbsent(piece, p -> new PieceInfo(thread, new AddressSet()));
if (handlerFor(piece).dataWritten(acc, info.written, thread, piece, address, length,
value)) {
return;
}
@@ -769,12 +758,12 @@ public enum TraceEmulationIntegration {
}
Address end = address.addWrap(length - 1);
if (address.compareTo(end) <= 0) {
written.add(address, end);
info.written.add(address, end);
}
else {
AddressSpace space = address.getAddressSpace();
written.add(address, space.getMaxAddress());
written.add(space.getMinAddress(), end);
info.written.add(address, space.getMaxAddress());
info.written.add(space.getMinAddress(), end);
}
}
@@ -782,19 +771,18 @@ public enum TraceEmulationIntegration {
public <B, U> void dataWritten(PcodeThread<Object> thread,
PcodeExecutorStatePiece<B, U> piece, AddressSpace space, B offset, int length,
U value) {
PcodeTraceDataAccess acc = space.isRegisterSpace() ? getRegAccess(thread) : memAccess;
AddressSet written = space.isRegisterSpace()
? regsWritten.computeIfAbsent(thread, t -> new AddressSet())
: memWritten;
handlerFor(piece).abstractWritten(acc, written, thread, piece, space, offset, length,
value);
PcodeTraceDataAccess acc = getDataAccess(access, space, thread);
PieceInfo info =
pieces.computeIfAbsent(piece, p -> new PieceInfo(thread, new AddressSet()));
handlerFor(piece).abstractWritten(acc, info.written, thread, piece, space, offset,
length, value);
}
@Override
public <B, U> int readUninitialized(PcodeThread<Object> thread,
PcodeExecutorStatePiece<B, U> piece, AddressSpace space, B offset, int length,
Reason reason) {
PcodeTraceDataAccess acc = space.isRegisterSpace() ? getRegAccess(thread) : memAccess;
PcodeTraceDataAccess acc = getDataAccess(access, space, thread);
return handlerFor(piece).abstractReadUninit(acc, thread, piece, space, offset, length,
reason);
}
@@ -809,7 +797,7 @@ public enum TraceEmulationIntegration {
if (space.isUniqueSpace()) {
return set;
}
PcodeTraceDataAccess acc = space.isRegisterSpace() ? getRegAccess(thread) : memAccess;
PcodeTraceDataAccess acc = getDataAccess(access, space, thread);
return handlerFor(piece).readUninitialized(acc, thread, piece, set);
}
}
@@ -16,6 +16,8 @@
package ghidra.pcode.exec.trace.data;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
@@ -36,6 +38,8 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
protected final TraceMemoryManager mm;
protected final Map<String, PcodeTracePropertyAccess<?>> properties = new HashMap<>();
/**
* Construct a shim
*
@@ -204,7 +208,11 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
}
@Override
@SuppressWarnings("unchecked")
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
return new DefaultPcodeTracePropertyAccess<>(this, name, type);
synchronized (properties) {
return (PcodeTracePropertyAccess<T>) properties.computeIfAbsent(name,
n -> new DefaultPcodeTracePropertyAccess<>(this, n, type));
}
}
}
@@ -26,6 +26,7 @@ import java.util.List;
import org.junit.Test;
import db.Transaction;
import generic.Unique;
import ghidra.app.plugin.assembler.*;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock;
import ghidra.pcode.emu.PcodeEmulator;
@@ -68,7 +69,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest
emu.getSharedState().setVar(tb.addr(0x00400000), 0, false, tb.arr());
TraceWriter tw = (TraceWriter) writer;
assertEquals(tb.set(), tw.memWritten);
assertEquals(tb.set(), Unique.assertOne(tw.pieces.entrySet()).getValue().written());
}
}

Some files were not shown because too many files have changed in this diff Show More