GP-2834: Add Unwind Stack action, hovers for dynamic variable values.

This commit is contained in:
Dan 2023-01-12 13:38:17 -05:00
parent 01dfe6cab5
commit df9a1e2756
105 changed files with 12292 additions and 482 deletions

View file

@ -68,6 +68,7 @@ src/main/help/help/topics/DebuggerRegistersPlugin/images/DebuggerAvailableRegist
src/main/help/help/topics/DebuggerRegistersPlugin/images/DebuggerRegistersPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerStackPlugin/DebuggerStackPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerStackPlugin/images/DebuggerStackPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerStackPlugin/images/DebuggerStackUnwindInListing.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerStaticMappingPlugin/images/DebuggerStaticMappingPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerTargetsPlugin/DebuggerTargetsPlugin.html||GHIDRA||||END|
@ -83,6 +84,10 @@ src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTimeSelecti
src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTraceViewDiffPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png||GHIDRA||||END|
src/main/help/help/topics/VariableValueHoverPlugin/VariableValueHoverPlugin.html||GHIDRA||||END|
src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginBrowser.png||GHIDRA||||END|
src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginDecompiler.png||GHIDRA||||END|
src/main/help/help/topics/VariableValueHoverPlugin/images/VariableValueHoverPluginListing.png||GHIDRA||||END|
src/main/resources/defaultTools/Debugger.tool||GHIDRA||||END|
src/main/resources/images/add.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/attach.png||GHIDRA||||END|

View file

@ -0,0 +1,64 @@
/* ###
* 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.
*/
//A script to analyze unwind information for the current function wrt. the current location
//as a program counter. The resulting information can be used to interpret how the function
//is using various elements on the stack when the program counter is at the cursor. This
//script is more for diagnostic and demonstration purposes, since the application of unwind
//information is already integrated into the Debugger.
//@author
//@category Stack
//@keybinding
//@menupath
//@toolbar
import java.util.Map.Entry;
import ghidra.app.plugin.core.debug.stack.*;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register;
public class ComputeUnwindInfoScript extends GhidraScript {
String addressToString(Address address) {
Register[] registers = currentProgram.getLanguage().getRegisters(address);
if (registers.length == 0) {
return address.toString();
}
return registers[0].getBaseRegister().toString();
}
@Override
protected void run() throws Exception {
UnwindAnalysis ua = new UnwindAnalysis(currentProgram);
UnwindInfo info = ua.computeUnwindInfo(currentAddress, monitor);
if (info == null) {
println("Could not unwind");
return;
}
println("Stack depth at " + currentAddress + ": " + info.depth());
println("Return address address: " + addressToString(info.ofReturn()));
println("Saved registers:");
for (Entry<Register, Address> entry : info.saved().entrySet()) {
println(" " + entry);
}
println("Warnings:");
for (StackUnwindWarning warning : info.warnings()) {
println(" " + warning.getMessage());
}
}
}

View file

@ -143,11 +143,11 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
protected BytesPcodeThread createThread(String name) {
return new BytesPcodeThread(name, this) {
TraceThread thread = trace.getThreadManager().getLiveThreadByPath(snap, name);
PcodeExecutor<Pair<byte[], Address>> inspector =
PcodeExecutor<Pair<byte[], ValueLocation>> inspector =
new PcodeExecutor<>(language,
new PairedPcodeArithmetic<>(arithmetic,
AddressOfPcodeArithmetic.INSTANCE),
state.paired(new AddressOfPcodeExecutorStatePiece(language)),
LocationPcodeArithmetic.forEndian(language.isBigEndian())),
state.paired(new LocationPcodeExecutorStatePiece(language)),
Reason.INSPECT);
{
@ -169,7 +169,7 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
}
public CheckRow createRow() {
List<Pair<byte[], Address>> values = new ArrayList<>();
List<Pair<byte[], ValueLocation>> values = new ArrayList<>();
for (PcodeExpression exp : compiled) {
values.add(exp.evaluate(inspector));
}
@ -254,17 +254,17 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
class CheckRow implements AddressableRowObject {
private final TraceSchedule schedule;
private final Address pc;
private final List<Pair<byte[], Address>> values;
private final List<Pair<byte[], ValueLocation>> values;
public CheckRow(TraceSchedule schedule, Address pc,
List<Pair<byte[], Address>> values) {
List<Pair<byte[], ValueLocation>> values) {
this.schedule = schedule;
this.pc = pc;
this.values = values;
}
@Override
public Address getAddress() {
public Address getAddress() { // Instruction address
TraceProgramView view = getCurrentView();
if (view == null) {
return Address.NO_ADDRESS;
@ -352,8 +352,8 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
private Object getObjectValue(CheckRow r) {
try {
Pair<byte[], Address> p = r.values.get(index);
Address addr = p.getRight();
Pair<byte[], ValueLocation> p = r.values.get(index);
Address addr = p.getRight() == null ? null : p.getRight().getAddress();
byte[] bytes = p.getLeft();
return type.getValue(new ByteMemBufferImpl(addr, bytes, isBigEndian),
watch.settings, bytes.length);
@ -365,8 +365,8 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
private String getStringValue(CheckRow r) {
try {
Pair<byte[], Address> p = r.values.get(index);
Address addr = p.getRight();
Pair<byte[], ValueLocation> p = r.values.get(index);
Address addr = p.getRight() == null ? null : p.getRight().getAddress();
byte[] bytes = p.getLeft();
return type.getRepresentation(new ByteMemBufferImpl(addr, bytes, isBigEndian),
watch.settings, bytes.length);

View file

@ -153,8 +153,12 @@
sortgroup="n"
target="help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html" />
<tocdef id="DebuggerControlPlugin" text="Control and Machine State"
<tocdef id="VariableValueHoverPlugin" text="Variable Hovers"
sortgroup="n1"
target="help/topics/VariableValueHoverPlugin/VariableValueHoverPlugin.html" />
<tocdef id="DebuggerControlPlugin" text="Control and Machine State"
sortgroup="n2"
target="help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html" />
<tocdef id="DebuggerMemviewPlugin" text="Memview Plot"

View file

@ -49,5 +49,60 @@
<LI>Comment - a user-modifiable comment.</LI>
</UL>
<H2>Action</H2>
<P>The stack plugin provides a single action:</P>
<H3><A name="unwind_stack">Unwind Stack (U)</A></H3>
<P>This action is in the main menu: <SPAN class="menu">Debugger &rarr; Analysis &rarr; Unwind
from frame 0</SPAN>. It attempts to unwind the current thread's stack segment, creating frame
data units in the listing. It starts by reading the program counter and stack pointer from the
innermost frame of the current thread. It then maps the program counter to the program database
and analyzes the function containing it. If successful, it can determine the frame's base
address and locate variables, saved registers, and the return address. Knowing the return
address and frame depth, it can derive the program counter and stack pointer of the next frame
and unwind it in the same manner. This proceeds until analysis fails or the stack segment is
exhausted. For best results, ensure you have imported and opened the Ghidra program database
for every module, or at least the subset you expect to see in your stack. To view the results,
navigate to or follow the stack pointer in a Dynamic Listing.</P>
<TABLE width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG alt="" border="1" src=
"images/DebuggerStackUnwindInListing.png"></TD>
</TR>
</TBODY>
</TABLE>
<P>Each call record generates a structure data unit derived from the function's frame. The
exact contents of the structure depend on the current program counter within that function.
Only those entries actually allocated at the program counter are included. Each field in that
structure can be one of five kinds:</P>
<UL>
<LI><B>Local stack variable:</B> These are named with the <CODE>local_</CODE> prefix. They
correspond exactly to those entries found in the function's stack frame in the program
database.</LI>
<LI><B>Stack parameter:</B> These are named with the <CODE>param_</CODE> prefix. They
correspond exactly to those entries found in the function's stack frame in the program
database.</LI>
<LI><B>Return address:</B> This is named <CODE>return_address</CODE>. It is determined by
interpreting the function's machine code.</LI>
<LI><B>Saved register:</B> These are named with the <CODE>saved_</CODE> prefix. They are
determined by interpreting the function's machine code.</LI>
<LI><B>Slack space:</B> These are named with the <CODE>offset_</CODE> prefix (or
<CODE>posOff_</CODE> for positive offsets). They represent unused or unknown entries.</LI>
</UL>
<P>The frame entries are not automatically updated when a function's frame changes in a program
database. To update the unwind after changing a function's stack frame, you must unwind
again.</P>
</BODY>
</HTML>

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -0,0 +1,141 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Debugger: Variable Hovers</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY lang="EN-US">
<H1><A name="plugin"></A>Debugger: Variable Hovers</H1>
<P>This service plugin provides hovers to the <A href=
"help/topics/CodeBrowserPlugin/CodeBrowser.htm">Static Listing</A>, the <A href=
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Dynamic Listing</A>, and the <A
href="help/topics/DecompilePlugin/DecompilerIntro.html">Decompiler</A>. Hovering the mouse over
variables or operands in any of those windows will cause the service to display a tip showing
the value of that variable, if it can. For stack and register variables, the service will
attempt to unwind the stack until it finds a call record for the function using the variable.
Thus, it is important to have all the modules imported, analyzed, and open in the Debugger so
that the unwinder can access the static analysis of every function it needs to unwind.</P>
<P>Stack unwinding can be tenuous. It relies heavily on accurate static analysis and expects
functions to follow certain conventions. Thus, it's very easy to break and may frequently be
incorrect. For hovers that include a <B>Frame</B> row, the displayed value depends on an
accurately unwound stack. Take the value with a grain of salt, especially if the hover also
includes a <B>Warnings:</B> row. To diagnose the unwound stack, use the <A href=
"help/topics/DebuggerStackPlugin/DebuggerStackPlugin.html#unwind_stack">Unwind Stack</A>
action.</P>
<H2>Table Rows</H2>
<P>A hover may display any of the following rows:</P>
<UL>
<LI><B>Name:</B> The name of the variable or operand.</LI>
<LI><B>Frame:</B> A description of the frame (call record) unwound to evaluate the variable.
This is omitted for global or static variables and for raw register operands.</LI>
<LI><B>Storage:</B> The statically-defined storage of the variable.</LI>
<LI><B>Type:</B> The data type of the variable.</LI>
<LI><B>Instruction:</B> If the operand refers to code, the instruction at the target
address.</LI>
<LI><B>Location:</B> The actual location of the variable on the target.</LI>
<LI><B>Bytes:</B> The bytes in the variable, subject to the target's endianness. For long
buffers, the bytes are split into lines of 16 bytes each for at most 16 lines.</LI>
<LI><B>Integer:</B> The value displayed as an integer in various formats: decimal,
hexadecimal; unsigned, signed. The alternative formats are only included if they differ from
the formats already presented.</LI>
<LI><B>Value:</B> The value as given by its type's default representation. This only applies
if the variable has a type and it was able to interpret the variable's bytes.</LI>
<LI><B>Status:</B> If the evaluation is taking significant time, this provides feedback while
evaluation proceeds in the background.</LI>
<LI><B>Warnings:</B> Displays any warnings encountered while unwinding the stack, if
applicable.</LI>
<LI><B>Error:</B> Displays an exception or error message when there was a problem evaluating
the variable. Other rows may be present, but overall the table is incomplete.</LI>
</UL>
<H2>Examples</H2>
<TABLE width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG alt="" border="1" src=
"images/VariableValueHoverPluginListing.png"></TD>
</TR>
<TR>
<TD align="center">A register operand in the Dynamic Listing</TD>
</TR>
</TBODY>
</TABLE>
<P>When hovering over operands in the Dynamic Listing, that operand is most likely a register.
The register's value is displayed without regard to the stack frame. It will always use the
"innermost frame," meaning it will display the register's current value. In the example, the
user has hovered over register EDX; however, the value of EDX was not recorded, so its integer
value <CODE>0</CODE> is displayed in gray. Furthermore, the user had not assigned a type to EDX
in the <A href="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A>
window, and so the service cannot interpret the value except as an integer. Register values are
never displayed as raw byte arrays.</P>
<TABLE width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG alt="" border="1" src=
"images/VariableValueHoverPluginBrowser.png"></TD>
</TR>
<TR>
<TD align="center">A stack variable in the Static Listing</TD>
</TR>
</TBODY>
</TABLE>
<P>When hovering over operands in the Static Listing, the service will gather context about the
operand and find a frame for the relevant function. It will take the first appropriate frame it
encounters during unwinding (from innermost out) so long as the frame's level is at least the
current frame level. In the example, the user has hovered over the parameter <EM>n</EM>, which
is a stack variable of the function <EM>fib</EM>. The curent frame is 0, so the service unwinds
the stack, finds that the current frame is a call record for <EM>fib</EM>, and selects it. It
displays the variable's static storage <CODE>Stack[0x4]:4</CODE> and type <CODE>uint</CODE>. It
then applies this information to determine the actual run-time location and value. Since the
frame base is <CODE>00004fa0</CODE>, it adds the stack offset to compute the run-time location
<CODE>00004fa4:4</CODE>. It reads the bytes <CODE>01 00 00 00</CODE> from the target and
computes the integer value <CODE>1</CODE>. It also interprets the value using the assigned data
type, giving <CODE>1h</CODE>.</P>
<TABLE width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG alt="" border="1" src=
"images/VariableValueHoverPluginDecompiler.png"></TD>
</TR>
<TR>
<TD align="center">A stack variable in the Decompiler</TD>
</TR>
</TBODY>
</TABLE>
<P>When hovering over variables in the Decompiler, the service behaves similarly to how it does
for operands in the Static Listing. It locates the appropriate frame and attempts to derive the
variable's run-time value. Just as in the Static Listing example above, the user has hovered
over the variable <EM>n</EM>, so the service has again computed the value <CODE>1h</CODE>.</P>
</BODY>
</HTML>

View file

@ -39,6 +39,7 @@ import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.listing.MultiBlendedListingBackgroundColorModel;
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimeSelectionDialog;
import ghidra.app.plugin.core.debug.utils.BackgroundUtils.PluginToolExecutorService;
import ghidra.app.plugin.core.debug.utils.BackgroundUtils.PluginToolExecutorService.TaskOpt;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerListingService.LocationTrackingSpecChangeListener;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
@ -58,12 +59,21 @@ import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Msg;
@PluginInfo(shortDescription = "Compare memory state between times in a trace", description = "Provides a side-by-side diff view between snapshots (points in time) in a " +
"trace. The comparison is limited to raw bytes.", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, eventsConsumed = {
@PluginInfo(
shortDescription = "Compare memory state between times in a trace",
description = "Provides a side-by-side diff view between snapshots (points in time) in a " +
"trace. The comparison is limited to raw bytes.",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceClosedPluginEvent.class,
}, eventsProduced = {}, servicesRequired = {
},
eventsProduced = {},
servicesRequired = {
DebuggerListingService.class,
}, servicesProvided = {})
},
servicesProvided = {})
public class DebuggerTraceViewDiffPlugin extends AbstractDebuggerPlugin {
protected static final String MARKER_NAME = "Trace Diff";
protected static final String MARKER_DESCRIPTION = "Difference between snapshots in this trace";
@ -223,7 +233,8 @@ public class DebuggerTraceViewDiffPlugin extends AbstractDebuggerPlugin {
DebuggerCoordinates current = traceManager.getCurrent();
DebuggerCoordinates alternate = traceManager.resolveTime(time);
PluginToolExecutorService toolExecutorService =
new PluginToolExecutorService(tool, "Computing diff", true, true, false, 500);
new PluginToolExecutorService(tool, "Computing diff", null, 500,
TaskOpt.HAS_PROGRESS, TaskOpt.CAN_CANCEL);
return traceManager.materialize(alternate).thenApplyAsync(snap -> {
clearMarkers();
TraceProgramView altView = alternate.getTrace().getFixedProgramView(snap);

View file

@ -109,10 +109,9 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
}
/**
* Convenience method for locating columns by name.
* Implementation is naive so this should be overridden if
* this method is to be called often. This method is not
* in the TableModel interface and is not used by the JTable.
* Convenience method for locating columns by name. Implementation is naive so this should be
* overridden if this method is to be called often. This method is not in the TableModel
* interface and is not used by the JTable.
*/
@Override
public int findColumn(String columnName) {
@ -125,7 +124,7 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
}
/**
* Returns Object.class by default
* Returns Object.class by default
*/
@Override
public Class<?> getColumnClass(int columnIndex) {
@ -136,7 +135,7 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
}
/**
* Return whether this column is editable.
* Return whether this column is editable.
*/
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
@ -144,10 +143,9 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
}
/**
* Returns the number of records managed by the data source object. A
* <B>JTable</B> uses this method to determine how many rows it
* should create and display. This method should be quick, as it
* is call by <B>JTable</B> quite frequently.
* Returns the number of records managed by the data source object. A <B>JTable</B> uses this
* method to determine how many rows it should create and display. This method should be quick,
* as it is call by <B>JTable</B> quite frequently.
*
* @return the number or rows in the model
* @see #getColumnCount

View file

@ -16,31 +16,49 @@
package ghidra.app.plugin.core.debug.gui.stack;
import java.awt.BorderLayout;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.Objects;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.*;
import org.apache.commons.lang3.ArrayUtils;
import docking.ActionContext;
import docking.WindowPosition;
import docking.action.DockingAction;
import docking.action.builder.ActionBuilder;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.stack.UnwindStackCommand;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.HelpLocation;
public class DebuggerStackProvider extends ComponentProviderAdapter {
public interface UnwindStackAction {
String NAME = "Unwind from frame 0";
String DESCRIPTION = "Unwind the stack, placing frames in the dynamic listing";
String HELP_ANCHOR = "unwind_stack";
KeyStroke KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_U, 0);
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName).description(DESCRIPTION)
.menuPath(DebuggerPluginPackage.NAME, "Analysis", NAME)
.keyBinding(KEY_STROKE)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
if (!Objects.equals(a.getTrace(), b.getTrace())) {
return false;
@ -71,6 +89,8 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
/*testing*/ DebuggerStackPanel panel;
/*testing*/ DebuggerLegacyStackPanel legacyPanel;
DockingAction actionUnwindStack;
public DebuggerStackProvider(DebuggerStackPlugin plugin) {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_STACK, plugin.getName());
this.plugin = plugin;
@ -96,7 +116,14 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
}
protected void createActions() {
// TODO: Anything?
actionUnwindStack = UnwindStackAction.builder(plugin)
.enabledWhen(ctx -> current.getTrace() != null)
.onAction(this::activatedUnwindStack)
.buildAndInstall(tool);
}
private void activatedUnwindStack(ActionContext ignored) {
new UnwindStackCommand(tool, current).run(tool, current.getTrace());
}
@Override

View file

@ -0,0 +1,66 @@
/* ###
* 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.stack.vars;
import ghidra.app.decompiler.component.hover.DecompilerHoverService;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.codebrowser.hover.ListingHoverService;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = DebuggerPluginPackage.NAME,
category = PluginCategoryNames.DEBUGGER,
shortDescription = "Variable Values Hover",
description = "Displays live variable values in a tooltip as you hover over a variable in " +
"the listings or decompiler.",
eventsConsumed = {
TraceClosedPluginEvent.class
},
servicesProvided = {
ListingHoverService.class,
DecompilerHoverService.class
})
public class VariableValueHoverPlugin extends Plugin {
private VariableValueHoverService hoverService;
public VariableValueHoverPlugin(PluginTool tool) {
super(tool);
hoverService = new VariableValueHoverService(tool);
registerServiceProvided(ListingHoverService.class, hoverService);
registerServiceProvided(DecompilerHoverService.class, hoverService);
}
public VariableValueHoverService getHoverService() {
return hoverService;
}
@Override
protected void dispose() {
hoverService.dispose();
}
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
if (event instanceof TraceClosedPluginEvent evt) {
hoverService.traceClosed(evt.getTrace());
}
}
}

View file

@ -0,0 +1,675 @@
/* ###
* 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.stack.vars;
import java.awt.Window;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import javax.swing.*;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.support.FieldLocation;
import ghidra.GhidraOptions;
import ghidra.app.decompiler.ClangFieldToken;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.component.ClangTextField;
import ghidra.app.decompiler.component.hover.DataTypeDecompilerHover;
import ghidra.app.decompiler.component.hover.DecompilerHoverService;
import ghidra.app.plugin.core.codebrowser.hover.ListingHoverService;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueRow.*;
import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueUtils.VariableEvaluator;
import ghidra.app.plugin.core.debug.stack.*;
import ghidra.app.plugin.core.debug.utils.BackgroundUtils.PluginToolExecutorService;
import ghidra.app.plugin.core.debug.utils.BackgroundUtils.PluginToolExecutorService.TaskOpt;
import ghidra.app.plugin.core.hover.AbstractConfigurableHover;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.async.AsyncUtils;
import ghidra.async.SwingExecutorService;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.pcode.exec.DebuggerPcodeUtils;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValuePcodeExecutorState;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.PointerDataType;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.*;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.util.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.task.TaskMonitor;
public class VariableValueHoverService extends AbstractConfigurableHover
implements ListingHoverService, DecompilerHoverService {
private static final String NAME = "Variable Value Display";
private static final String DESCRIPTION =
"Show a variable's value when hovering over it and debugging";
private static final int PRIORITY = 100;
private static class LRUCache<K, V> extends LinkedHashMap<K, V> {
private static final int DEFAULT_MAX_SIZE = 5;
private int maxSize;
public LRUCache() {
this(DEFAULT_MAX_SIZE);
}
public LRUCache(int maxSize) {
super(maxSize, 0.75f, true);
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
if (size() > maxSize) {
removed(eldest);
return true;
}
return false;
}
protected void removed(Map.Entry<K, V> eldest) {
}
}
// TODO: Option to always unwind from frame 0, or take the nearest frame?
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
private DebuggerStaticMappingService mappingService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
private final Map<DebuggerCoordinates, VariableEvaluator> cachedEvaluators =
new LRUCache<>() {
protected void removed(Map.Entry<DebuggerCoordinates, VariableEvaluator> eldest) {
eldest.getValue().dispose();
}
};
public VariableValueHoverService(PluginTool tool) {
super(tool, PRIORITY);
autoServiceWiring = AutoService.wireServicesConsumed(tool, this);
}
@Override
public void dispose() {
super.dispose();
for (VariableEvaluator eval : cachedEvaluators.values()) {
eval.dispose();
}
}
@Override
protected String getName() {
return NAME;
}
@Override
protected String getDescription() {
return DESCRIPTION;
}
@Override
protected String getOptionsCategory() {
return GhidraOptions.CATEGORY_DECOMPILER_POPUPS;
}
public static class TableFiller {
private final VariableValueTable table;
private final PluginTool tool;
private final DebuggerCoordinates current;
private final List<String> warnings;
private final DebuggerStaticMappingService mappingService;
private final VariableEvaluator eval;
public TableFiller(VariableValueTable table, PluginTool tool, DebuggerCoordinates current,
VariableEvaluator eval, List<String> warnings) {
this.table = table;
this.tool = tool;
this.current = current;
this.warnings = warnings;
this.mappingService = tool.getService(DebuggerStaticMappingService.class);
this.eval = eval;
}
protected <T> CompletableFuture<T> executeBackground(
java.util.function.Function<TaskMonitor, T> command) {
PluginToolExecutorService executor =
new PluginToolExecutorService(tool, "Get Variable Value", current.getTrace(), 250,
TaskOpt.IS_BACKGROUND, TaskOpt.CAN_CANCEL);
return CompletableFuture.supplyAsync(() -> command.apply(executor.getLastMonitor()),
executor);
}
public VariableValueTable fillUndefinedUnit(TraceData dynData, Program stProg,
Address stAddr) {
if (stProg == null) {
return fillDefinedData(dynData);
}
CodeUnit stUnit = stProg.getListing().getCodeUnitAt(stAddr);
if (stUnit == null) {
return fillDefinedData(dynData);
}
if (stUnit instanceof Data stData) {
DataType stType = stData.getDataType();
if (stType == DataType.DEFAULT) {
return fillDefinedData(dynData);
}
table.add(StorageRow.fromCodeUnit(stUnit));
table.add(new TypeRow(stType));
AddressRange dynRange = new AddressRangeImpl(dynData.getMinAddress(),
dynData.getMinAddress().add(stData.getLength() - 1));
table.add(LocationRow.fromRange(dynRange));
BytesRow bytesRow =
BytesRow.fromRange(current.getPlatform(), dynRange, current.getViewSnap());
table.add(bytesRow);
table.add(new IntegerRow(bytesRow));
String repr = eval.getRepresentation(dynData.getAddress(), bytesRow.bytes().bytes(),
stType, stData);
if (repr != null) {
table.add(new ValueRow(repr, bytesRow.state()));
}
return table;
}
if (stUnit instanceof Instruction stIns) {
fillDefinedData(dynData);
table.add(new InstructionRow(stIns));
table.add(new WarningsRow("Instruction taken from static listing"));
return table;
}
throw new AssertionError();
}
public VariableValueTable fillDefinedData(TraceData data) {
table.add(new TypeRow(data.getDataType()));
table.add(LocationRow.fromCodeUnit(data));
BytesRow bytesRow = BytesRow.fromCodeUnit(data, current.getViewSnap());
table.add(bytesRow);
table.add(new IntegerRow(bytesRow));
String repr = data.getDefaultValueRepresentation();
if (repr != null) {
table.add(new ValueRow(repr, bytesRow.state()));
}
return table;
}
public VariableValueTable fillInstruction(TraceInstruction ins) {
table.add(LocationRow.fromCodeUnit(ins));
table.add(BytesRow.fromCodeUnit(ins, current.getViewSnap()));
table.add(new InstructionRow(ins));
return table;
}
public VariableValueTable fillCodeUnit(TraceCodeUnit unit, Program stProg, Address stAddr) {
Symbol[] dynSymbols = unit.getSymbols();
if (dynSymbols.length != 0) {
table.add(new NameRow(dynSymbols[0].getName(true)));
}
else if (stProg != null) {
Symbol[] stSymbols = stProg.getSymbolTable().getSymbols(stAddr);
if (stSymbols.length != 0) {
table.add(new NameRow(stSymbols[0].getName(true)));
}
}
if (unit instanceof TraceData data) {
if (data.getDataType() == DataType.DEFAULT) {
return fillUndefinedUnit(data, stProg, stAddr);
}
return fillDefinedData(data);
}
else if (unit instanceof TraceInstruction ins) {
return fillInstruction(ins);
}
else {
throw new AssertionError();
}
}
record MappedLocation(Program stProg, Address stAddr, Address dynAddr) {
}
protected MappedLocation mapLocation(Program programOrView, Address address) {
if (programOrView instanceof TraceProgramView view) {
ProgramLocation stLoc =
mappingService.getStaticLocationFromDynamic(new ProgramLocation(view, address));
return stLoc == null
? new MappedLocation(null, null, address)
: new MappedLocation(stLoc.getProgram(), stLoc.getAddress(), address);
}
ProgramLocation dynLoc = mappingService.getDynamicLocationFromStatic(current.getView(),
new ProgramLocation(programOrView, address));
return new MappedLocation(programOrView, address,
dynLoc == null ? null : dynLoc.getAddress());
}
public CompletableFuture<VariableValueTable> fillMemory(Program programOrView,
Address refAddress) {
MappedLocation mapped = mapLocation(programOrView, refAddress);
if (mapped.dynAddr == null) {
return null;
}
WatchValuePcodeExecutorState state = DebuggerPcodeUtils.buildWatchState(tool, current);
TraceCodeUnitsView codeUnits = current.getTrace().getCodeManager().codeUnits();
TraceCodeUnit unit = codeUnits.getContaining(current.getViewSnap(), mapped.dynAddr);
if (unit == null) {
// Not sure this should ever happen....
return null;
}
return CompletableFuture.supplyAsync(() -> {
state.getVar(mapped.dynAddr, unit.getLength(), true, Reason.INSPECT);
TraceCodeUnit unitAfterUpdate =
codeUnits.getContaining(current.getViewSnap(), mapped.dynAddr);
return fillCodeUnit(unitAfterUpdate, mapped.stProg, mapped.stAddr);
});
}
public CompletableFuture<VariableValueTable> fillStack(Instruction ins,
Address stackAddress) {
Function function =
ins.getProgram().getFunctionManager().getFunctionContaining(ins.getMinAddress());
if (function == null) {
return null;
}
Variable variable = VariableValueUtils.findStackVariable(function, stackAddress);
return executeBackground(monitor -> {
UnwoundFrame<WatchValue> frame = eval.getStackFrame(function, warnings, monitor);
if (frame == null) {
throw new UnwindException("Cannot find frame for " + function);
}
if (variable != null) {
return fillFrameStorage(frame, variable.getName(), variable.getDataType(),
variable.getVariableStorage());
}
Address dynAddr = frame.getBasePointer().add(stackAddress.getOffset());
TraceCodeUnit unit = current.getTrace()
.getCodeManager()
.codeUnits()
.getContaining(current.getViewSnap(), dynAddr);
if (unit instanceof TraceData data && ListingUnwoundFrame.isFrame(data)) {
int offset = (int) dynAddr.subtract(data.getMinAddress());
TraceData comp = data.getComponentContaining(offset);
return fillCodeUnit(comp, null, null);
}
return fillCodeUnit(unit, null, null);
});
}
public CompletableFuture<VariableValueTable> fillReference(CodeUnit unit,
Address refAddress) {
if (refAddress.isMemoryAddress()) {
return fillMemory(unit.getProgram(), refAddress);
}
if (refAddress.isStackAddress() && unit instanceof Instruction ins) {
return fillStack(ins, refAddress);
}
return null;
}
public VariableValueTable fillRegisterNoFrame(Register register) {
TraceData data = eval.getRegisterUnit(register);
if (data != null) {
table.add(new NameRow(register.getName()));
table.add(new TypeRow(data.getDataType()));
IntegerRow intRow = IntegerRow.fromCodeUnit(data, current.getSnap());
table.add(intRow);
table.add(new ValueRow(data.getDefaultValueRepresentation(), intRow.state()));
return table;
}
// Just display the raw register value
table.add(new NameRow(register.getName()));
WatchValue raw = eval.getRawRegisterValue(register);
table.add(new IntegerRow(raw));
return table;
}
public CompletableFuture<VariableValueTable> fillRegister(Instruction ins,
Register register) {
Function function =
ins.getProgram().getFunctionManager().getFunctionContaining(ins.getMinAddress());
Variable variable =
function == null ? null : VariableValueUtils.findVariable(function, register);
return executeBackground(monitor -> {
UnwoundFrame<WatchValue> frame;
if (function == null) {
warnings.add("Instruction is not in a function. Using innermost frame.");
frame = VariableValueUtils.locateInnermost(tool, current);
}
else {
frame = eval.getStackFrame(function, warnings, monitor);
}
if (frame == null) {
warnings.add(
"Could not locate " + function + " in stack. Using innermost frame.");
return fillRegisterNoFrame(register);
}
if (variable != null) {
return fillFrameStorage(frame, variable.getName(), variable.getDataType(),
variable.getVariableStorage());
}
if (frame.getLevel() == 0) {
return fillRegisterNoFrame(register);
}
// Still raw register value, but this time it can be restored from stack
table.add(new NameRow(register.getName()));
if (!frame.isFake()) {
table.add(new FrameRow(frame));
}
WatchValue value = frame.getValue(register);
table.add(LocationRow.fromWatchValue(value, current.getPlatform().getLanguage()));
table.add(new IntegerRow(value));
return table;
});
}
public CompletableFuture<VariableValueTable> fillOperand(OperandFieldLocation opLoc,
Instruction ins) {
RefType refType = ins.getOperandRefType(opLoc.getOperandIndex());
if (refType.isFlow()) {
return null;
}
Object operand = ins.getDefaultOperandRepresentationList(opLoc.getOperandIndex())
.get(opLoc.getSubOperandIndex());
if (operand instanceof Register register) {
return fillRegister(ins, register);
}
Address refAddress = opLoc.getRefAddress();
if (operand instanceof Scalar scalar && refAddress != null) {
return fillReference(ins, refAddress);
}
if (operand instanceof Address address) {
return fillReference(ins, address);
}
return null;
}
public CompletableFuture<VariableValueTable> fillStorage(Function function, String name,
DataType type, VariableStorage storage, AddressSetView symbolStorage) {
return executeBackground(monitor -> {
UnwoundFrame<WatchValue> frame =
VariableValueUtils.requiresFrame(storage, symbolStorage)
? eval.getStackFrame(function, warnings, monitor)
: eval.getGlobalsFakeFrame();
if (frame == null) {
throw new UnwindException("Cannot find frame for " + function);
}
return fillFrameStorage(frame, name, type, storage);
});
}
public CompletableFuture<VariableValueTable> fillPcodeOp(Function function, String name,
DataType type, PcodeOp op, AddressSetView symbolStorage) {
return executeBackground(monitor -> {
UnwoundFrame<WatchValue> frame = VariableValueUtils.requiresFrame(op, symbolStorage)
? eval.getStackFrame(function, warnings, monitor)
: eval.getGlobalsFakeFrame();
if (frame == null) {
throw new UnwindException("Cannot find frame for " + function);
}
return fillFrameOp(frame, function.getProgram(), name, type, op, symbolStorage);
});
}
public VariableValueTable fillWatchValue(UnwoundFrame<WatchValue> frame, Address address,
DataType type, WatchValue value) {
table.add(LocationRow.fromWatchValue(value, current.getPlatform().getLanguage()));
if (value.address() != null && !value.address().isRegisterAddress()) {
table.add(new BytesRow(value));
}
table.add(new IntegerRow(value));
if (type != DataType.DEFAULT) {
String repr = eval.getRepresentation(frame, address, value, type);
table.add(new ValueRow(repr, value.state()));
}
return table;
}
public VariableValueTable fillFrameStorage(UnwoundFrame<WatchValue> frame, String name,
DataType type, VariableStorage storage) {
table.add(new NameRow(name));
if (!frame.isFake()) {
table.add(new FrameRow(frame));
}
table.add(new StorageRow(storage));
table.add(new TypeRow(type));
WatchValue value = frame.getValue(storage);
return fillWatchValue(frame, storage.getMinAddress(), type, value);
}
public VariableValueTable fillFrameOp(UnwoundFrame<WatchValue> frame, Program program,
String name, DataType type, PcodeOp op, AddressSetView symbolStorage) {
table.add(new NameRow(name));
if (!frame.isFake()) {
table.add(new FrameRow(frame));
}
table.add(new TypeRow(type));
WatchValue value = frame.evaluate(program, op, symbolStorage);
// TODO: What if the type is dynamic with non-fixed size?
if (type.getLength() != value.length()) {
value = frame.zext(value, type.getLength());
}
return fillWatchValue(frame, op.getOutput().getAddress(), type, value);
}
public CompletableFuture<VariableValueTable> fillHighVariable(HighVariable hVar,
String name,
AddressSetView symbolStorage) {
Function function = hVar.getHighFunction().getFunction();
VariableStorage storage = VariableValueUtils.fabricateStorage(hVar);
if (storage.isUniqueStorage()) {
table.add(new NameRow(name));
table.add(new StorageRow(storage));
table.add(new ValueRow("(Unique)", TraceMemoryState.KNOWN));
return CompletableFuture.completedFuture(table);
}
return fillStorage(function, name, hVar.getDataType(), storage, symbolStorage);
}
public CompletableFuture<VariableValueTable> fillHighVariable(HighVariable hVar,
AddressSetView symbolStorage) {
return fillHighVariable(hVar, hVar.getName(), symbolStorage);
}
public CompletableFuture<VariableValueTable> fillComponent(ClangFieldToken token,
AddressSetView symbolStorage) {
Function function = token.getClangFunction().getHighFunction().getFunction();
Program program = function.getProgram();
PcodeOp op = token.getPcodeOp();
Varnode vn = op.getOutput();
HighVariable hVar = vn.getHigh();
DataType type = DataTypeDecompilerHover.getFieldDataType(token);
if (hVar.getDataType().isEquivalent(new PointerDataType(type))) {
op = VariableValueUtils.findDeref(program.getAddressFactory(), vn);
}
return fillPcodeOp(function, token.getText(), type, op, symbolStorage);
}
public CompletableFuture<VariableValueTable> fillComposite(HighSymbol hSym,
HighVariable hVar, AddressSetView symbolStorage) {
return fillStorage(hVar.getHighFunction().getFunction(), hSym.getName(),
hSym.getDataType(), hSym.getStorage(), symbolStorage);
}
public CompletableFuture<VariableValueTable> fillToken(ClangToken token) {
if (token == null) {
return null;
}
/**
* I can't get just the expression tree here, except as p-code AST, which doesn't seem
* to include token info. A line should contain the full expression, though. I'll grab
* the symbols' storage from it and ensure my evaluation recurses until it hits those
* symbols.
*/
AddressSet symbolStorage =
VariableValueUtils.collectSymbolStorage(token.getLineParent());
if (token instanceof ClangFieldToken fieldToken) {
return fillComponent(fieldToken, symbolStorage);
}
HighVariable hVar = token.getHighVariable();
if (hVar == null) {
return null;
}
HighSymbol hSym = hVar.getSymbol();
if (hSym == null) {
// This is apparently the case for literals.
return null;
}
VariableStorage storage = hSym.getStorage();
String name = hVar.getName();
if (name == null) {
name = hSym.getName();
}
Varnode representative = hVar.getRepresentative();
if (!storage.contains(representative.getAddress())) {
// I'm not sure this can ever happen....
return fillHighVariable(hVar, symbolStorage);
}
if (Arrays.asList(storage.getVarnodes()).equals(List.of(representative))) {
// The var is the symbol
return fillHighVariable(hVar, symbolStorage);
}
// Presumably, there's some component path from symbol to high var
return fillComposite(hSym, hVar, symbolStorage);
}
public CompletableFuture<VariableValueTable> fillVariable(Variable variable) {
Function function = variable.getFunction();
return executeBackground(monitor -> {
UnwoundFrame<WatchValue> frame = eval.getStackFrame(function, warnings, monitor);
if (frame == null) {
throw new UnwindException("Cannot find frame for " + function);
}
return fillFrameStorage(frame, variable.getName(), variable.getDataType(),
variable.getVariableStorage());
});
}
}
public CompletableFuture<VariableValueTable> fillVariableValueTable(VariableValueTable table,
ProgramLocation programLocation, DebuggerCoordinates current,
FieldLocation fieldLocation, Field field, List<String> warnings) {
if (traceManager == null || mappingService == null) {
return null;
}
VariableEvaluator eval;
synchronized (cachedEvaluators) {
eval = cachedEvaluators.computeIfAbsent(current, c -> new VariableEvaluator(tool, c));
}
TableFiller filler = new TableFiller(table, tool, current, eval, warnings);
if (field instanceof ClangTextField clangField) {
return filler.fillToken(clangField.getToken(fieldLocation));
}
if (programLocation == null) {
return null;
}
Address refAddress = programLocation.getRefAddress();
CodeUnit unit = programLocation.getProgram()
.getListing()
.getCodeUnitContaining(programLocation.getAddress());
if (programLocation instanceof OperandFieldLocation opLoc &&
unit instanceof Instruction ins) {
return filler.fillOperand(opLoc, ins);
}
if (programLocation instanceof OperandFieldLocation && refAddress != null &&
refAddress.isMemoryAddress()) {
return filler.fillReference(unit, refAddress);
}
if (programLocation instanceof VariableLocation varLoc) {
return filler.fillVariable(varLoc.getVariable());
}
return null;
}
@Override
public JComponent getHoverComponent(Program program, ProgramLocation programLocation,
FieldLocation fieldLocation, Field field) {
if (!enabled || traceManager == null) {
return null;
}
VariableValueTable table = new VariableValueTable();
List<String> warnings = new ArrayList<>();
CompletableFuture<VariableValueTable> future;
try {
future = fillVariableValueTable(table, programLocation,
traceManager.getCurrent(), fieldLocation, field, warnings);
}
catch (Exception e) {
table.add(new ErrorRow(e));
return createTooltipComponent("<html>" + table.toHtml());
}
if (future == null) {
return null;
}
if (!future.isDone()) {
table.add(new StatusRow("In Progress"));
}
JComponent component = createTooltipComponent("<html>" + table.toHtml());
if (!(component instanceof JToolTip tooltip)) {
throw new AssertionError("Expected a JToolTip");
}
future.handleAsync((__, ex) -> {
table.remove(RowKey.STATUS);
if (ex != null) {
table.add(new ErrorRow(AsyncUtils.unwrapThrowable(ex)));
}
else {
table.add(new WarningsRow(warnings));
}
tooltip.setTipText("<html>" + table.toHtml());
Window window = SwingUtilities.getWindowAncestor(tooltip);
if (window != null) {
window.pack();
} // else, the computation completed before tooltip was returned
return null;
}, SwingExecutorService.MAYBE_NOW);
return tooltip;
}
public void traceClosed(Trace trace) {
synchronized (cachedEvaluators) {
cachedEvaluators.keySet().removeIf(coords -> coords.getTrace() == trace);
}
}
}

View file

@ -0,0 +1,647 @@
/* ###
* 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.stack.vars;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import generic.theme.GColor;
import ghidra.app.plugin.core.debug.stack.UnwoundFrame;
import ghidra.pcode.exec.DebuggerPcodeUtils.PrettyBytes;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
import ghidra.pcode.exec.ValueLocation;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.pcode.Varnode;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.listing.TraceCodeUnit;
import ghidra.trace.model.memory.*;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.HTMLUtilities;
import ghidra.util.exception.InvalidInputException;
/**
* A row to be displayed in a variable value hover's table
*/
public interface VariableValueRow {
// TODO: Colors specific to hovers?
GColor COLOR_ERROR = new GColor("color.fg.error");
GColor COLOR_STALE = new GColor("color.fg.debugger.value.stale");
/**
* Perform the simplest styling of the object
*
* <p>
* This merely invokes the object's {@link Object#toString()} method and escapes its. If it's
* null, it will render "None" is the error color.
*
* @param obj the object, possibly null
* @return the HTML-styled string
*/
static String styleSimple(Object obj) {
return obj == null ? htmlFg(COLOR_ERROR, "None") : HTMLUtilities.escapeHTML(obj.toString());
}
/**
* Style a given string according to the given memory state
*
* <p>
* This renders stale ({@link TraceMemoryState#UNKNOWN}) values in the stale color, usually
* gray.
*
* @param state the state
* @param str the HTML string
* @return the HTML-styled string
*/
static String styleState(TraceMemoryState state, String str) {
if (state == TraceMemoryState.KNOWN) {
return str;
}
return "<font color='" + COLOR_STALE.toHexString() + "'>" + str + "</font>";
}
/**
* Escape and style the given text in the given color
*
* @param color the color
* @param text the text
* @return the HTML-styled string
*/
static String htmlFg(GColor color, String text) {
return "<font color='" + color.toHexString() + "'>" + HTMLUtilities.escapeHTML(text) +
"</font>";
}
/**
* A key naming a given row type
*
* <p>
* This ensures the rows always appear in conventional order, and that there is only one of
* each.
*/
enum RowKey {
NAME("Name"),
FRAME("Frame"),
STORAGE("Storage"),
TYPE("Type"),
INSTRUCTION("Instruction"),
LOCATION("Location"),
BYTES("Bytes"),
INTEGER("Integer"),
VALUE("Value"),
STATUS("Status"),
WARNINGS("Warnings"),
ERROR("Error"),
;
private final String display;
RowKey(String display) {
this.display = display;
}
@Override
public String toString() {
return display;
}
}
/**
* Get the key for this row type
*
* @return the key
*/
RowKey key();
/**
* Render the key for display in diagnostics
*
* @return the the key as a string
*/
default String keyToSimpleString() {
return key().toString();
}
/**
* Render the key for display in the table
*
* @return the key as an HTML string
*/
default String keyToHtml() {
return HTMLUtilities.escapeHTML(key() + ":");
}
/**
* Render the value for display in diagnostics
*
* @return the value as a string
*/
String valueToSimpleString();
/**
* Render the value for display in the table
*
* @return the value as an HTML string
*/
String valueToHtml();
/**
* Render this complete row for display in diagnostics
*
* @return the row as a string
*/
default String toSimpleString() {
return String.format("%s: %s", keyToSimpleString(), valueToSimpleString());
}
/**
* Render this complete row for display in the table
*
* @return the row as an HTMl string
*/
default String toHtml() {
return String.format("<tr><td valign='top'><b>%s</b></td><td><tt>%s</tt></td></tr>",
keyToHtml(), valueToHtml());
}
/**
* A row for the variable's name
*/
record NameRow(String name) implements VariableValueRow {
@Override
public RowKey key() {
return RowKey.NAME;
}
@Override
public String valueToHtml() {
return styleSimple(name);
}
@Override
public String valueToSimpleString() {
return name;
}
}
/**
* A row for the frame used to compute the location and value
*/
record FrameRow(UnwoundFrame<?> frame) implements VariableValueRow {
@Override
public RowKey key() {
return RowKey.FRAME;
}
@Override
public String valueToHtml() {
return styleSimple(frame.getDescription());
}
@Override
public String valueToSimpleString() {
return frame.getDescription();
}
}
/**
* A row for the variable's statically-defined storage
*/
record StorageRow(VariableStorage storage) implements VariableValueRow {
public static StorageRow fromCodeUnit(CodeUnit unit) {
try {
return new StorageRow(new VariableStorage(unit.getProgram(),
new Varnode(unit.getMinAddress(), unit.getLength())));
}
catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
@Override
public RowKey key() {
return RowKey.STORAGE;
}
@Override
public String valueToHtml() {
return styleSimple(storage);
}
@Override
public String valueToSimpleString() {
return storage.toString();
}
}
/**
* A row for the variable's type
*/
record TypeRow(DataType type) implements VariableValueRow {
@Override
public RowKey key() {
return RowKey.TYPE;
}
@Override
public String valueToHtml() {
return styleSimple(type.getDisplayName());
}
@Override
public String valueToSimpleString() {
return type.getDisplayName();
}
}
/**
* If an operand refers to code, a row for the target instruction
*/
record InstructionRow(Instruction instruction) implements VariableValueRow {
@Override
public RowKey key() {
return RowKey.INSTRUCTION;
}
@Override
public String valueToHtml() {
return styleSimple(instruction);
}
@Override
public String valueToSimpleString() {
return instruction.toString();
}
}
/**
* A row for the variable's dynamic location
*/
record LocationRow(String locString) implements VariableValueRow {
/**
* Create a row from the given range
*
* @param range the range
* @return the row
*/
public static LocationRow fromRange(AddressRange range) {
return new LocationRow(
String.format("%s:%d", range.getMinAddress(), range.getLength()));
}
/**
* Create a row from the given code unit
*
* @param unit the unit
* @return the row
*/
public static LocationRow fromCodeUnit(CodeUnit unit) {
return new LocationRow(
String.format("%s:%d", unit.getMinAddress(), unit.getLength()));
}
/***
* Create a row from the given watch value
*
* @param value the value
* @param language the language (for register name substitution)
* @return the row
*/
public static LocationRow fromWatchValue(WatchValue value, Language language) {
ValueLocation loc = value.location();
if (loc == null || loc.isEmpty()) {
return new LocationRow(null);
}
return new LocationRow(loc.toString(language));
}
@Override
public RowKey key() {
return RowKey.LOCATION;
}
@Override
public String valueToHtml() {
return styleSimple(locString);
}
@Override
public String valueToSimpleString() {
return locString == null ? "None" : locString;
}
}
/**
* Compute the memory state of a given range
*
* <p>
* If any part of the range is not {@link TraceMemoryState#KNOWN} the result is
* {@link TraceMemoryState#UNKNOWN}.
*
* @param trace the trace
* @param space the thread, frame level, and address space
* @param range the address range
* @param snap the snapshot key
* @return the composite state
*/
static TraceMemoryState computeState(Trace trace, TraceAddressSpace space, AddressRange range,
long snap) {
TraceMemoryManager mem = trace.getMemoryManager();
TraceMemoryOperations ops;
if (space != null && space.getAddressSpace().isRegisterSpace()) {
ops = mem.getMemoryRegisterSpace(space.getThread(), space.getFrameLevel(), false);
}
else {
ops = mem;
}
return ops != null && ops.isKnown(snap, range)
? TraceMemoryState.KNOWN
: TraceMemoryState.UNKNOWN;
}
/**
* Compute the memory state of a given code unit
*
* @param unit the code unit
* @param snap the snapshot key
* @return the composite state.
* @see #computeState(Trace, TraceAddressSpace, AddressRange, long)
*/
static TraceMemoryState computeState(TraceCodeUnit unit, long snap) {
return computeState(unit.getTrace(), unit.getTraceSpace(), unit.getRange(), snap);
}
/**
* A row to display the bytes in the variable
*/
record BytesRow(PrettyBytes bytes, TraceMemoryState state) implements VariableValueRow {
/**
* Create a row from a given range
*
* @param platform the platform (for trace memory and language)
* @param range the range
* @param snap the snapshot key
* @return the row
*/
public static BytesRow fromRange(TracePlatform platform, AddressRange range, long snap) {
long size = range.getLength();
ByteBuffer buf = ByteBuffer.allocate((int) size);
Trace trace = platform.getTrace();
if (size != trace.getMemoryManager().getViewBytes(snap, range.getMinAddress(), buf)) {
throw new AssertionError(new MemoryAccessException("Could not read bytes"));
}
return new BytesRow(
new PrettyBytes(platform.getLanguage().isBigEndian(), buf.array()),
computeState(trace, null, range, snap));
}
/**
* Create a row from a given code unit
*
* @param unit unit
* @param snap the snapshot key
* @return the row
*/
public static BytesRow fromCodeUnit(TraceCodeUnit unit, long snap) {
try {
return new BytesRow(new PrettyBytes(unit.isBigEndian(), unit.getBytes()),
computeState(unit, snap));
}
catch (MemoryAccessException e) {
throw new AssertionError(e);
}
}
/**
* Create a row from a given watch value
*
* @param value the value
*/
public BytesRow(WatchValue value) {
this(value.bytes(), value.state());
}
@Override
public RowKey key() {
return RowKey.BYTES;
}
@Override
public String valueToHtml() {
return styleState(state, bytes.toBytesString().replace("\n", "<br>"));
}
@Override
public String valueToSimpleString() {
return String.format("(%s) %s", state, bytes.toBytesString());
}
}
/**
* A row to display a variable's value as an integer in various formats
*/
record IntegerRow(PrettyBytes bytes, TraceMemoryState state) implements VariableValueRow {
/**
* Create a row from a given code unit
*
* @param unit the unit
* @param snap the snapshot key
* @return the row
*/
public static IntegerRow fromCodeUnit(TraceCodeUnit unit, long snap) {
try {
return new IntegerRow(new PrettyBytes(unit.isBigEndian(), unit.getBytes()),
computeState(unit, snap));
}
catch (MemoryAccessException e) {
throw new AssertionError(e);
}
}
/**
* Create a row from the given {@link BytesRow}
*
* @param bytes the bytes row
*/
public IntegerRow(BytesRow bytes) {
this(bytes.bytes, bytes.state);
}
/**
* Create a row from the given watch value
*
* @param value the value
*/
public IntegerRow(WatchValue value) {
this(value.bytes(), value.state());
}
@Override
public RowKey key() {
return RowKey.INTEGER;
}
@Override
public String toHtml() {
if (bytes.length() > 16) {
return "";
}
return VariableValueRow.super.toHtml();
}
@Override
public String valueToHtml() {
return styleState(state, bytes.collectDisplays().replace("\n", "<br>"));
}
@Override
public String valueToSimpleString() {
return String.format("(%s) %s", state, bytes.collectDisplays());
}
}
/**
* A row to display the variable's value in its type's default representation
*/
record ValueRow(String value, TraceMemoryState state) implements VariableValueRow {
@Override
public RowKey key() {
return RowKey.VALUE;
}
@Override
public String valueToHtml() {
return styleState(state, HTMLUtilities.escapeHTML(value));
}
@Override
public String valueToSimpleString() {
return String.format("(%s) %s", state, value);
}
}
/**
* A row to indicate the computation status, in case it takes a moment
*/
record StatusRow(String status) implements VariableValueRow {
@Override
public RowKey key() {
return RowKey.STATUS;
}
@Override
public String valueToHtml() {
return String.format("<em>%s</em>", HTMLUtilities.escapeHTML(status.toString()));
}
@Override
public String valueToSimpleString() {
return status.toString();
}
}
/**
* A row to display the warnings encountered while unwinding the frame used to evaluate the
* variable
*/
record WarningsRow(String warnings) implements VariableValueRow {
/**
* Create a row from the given list of warnings
*
* @param warnings the warnings
*/
public WarningsRow(List<String> warnings) {
this(warnings.stream()
.map(String::trim)
.filter(w -> !w.isBlank())
.collect(Collectors.joining("\n")));
}
@Override
public RowKey key() {
return RowKey.WARNINGS;
}
@Override
public String keyToHtml() {
return htmlFg(COLOR_ERROR, key() + ":");
}
@Override
public String valueToHtml() {
String[] split = warnings.split("\n");
String formatted = Stream.of(split)
.map(w -> String.format("<li>%s</li>", HTMLUtilities.escapeHTML(w)))
.collect(Collectors.joining("\n "));
return String.format("""
<ul>
%s
</ul>
""", formatted);
}
@Override
public String valueToSimpleString() {
return warnings;
}
@Override
public String toHtml() {
if (warnings.isBlank()) {
return "";
}
return String.format("<tr><td valign='top'><b>%s</b></td><td>%s</td></tr>",
keyToHtml(), valueToHtml());
}
}
/**
* A row to display an error in case the table is incomplete
*/
record ErrorRow(Throwable error) implements VariableValueRow {
@Override
public RowKey key() {
return RowKey.ERROR;
}
@Override
public String keyToHtml() {
return htmlFg(COLOR_ERROR, key() + ":");
}
@Override
public String valueToHtml() {
return styleSimple(error);
}
@Override
public String valueToSimpleString() {
return error.toString();
}
@Override
public String toHtml() {
return String.format("<tr><td valign='top'><b>%s</b></td><td>%s</td></tr>",
keyToHtml(), valueToHtml());
}
}
}

View file

@ -0,0 +1,115 @@
/* ###
* 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.stack.vars;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueRow.RowKey;
/**
* A table for display in a variable value hover
*/
public class VariableValueTable {
private final Map<RowKey, VariableValueRow> rows = new TreeMap<>();
/**
* Add a row to the table
* <p>
* At most one of each row type can be present. Adding a row whose type already exists will
* remove the old row of the same type.
*
* @param row
*/
public void add(VariableValueRow row) {
synchronized (rows) {
rows.put(row.key(), row);
}
}
@Override
public String toString() {
synchronized (rows) {
return String.format("""
<%s:
%s
>
""",
getClass().getSimpleName(),
rows.values()
.stream()
.map(VariableValueRow::toSimpleString)
.collect(Collectors.joining("\n ")));
}
}
/**
* Render the table as HTML for display in the GUI
*
* <p>
* The rows are always ordered as in {@link RowKey}.
*
* @return the HTML string
*/
public String toHtml() {
synchronized (rows) {
return String.format("""
<table>
%s
</table>
""",
rows.values()
.stream()
.map(VariableValueRow::toHtml)
.collect(Collectors.joining("\n")));
}
}
/**
* Count the number of rows
*
* @return the count
*/
public int getNumRows() {
synchronized (rows) {
return rows.size();
}
}
/**
* Get the row of the given type
*
* @param key the key / type
* @return the row, or null
*/
public VariableValueRow get(RowKey key) {
synchronized (rows) {
return rows.get(key);
}
}
/**
* Remove the row of the given type
*
* @param key the key / type
*/
public void remove(RowKey key) {
synchronized (rows) {
rows.remove(key);
}
}
}

View file

@ -0,0 +1,847 @@
/* ###
* 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.stack.vars;
import java.util.*;
import ghidra.app.decompiler.ClangLine;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.stack.*;
import ghidra.docking.settings.Settings;
import ghidra.docking.settings.SettingsDefinition;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.eval.AbstractVarnodeEvaluator;
import ghidra.pcode.exec.DebuggerPcodeUtils;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValuePcodeExecutorState;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.opbehavior.BinaryOpBehavior;
import ghidra.pcode.opbehavior.UnaryOpBehavior;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.ByteMemBufferImpl;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.pcode.*;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.MathUtilities;
import ghidra.util.Msg;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
/**
* Various utilities for evaluating statically-defined variables in the context of a dynamic trace.
*/
public enum VariableValueUtils {
;
/**
* An "evaluator" which simply determines whether actual evaluation will require a frame for
* context
*/
private static final class RequiresFrameEvaluator extends AbstractVarnodeEvaluator<Boolean> {
private final AddressSetView symbolStorage;
private RequiresFrameEvaluator(AddressSetView symbolStorage) {
this.symbolStorage = symbolStorage;
}
@Override
protected boolean isLeaf(Varnode vn) {
return vn.isConstant() ||
symbolStorage.contains(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1));
}
@Override
protected Address applyBase(long offset) {
throw new AssertionError();
}
@Override
protected Boolean evaluateConstant(long value, int size) {
return false;
}
@Override
protected Boolean evaluateRegister(Address address, int size) {
return true;
}
@Override
protected Boolean evaluateStack(long offset, int size) {
return true;
}
@Override
protected Boolean evaluateMemory(Address address, int size) {
return false;
}
@Override
protected Boolean evaluateUnique(long offset, int size) {
/**
* Generally speaking, this getting called is bad. We'll "let it go" here and the error
* should surface in actual evaluation.
*/
return false;
}
@Override
protected Boolean evaluateAbstract(Program program, AddressSpace space, Boolean offset,
int size, Map<Varnode, Boolean> already) {
/**
* This generally happens for dereferences. The evaluator will already have determined
* if computing the address requires a frame, which we should just echo back. Neither
* the location of the target nor its value has any bearing on whether or not a frame is
* required.
*/
return offset;
}
@Override
protected Boolean evaluateUnaryOp(Program program, PcodeOp op, UnaryOpBehavior unOp,
Map<Varnode, Boolean> already) {
return evaluateVarnode(program, op.getInput(0), already);
}
@Override
protected Boolean evaluateBinaryOp(Program program, PcodeOp op, BinaryOpBehavior binOp,
Map<Varnode, Boolean> already) {
return evaluateVarnode(program, op.getInput(0), already) ||
evaluateVarnode(program, op.getInput(1), already);
}
@Override
protected Boolean evaluateLoad(Program program, PcodeOp op,
Map<Varnode, Boolean> already) {
return evaluateVarnode(program, op.getInput(1), already);
}
@Override
protected Boolean evaluatePtrAdd(Program program, PcodeOp op,
Map<Varnode, Boolean> already) {
// Third input is a constant, according to pcoderef.xml
return evaluateBinaryOp(program, op, null, already);
}
@Override
protected Boolean evaluatePtrSub(Program program, PcodeOp op,
Map<Varnode, Boolean> already) {
return evaluateBinaryOp(program, op, null, already);
}
@Override
protected Boolean catenate(int total, Boolean value, Boolean piece, int size) {
return value || piece;
}
@Override
public Boolean evaluateStorage(VariableStorage storage) {
return evaluateStorage(storage, false);
}
}
/**
* A settings that provides the given space as the default for pointers
*/
static class DefaultSpaceSettings implements Settings {
final Settings delegate;
final AddressSpace space;
public DefaultSpaceSettings(Settings delegate, AddressSpace space) {
this.delegate = delegate;
this.space = space;
}
@Override
public boolean isChangeAllowed(SettingsDefinition settingsDefinition) {
return delegate.isChangeAllowed(settingsDefinition);
}
@Override
public Long getLong(String name) {
return delegate.getLong(name);
}
@Override
public String getString(String name) {
if (AddressSpaceSettingsDefinition.DEF.getStorageKey().equals(name)) {
return space.getName();
}
return delegate.getString(name);
}
@Override
public Object getValue(String name) {
if (AddressSpaceSettingsDefinition.DEF.getStorageKey().equals(name)) {
return space.getName();
}
return delegate.getValue(name);
}
@Override
public void setLong(String name, long value) {
throw new UnsupportedOperationException();
}
@Override
public void setString(String name, String value) {
throw new UnsupportedOperationException();
}
@Override
public void setValue(String name, Object value) {
throw new UnsupportedOperationException();
}
@Override
public void clearSetting(String name) {
throw new UnsupportedOperationException();
}
@Override
public void clearAllSettings() {
throw new UnsupportedOperationException();
}
@Override
public String[] getNames() {
return delegate.getNames();
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public Settings getDefaultSettings() {
return delegate.getDefaultSettings();
}
}
/**
* Compute the address range where annotated frames would be expected in the listing
*
* @param coordinates the coordinates
* @return the range, usually from stack pointer to the end of the stack segment
*/
public static AddressRange computeFrameSearchRange(DebuggerCoordinates coordinates) {
TraceThread thread = coordinates.getThread();
if (thread == null) {
return null;
}
Trace trace = thread.getTrace();
long viewSnap = coordinates.getViewSnap();
TraceMemoryManager mem = trace.getMemoryManager();
TracePlatform platform = coordinates.getPlatform();
CompilerSpec cSpec = platform.getCompilerSpec();
Register sp = cSpec.getStackPointer();
TraceMemorySpace regs = mem.getMemoryRegisterSpace(thread, 0, false);
RegisterValue spRV = regs.getValue(platform, viewSnap, sp);
Address spVal = cSpec.getStackBaseSpace().getAddress(spRV.getUnsignedValue().longValue());
Address max;
TraceMemoryRegion stackRegion = mem.getRegionContaining(coordinates.getSnap(), spVal);
if (stackRegion != null) {
max = stackRegion.getMaxAddress();
}
else {
long toMax = spVal.getAddressSpace().getMaxAddress().subtract(spVal);
max = spVal.add(MathUtilities.unsignedMin(4095, toMax));
}
return new AddressRangeImpl(spVal, max);
}
/**
* Find the innermost frame for the given coordinates
*
* @param tool the tool
* @param coordinates the coordinates
* @return the frame, or null
*/
public static ListingUnwoundFrame locateInnermost(PluginTool tool,
DebuggerCoordinates coordinates) {
AddressRange range = computeFrameSearchRange(coordinates);
if (range == null) {
return null;
}
// TODO: Positive stack growth?
for (TraceData data : coordinates.getTrace()
.getCodeManager()
.definedData()
.get(coordinates.getViewSnap(), range, true)) {
try {
return new ListingUnwoundFrame(tool, coordinates, data);
}
catch (UnwindException e) {
Msg.warn(VariableValueUtils.class, "Skipping frame " + data + ". " + e);
// Just try the next
}
}
return null;
}
/**
* Locate an already unwound frame in the listing at the given coordinates
*
* @param tool the tool for context, especially for mappings to static programs
* @param coordinates the coordinates to search. Note that recursive calls are distinguished by
* the coordinates' frame level, though unwinding starts at frame 0.
* @param function the function the allocated the desired frame / call record
* @see AnalysisUnwoundFrame#applyToListing(int, TaskMonitor)
* @return the frame or null
*/
public static ListingUnwoundFrame locateFrame(PluginTool tool, DebuggerCoordinates coordinates,
Function function) {
int minLevel = coordinates.getFrame();
AddressRange range = computeFrameSearchRange(coordinates);
if (range == null) {
return null;
}
// TODO: Positive stack growth?
for (TraceData data : coordinates.getTrace()
.getCodeManager()
.definedData()
.get(coordinates.getViewSnap(), range, true)) {
try {
ListingUnwoundFrame frame = new ListingUnwoundFrame(tool, coordinates, data);
minLevel--;
if (minLevel < 0 && frame.getFunction() == function) {
return frame;
}
}
catch (UnwindException e) {
Msg.warn(VariableValueUtils.class, "Skipping frame " + data + ". " + e);
// Just try the next
}
}
Msg.info(VariableValueUtils.class, "Cannot find frame for function " + function);
return null;
}
/**
* Check if evaluation of the given storage will require a frame
*
* @param storage the storage to evaluate
* @param symbolStorage the leaves of evaluation, usually storage used by symbols in scope. See
* {@link #collectSymbolStorage(ClangLine)}
* @return true if a frame is required, false otherwise
*/
public static boolean requiresFrame(VariableStorage storage, AddressSetView symbolStorage) {
return new RequiresFrameEvaluator(symbolStorage).evaluateStorage(storage);
}
/**
* Check if evaluation of the given p-code op will require a frame
*
* @param op the op whose output to evaluation
* @param symbolStorage the leaves of evaluation, usually storage used by symbols in scope. See
* {@link #collectSymbolStorage(ClangLine)}
* @return true if a frame is required, false otherwise
*/
public static boolean requiresFrame(PcodeOp op, AddressSetView symbolStorage) {
return new RequiresFrameEvaluator(symbolStorage).evaluateOp(null, op);
}
/**
* Get the program counter for the given thread's innermost frame using its {@link TraceStack}
*
* <p>
* This will prefer the program counter in the {@link TraceStackFrame}. If that's not available,
* it will use the value of the program counter register from the thread's register bank for
* frame 0.
*
* @param platform the platform
* @param thread the thread
* @param snap the snapshot key
* @return the address
*/
public static Address getProgramCounterFromStack(TracePlatform platform, TraceThread thread,
long snap) {
TraceStack stack = thread.getTrace().getStackManager().getStack(thread, snap, false);
if (stack == null) {
return null;
}
TraceStackFrame frame = stack.getFrame(0, false);
if (frame == null) {
return null;
}
return frame.getProgramCounter(snap);
}
/**
* Get the program counter for the given thread's innermost frame using its
* {@link TraceMemorySpace}, i.e., registers
*
* @param platform the platform
* @param thread the thread
* @param snap the snapshot key
* @return the address
*/
public static Address getProgramCounterFromRegisters(TracePlatform platform, TraceThread thread,
long snap) {
TraceMemorySpace regs =
thread.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, false);
if (regs == null) {
return null;
}
RegisterValue value =
regs.getValue(platform, snap, platform.getLanguage().getProgramCounter());
return platform.getLanguage()
.getDefaultSpace()
.getAddress(value.getUnsignedValue().longValue());
}
/**
* Get the program counter from the innermost frame of the given thread's stack
*
* <p>
* This will prefer the program counter in the {@link TraceStackFrame}. If that's not available,
* it will use the value of the program counter register from the thread's register bank for
* frame 0.
*
* @param platform the platform
* @param thread the thread
* @param snap the snapshot key
* @return the address
*/
public static Address getProgramCounter(TracePlatform platform, TraceThread thread, long snap) {
Address pcFromStack = getProgramCounterFromStack(platform, thread, snap);
if (pcFromStack != null) {
return pcFromStack;
}
return getProgramCounterFromRegisters(platform, thread, snap);
}
/**
* Check if the unwound frames annotated in the listing are "fresh"
*
* <p>
* It can be difficult to tell. The heuristic we use is if the PC of the innermost frame agrees
* with the PC recorded for the current thread.
*
* @param tool the tool
* @param coordinates the coordinates
* @return true if the unwind appears fresh
*/
public static boolean hasFreshUnwind(PluginTool tool, DebuggerCoordinates coordinates) {
ListingUnwoundFrame innermost = locateInnermost(tool, coordinates);
if (innermost == null || !Objects.equals(innermost.getProgramCounter(),
getProgramCounter(coordinates.getPlatform(), coordinates.getThread(),
coordinates.getViewSnap()))) {
return false;
}
return true;
}
/**
* Find the function's variable whose storage is exactly the given register
*
* @param function the function
* @param register the register
* @return the variable, or null
*/
public static Variable findVariable(Function function, Register register) {
for (Variable variable : function.getAllVariables()) {
if (variable.isRegisterVariable() && variable.getRegister() == register) {
return variable;
}
}
return null;
}
/**
* Find the fuction's variable whose storage contains the given stack offset
*
* @param function the function
* @param stackAddress the stack offset
* @return the variable, or null
*/
public static Variable findStackVariable(Function function, Address stackAddress) {
if (!stackAddress.isStackAddress()) {
throw new IllegalArgumentException("stackAddress is not a stack address");
}
return function.getStackFrame().getVariableContaining((int) stackAddress.getOffset());
}
/**
* Convert the given varnode to an address range
*
* @param vn the varnode
* @return the address range
*/
public static AddressRange rangeFromVarnode(Varnode vn) {
return new AddressRangeImpl(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1));
}
/**
* Check if the given address set completely contains the given varnode
*
* @param set the set
* @param vn the varnode
* @return true if completely contained
*/
public static boolean containsVarnode(AddressSetView set, Varnode vn) {
return set.contains(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1));
}
/**
* Collect the addresses used for storage by any symbol in the given line of decompiled C code
*
* <p>
* It's not the greatest, but an variable to be evaluated should only be expressed in terms of
* symbols on the same line (at least by the decompiler's definition, wrapping shouldn't count
* against us). This can be used to determine where evaluation should cease descending into
* defining p-code ops. See {@link #requiresFrame(PcodeOp, AddressSetView)}, and
* {@link UnwoundFrame#evaluate(Program, PcodeOp, AddressSetView)}.
*
* @param line the line
* @return the address set
*/
public static AddressSet collectSymbolStorage(ClangLine line) {
AddressSet storage = new AddressSet();
for (ClangToken tok : line.getAllTokens()) {
HighVariable hVar = tok.getHighVariable();
if (hVar == null) {
continue;
}
HighSymbol hSym = hVar.getSymbol();
if (hSym == null) {
continue;
}
for (Varnode vn : hSym.getStorage().getVarnodes()) {
storage.add(rangeFromVarnode(vn));
}
}
return storage;
}
/**
* Find the descendent that dereferences this given varnode
*
* <p>
* This searches only one hop for a {@link PcodeOp#LOAD} or {@link PcodeOp#STORE}. If it find a
* load, it simply returns it. If it find a store, it generates the inverse load and returns it.
* This latter behavior ensures we can evaluate the lval or a decompiled assignment statement.
*
* @param factory an address factory for generating unique varnodes
* @param vn the varnode for which a dereference is expected
* @return the dereference, as a {@link PcodeOp#LOAD}
*/
public static PcodeOp findDeref(AddressFactory factory, Varnode vn) {
Iterable<PcodeOp> it = (Iterable<PcodeOp>) () -> vn.getDescendants();
for (PcodeOp desc : it) {
if (desc.getOpcode() == PcodeOp.LOAD) {
return desc;
}
}
for (PcodeOp desc : it) {
if (desc.getOpcode() == PcodeOp.STORE) {
PcodeOpAST op = new PcodeOpAST(desc.getSeqnum(), PcodeOp.LOAD, 2);
op.setInput(desc.getInput(0), 0);
op.setInput(desc.getInput(1), 1);
VarnodeAST out = new VarnodeAST(factory.getUniqueSpace().getAddress(1L << 31),
desc.getInput(2).getSize(), 0xf00d);
op.setOutput(out);
out.setDef(op);
return op;
}
}
return null;
}
/**
* Find an instance that occurs in the variable's symbol's storage
*
* <p>
* This goal is to find a stable location for evaluating the high variable, rather than some
* temporary register or worse unique location. If no satisfying instance is found, it defaults
* to the variable's representative instance.
*
* @param hVar the high variable
* @return the instance found
*/
public static Varnode getInstanceInSymbolStorage(HighVariable hVar) {
Varnode representative = hVar.getRepresentative();
HighSymbol hSym = hVar.getSymbol();
if (hSym == null) {
return representative;
}
AddressSet storageSet = new AddressSet();
for (Varnode vn : hSym.getStorage().getVarnodes()) {
storageSet.add(rangeFromVarnode(vn));
}
if (containsVarnode(storageSet, representative)) {
return representative;
}
for (Varnode instance : hVar.getInstances()) {
if (containsVarnode(storageSet, instance)) {
return instance;
}
}
return representative;
}
/**
* Create a {@link VariableStorage} object for the given high variable
*
* <p>
* This is not necessarily the same as the variable's symbol's storage. In fact, if the variable
* represents a field, it is likely a subset of the symbol's storage.
*
* @param hVar the high variable
* @return the storage
*/
public static VariableStorage fabricateStorage(HighVariable hVar) {
try {
return new VariableStorage(hVar.getHighFunction().getFunction().getProgram(),
getInstanceInSymbolStorage(hVar));
}
catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
/**
* A class which supports evaluating variables
*/
public static class VariableEvaluator {
/**
* A listener that invalidates the stack unwind whenever the trace's bytes change
*/
private class ListenerForChanges extends TraceDomainObjectListener {
public ListenerForChanges() {
listenFor(TraceMemoryBytesChangeType.CHANGED, this::bytesChanged);
}
private void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range) {
TraceThread thread = space.getThread();
// TODO: Consider the lifespan, too? Would have to use viewport....
if (thread == null || thread == coordinates.getThread()) {
invalidateCache();
}
}
}
private final Object lock = new Object();
private final PluginTool tool;
private final DebuggerCoordinates coordinates;
private final Language language;
private final ListenerForChanges listenerForChanges = new ListenerForChanges();
private List<UnwoundFrame<WatchValue>> unwound;
private FakeUnwoundFrame<WatchValue> fakeFrame;
/**
* Construct an evaluator for the given tool and coordinates
*
* @param tool the tool
* @param coordinates the coordinates
*/
public VariableEvaluator(PluginTool tool, DebuggerCoordinates coordinates) {
this.tool = tool;
this.coordinates = coordinates;
this.language = coordinates.getPlatform().getLanguage();
coordinates.getTrace().addListener(listenerForChanges);
}
/**
* Dispose of this evaluator, removing its listener
*/
public void dispose() {
coordinates.getTrace().removeListener(listenerForChanges);
}
/**
* Invalidate the stack unwind
*/
public void invalidateCache() {
synchronized (lock) {
unwound = null;
}
}
/**
* Get a fake frame for global / static variables
*
* @return the fake frame
*/
public UnwoundFrame<WatchValue> getGlobalsFakeFrame() {
synchronized (lock) {
if (fakeFrame == null) {
fakeFrame = new FakeUnwoundFrame<>(tool, coordinates,
DebuggerPcodeUtils.buildWatchState(tool, coordinates.frame(0)));
}
return fakeFrame;
}
}
/**
* Refresh the stack unwind
*
* @param monitor a monitor for cancellation
*/
protected void doUnwind(TaskMonitor monitor) {
monitor.setMessage("Unwinding Stack");
StackUnwinder unwinder = new StackUnwinder(tool, coordinates.getPlatform());
unwound = new ArrayList<>();
for (AnalysisUnwoundFrame<WatchValue> frame : unwinder.frames(coordinates.frame(0),
monitor)) {
unwound.add(frame);
}
}
/**
* Get the stack frame for the given function at or beyond the coordinates' frame level
*
* @param function the desired function
* @param warnings a place to emit warnings
* @param monitor a monitor for cancellation
* @return the frame if found, or null
*/
public UnwoundFrame<WatchValue> getStackFrame(Function function, List<String> warnings,
TaskMonitor monitor) {
synchronized (lock) {
if (unwound == null) {
doUnwind(monitor);
}
for (UnwoundFrame<WatchValue> frame : unwound.subList(coordinates.getFrame(),
unwound.size())) {
if (frame.getFunction() == function) {
String unwindWarnings = frame.getWarnings();
if (unwindWarnings != null && !unwindWarnings.isBlank()) {
warnings.add(unwindWarnings);
}
return frame;
}
}
return null;
}
}
/**
* Get the data unit for a register
*
* <p>
* This accounts for memory-mapped registers.
*
* @param register the register
* @return the data unit, or null if undefined or mismatched
*/
public TraceData getRegisterUnit(Register register) {
TraceCodeOperations code;
TraceCodeManager codeManager = coordinates.getTrace().getCodeManager();
if (register.getAddressSpace().isRegisterSpace()) {
TraceThread thread = coordinates.getThread();
if (thread == null) {
return null;
}
code = codeManager.getCodeRegisterSpace(thread, false);
if (code == null) {
return null;
}
}
else {
code = codeManager;
}
return code.definedData()
.getForRegister(coordinates.getPlatform(), coordinates.getViewSnap(), register);
}
/**
* Obtain the value of a register
*
* <p>
* In order to accommodate user-provided types on registers, it's preferable to obtain the
* data unit using {@link #getRegisterUnit(Register)}. Fall back to this method only if that
* one fails.
*
* @param register
* @return
*/
public WatchValue getRawRegisterValue(Register register) {
WatchValuePcodeExecutorState state =
DebuggerPcodeUtils.buildWatchState(tool, coordinates.frame(0));
return state.getVar(register, Reason.INSPECT);
}
/**
* Get the representation of a variable's value according to a given data type
*
* @param address the best static address giving the location of the variable
* @param bytes the bytes giving the variable's value
* @param type the type of the variable
* @param settings settings to configure the data type
* @return the string representation, or null
*/
public String getRepresentation(Address address, byte[] bytes, DataType type,
Settings settings) {
if (type instanceof Pointer && !AddressSpaceSettingsDefinition.DEF.hasValue(settings) &&
address.isRegisterAddress()) {
settings = new DefaultSpaceSettings(settings, language.getDefaultSpace());
}
ByteMemBufferImpl buf =
new ByteMemBufferImpl(address, bytes, language.isBigEndian()) {
@Override
public Memory getMemory() {
return coordinates.getView().getMemory();
}
};
return type.getRepresentation(buf, settings, bytes.length);
}
/**
* Get the representation of a variable's value according to a given data type
*
* @param frame the frame that evaluated the variable's value
* @param address the best static address giving the location of the variable. Note that the
* address given by {@link WatchValue#address()} is its dynamic address. The
* static address should instead be taken from the variable's storage or a p-code
* op's output varnode.
* @param value the value of the variable
* @param type the type of the variable
* @return the string representation, or null
*/
public String getRepresentation(UnwoundFrame<?> frame, Address address, WatchValue value,
DataType type) {
if (type == DataType.DEFAULT) {
return null;
}
Settings settings = type.getDefaultSettings();
if (address.isStackAddress()) {
address = frame.getBasePointer().add(address.getOffset());
if (frame instanceof ListingUnwoundFrame listingFrame) {
settings = listingFrame.getComponentContaining(address);
}
}
return getRepresentation(address, value.bytes().bytes(), type, settings);
}
}
}

View file

@ -126,7 +126,7 @@ public class WatchRow {
prevValue = prevExec == null ? null : compiled.evaluate(prevExec);
TracePlatform platform = provider.current.getPlatform();
value = fullValue.bytes();
value = fullValue.bytes().bytes();
error = null;
state = fullValue.state();
// TODO: Optional column for guest address?

View file

@ -15,11 +15,14 @@
*/
package ghidra.app.plugin.core.debug.service.emulation;
import java.util.Map;
import java.util.concurrent.*;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerDataAccess;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.exec.AccessPcodeExecutionException;
import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.memory.TraceMemoryState;
@ -44,6 +47,11 @@ public abstract class AbstractRWTargetPcodeExecutorStatePiece
super(language, space, backing);
}
protected AbstractRWTargetCachedSpace(Language language, AddressSpace space,
PcodeTraceDataAccess backing, SemisparseByteArray bytes, AddressSet written) {
super(language, space, backing, bytes, written);
}
protected abstract void fillUninitialized(AddressSet uninitialized);
@Override
@ -99,6 +107,20 @@ public abstract class AbstractRWTargetPcodeExecutorStatePiece
*/
protected abstract class TargetBackedSpaceMap
extends CacheingSpaceMap<PcodeDebuggerDataAccess, CachedSpace> {
public TargetBackedSpaceMap() {
super();
}
protected TargetBackedSpaceMap(Map<AddressSpace, CachedSpace> spaces) {
super(spaces);
}
@Override
public CachedSpace fork(CachedSpace s) {
return s.fork();
}
@Override
protected PcodeDebuggerDataAccess getBacking(AddressSpace space) {
return data;

View file

@ -16,7 +16,7 @@
package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerMemoryAccess;
import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
import ghidra.pcode.exec.trace.*;
/**
* A state composing a single {@link RWTargetMemoryPcodeExecutorStatePiece}
@ -31,4 +31,14 @@ public class RWTargetMemoryPcodeExecutorState extends DefaultTracePcodeExecutorS
public RWTargetMemoryPcodeExecutorState(PcodeDebuggerMemoryAccess data, Mode mode) {
super(new RWTargetMemoryPcodeExecutorStatePiece(data, mode));
}
protected RWTargetMemoryPcodeExecutorState(
TracePcodeExecutorStatePiece<byte[], byte[]> piece) {
super(piece);
}
@Override
public RWTargetMemoryPcodeExecutorState fork() {
return new RWTargetMemoryPcodeExecutorState(piece.fork());
}
}

View file

@ -15,10 +15,12 @@
*/
package ghidra.app.plugin.core.debug.service.emulation;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerDataAccess;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerMemoryAccess;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.memory.TraceMemoryState;
@ -66,6 +68,18 @@ public class RWTargetMemoryPcodeExecutorStatePiece
this.backing = backing;
}
protected RWTargetMemoryCachedSpace(Language language, AddressSpace space,
PcodeDebuggerMemoryAccess backing, SemisparseByteArray bytes, AddressSet written) {
super(language, space, backing, bytes, written);
this.backing = backing;
}
@Override
public RWTargetMemoryCachedSpace fork() {
return new RWTargetMemoryCachedSpace(language, space, backing, bytes.fork(),
new AddressSet(written));
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
if (space.isUniqueSpace()) {
@ -114,14 +128,29 @@ public class RWTargetMemoryPcodeExecutorStatePiece
this.mode = mode;
}
class WRTargetMemorySpaceMap extends TargetBackedSpaceMap {
public WRTargetMemorySpaceMap() {
super();
}
protected WRTargetMemorySpaceMap(Map<AddressSpace, CachedSpace> spaceMap) {
super(spaceMap);
}
@Override
public AbstractSpaceMap<CachedSpace> fork() {
return new WRTargetMemorySpaceMap(fork(spaces));
}
@Override
protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) {
return new RWTargetMemoryCachedSpace(language, space,
(PcodeDebuggerMemoryAccess) data);
}
}
@Override
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TargetBackedSpaceMap() {
@Override
protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) {
return new RWTargetMemoryCachedSpace(language, space,
(PcodeDebuggerMemoryAccess) data);
}
};
return new WRTargetMemorySpaceMap();
}
}

View file

@ -16,7 +16,7 @@
package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerRegistersAccess;
import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
import ghidra.pcode.exec.trace.*;
/**
* A state composing a single {@link RWTargetRegistersPcodeExecutorStatePiece}
@ -31,4 +31,14 @@ public class RWTargetRegistersPcodeExecutorState extends DefaultTracePcodeExecut
public RWTargetRegistersPcodeExecutorState(PcodeDebuggerRegistersAccess data, Mode mode) {
super(new RWTargetRegistersPcodeExecutorStatePiece(data, mode));
}
protected RWTargetRegistersPcodeExecutorState(
TracePcodeExecutorStatePiece<byte[], byte[]> piece) {
super(piece);
}
@Override
public RWTargetRegistersPcodeExecutorState fork() {
return new RWTargetRegistersPcodeExecutorState(piece.fork());
}
}

View file

@ -15,10 +15,12 @@
*/
package ghidra.app.plugin.core.debug.service.emulation;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerDataAccess;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerRegistersAccess;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.memory.TraceMemoryState;
@ -62,6 +64,19 @@ public class RWTargetRegistersPcodeExecutorStatePiece
this.backing = backing;
}
protected RWTargetRegistersCachedSpace(Language language, AddressSpace space,
PcodeDebuggerRegistersAccess backing, SemisparseByteArray bytes,
AddressSet written) {
super(language, space, backing, bytes, written);
this.backing = backing;
}
@Override
public RWTargetRegistersCachedSpace fork() {
return new RWTargetRegistersCachedSpace(language, uniqueSpace, backing, bytes.fork(),
new AddressSet(written));
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
if (space.isUniqueSpace()) {
@ -98,14 +113,29 @@ public class RWTargetRegistersPcodeExecutorStatePiece
this.mode = mode;
}
class WRTargetRegistersSpaceMap extends TargetBackedSpaceMap {
public WRTargetRegistersSpaceMap() {
super();
}
protected WRTargetRegistersSpaceMap(Map<AddressSpace, CachedSpace> spaceMap) {
super(spaceMap);
}
@Override
public AbstractSpaceMap<CachedSpace> fork() {
return new WRTargetRegistersSpaceMap(fork(spaces));
}
@Override
protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) {
return new RWTargetRegistersCachedSpace(language, space,
(PcodeDebuggerRegistersAccess) data);
}
}
@Override
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TargetBackedSpaceMap() {
@Override
protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) {
return new RWTargetRegistersCachedSpace(language, space,
(PcodeDebuggerRegistersAccess) data);
}
};
return new WRTargetRegistersSpaceMap();
}
}

View file

@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.service.emulation.data;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.trace.data.PcodeTraceAccess;
import ghidra.trace.model.thread.TraceThread;
/**
* A trace-and-debugger access shim
@ -34,4 +35,7 @@ public interface PcodeDebuggerAccess extends PcodeTraceAccess {
@Override
PcodeDebuggerRegistersAccess getDataForLocalState(PcodeThread<?> thread, int frame);
@Override
PcodeDebuggerRegistersAccess getDataForLocalState(TraceThread thread, int frame);
}

View file

@ -29,6 +29,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.event.TraceOpenedPluginEvent;
import ghidra.app.plugin.core.debug.stack.*;
import ghidra.app.plugin.core.debug.utils.*;
import ghidra.app.services.*;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;

View file

@ -0,0 +1,359 @@
/* ###
* 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.stack;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.ArrayUtils;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.async.AsyncFence;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.eval.AbstractVarnodeEvaluator;
import ghidra.pcode.eval.ArithmeticVarnodeEvaluator;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.opbehavior.BinaryOpBehavior;
import ghidra.pcode.opbehavior.UnaryOpBehavior;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceLocation;
import ghidra.trace.model.guest.TracePlatform;
/**
* An abstract implementation of {@link UnwoundFrame}
*
* <p>
* This generally contains all the methods for interpreting and retrieving higher-level variables
* once the frame context is known. It doesn't contain the mechanisms for creating or reading
* annotations.
*
* @param <T> the type of values retrievable from the unwound frame
*/
public abstract class AbstractUnwoundFrame<T> implements UnwoundFrame<T> {
/**
* A class which can evaluate high p-code varnodes in the context of a stack frame using a
* p-code arithmetic
*
* @param <U> the evaluation result type
*/
protected abstract class ArithmeticFrameVarnodeEvaluator<U>
extends ArithmeticVarnodeEvaluator<U> {
public ArithmeticFrameVarnodeEvaluator(PcodeArithmetic<U> arithmetic) {
super(arithmetic);
}
@Override
protected Address applyBase(long offset) {
return AbstractUnwoundFrame.this.applyBase(offset);
}
@Override
protected Address translateMemory(Program program, Address address) {
TraceLocation location = mappingService.getOpenMappedLocation(trace,
new ProgramLocation(program, address), snap);
return location == null ? null : location.getAddress();
}
}
/**
* A class which can evaluate high p-code varnodes in the context of a stack frame
*
* @param <U> the evaluation result type
*/
protected abstract class AbstractFrameVarnodeEvaluator<U> extends AbstractVarnodeEvaluator<U> {
@Override
protected Address applyBase(long offset) {
return AbstractUnwoundFrame.this.applyBase(offset);
}
@Override
protected Address translateMemory(Program program, Address address) {
TraceLocation location = mappingService.getOpenMappedLocation(trace,
new ProgramLocation(program, address), snap);
return location == null ? null : location.getAddress();
}
}
/**
* A frame evaluator which descends to symbol storage
*
* <p>
* This ensure that if a register is used as a temporary value in an varnode AST, that
* evaluation proceeds all the way to the "source" symbols.
*
* @param <U> the evaluation result type
*/
protected abstract class FrameVarnodeEvaluator<U> extends ArithmeticFrameVarnodeEvaluator<U> {
private final AddressSetView symbolStorage;
/**
* Construct an evaluator with the given arithmetic and symbol storage
*
* <p>
* Varnodes contained completely in symbol storage are presumed to be the inputs of the
* evaluation. All other varnodes are evaluated by examining their defining p-code op. It is
* an error to include any unique space in symbol storage.
*
* @param arithmetic the arithmetic for evaluating p-code ops
* @param symbolStorage the address ranges to regard as input, i.e., the leaves of evalution
*/
public FrameVarnodeEvaluator(PcodeArithmetic<U> arithmetic, AddressSetView symbolStorage) {
super(arithmetic);
this.symbolStorage = symbolStorage;
}
@Override
protected boolean isLeaf(Varnode vn) {
return vn.isConstant() ||
symbolStorage.contains(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1));
}
}
/**
* A frame "evaluator" which merely gets values
*
* <p>
* This evaluator never descends to defining p-code ops. It is an error to ask it for the value
* of unique varnodes. With some creativity, this can also be used as a varnode visitor to set
* values.
*
* @param <U> the evaluation result type
*/
protected abstract class FrameVarnodeValueGetter<U> extends ArithmeticFrameVarnodeEvaluator<U> {
public FrameVarnodeValueGetter(PcodeArithmetic<U> arithmetic) {
super(arithmetic);
}
@Override
protected boolean isLeaf(Varnode vn) {
return true;
}
}
/**
* A frame "evaluator" which actually sets values
*
* @param <U> the evaluation result type
*/
protected abstract class FrameVarnodeValueSetter<U> extends AbstractFrameVarnodeEvaluator<U> {
@Override
protected boolean isLeaf(Varnode vn) {
return true;
}
@Override
protected U evaluateUnaryOp(Program program, PcodeOp op, UnaryOpBehavior unOp,
Map<Varnode, U> already) {
throw new UnsupportedOperationException();
}
@Override
protected U evaluateBinaryOp(Program program, PcodeOp op, BinaryOpBehavior binOp,
Map<Varnode, U> already) {
throw new UnsupportedOperationException();
}
@Override
protected U evaluateAbstract(Program program, AddressSpace space, U offset, int size,
Map<Varnode, U> already) {
throw new UnsupportedOperationException();
}
@Override
protected U evaluateConstant(long value, int size) {
throw new UnsupportedOperationException();
}
@Override
protected U evaluateLoad(Program program, PcodeOp op, Map<Varnode, U> already) {
throw new UnsupportedOperationException();
}
@Override
protected U evaluatePtrAdd(Program program, PcodeOp op, Map<Varnode, U> already) {
throw new UnsupportedOperationException();
}
@Override
protected U evaluatePtrSub(Program program, PcodeOp op, Map<Varnode, U> already) {
throw new UnsupportedOperationException();
}
}
protected final DebuggerCoordinates coordinates;
protected final Trace trace;
protected final TracePlatform platform;
protected final long snap;
protected final long viewSnap;
protected final Language language;
protected final AddressSpace codeSpace;
protected final Register pc;
protected final PcodeExecutorState<T> state;
protected final DebuggerStaticMappingService mappingService;
/**
* Construct an unwound frame
*
* @param tool the tool requesting interpretation of the frame, which provides context for
* mapped static programs.
* @param coordinates the coordinates (trace, thread, snap, etc.) to examine
* @param state the machine state, typically the watch value state for the same coordinates. It
* is the caller's (i.e., subclass') responsibility to ensure the given state
* corresponds to the given coordinates.
*/
public AbstractUnwoundFrame(PluginTool tool, DebuggerCoordinates coordinates,
PcodeExecutorState<T> state) {
this.coordinates = coordinates;
this.trace = coordinates.getTrace();
this.platform = coordinates.getPlatform();
this.snap = coordinates.getSnap();
this.viewSnap = coordinates.getViewSnap();
this.language = platform.getLanguage();
this.codeSpace = language.getDefaultSpace();
this.pc = language.getProgramCounter();
this.state = state;
this.mappingService = tool.getService(DebuggerStaticMappingService.class);
}
/**
* Get or recover the saved register map
*
* <p>
* This indicates the location of saved registers on the stack that apply to this frame.
*
* @return the register map
*/
protected abstract SavedRegisterMap computeRegisterMap();
/**
* Compute the <em>address of</em> the return address
*
* @return the address of the return address
*/
protected abstract Address computeAddressOfReturnAddress();
/**
* Compute the address (in physical stack space) of the given stack offset
*
* @param offset the stack offset, relative to the stack pointer at the entry to the function
* that allocated this frame.
* @return the address in physical stack space
*/
protected abstract Address applyBase(long offset);
@Override
public T getValue(VariableStorage storage) {
SavedRegisterMap registerMap = computeRegisterMap();
return new FrameVarnodeValueGetter<T>(state.getArithmetic()) {
@Override
protected T evaluateMemory(Address address, int size) {
return registerMap.getVar(state, address, size, Reason.INSPECT);
}
}.evaluateStorage(storage);
}
@Override
public T getValue(Register register) {
SavedRegisterMap registerMap = computeRegisterMap();
return registerMap.getVar(state, register.getAddress(), register.getNumBytes(),
Reason.INSPECT);
}
@Override
public T evaluate(VariableStorage storage, AddressSetView symbolStorage) {
SavedRegisterMap registerMap = computeRegisterMap();
return new FrameVarnodeEvaluator<T>(state.getArithmetic(), symbolStorage) {
@Override
protected T evaluateMemory(Address address, int size) {
return registerMap.getVar(state, address, size, Reason.INSPECT);
}
}.evaluateStorage(storage);
}
@Override
public T evaluate(Program program, PcodeOp op, AddressSetView symbolStorage) {
SavedRegisterMap registerMap = computeRegisterMap();
return new FrameVarnodeEvaluator<T>(state.getArithmetic(), symbolStorage) {
@Override
protected T evaluateMemory(Address address, int size) {
return registerMap.getVar(state, address, size, Reason.INSPECT);
}
}.evaluateOp(program, op);
}
@Override
public CompletableFuture<Void> setValue(StateEditor editor, VariableStorage storage,
BigInteger value) {
SavedRegisterMap registerMap = computeRegisterMap();
ByteBuffer buf = ByteBuffer.wrap(Utils.bigIntegerToBytes(value, storage.size(), true));
AsyncFence fence = new AsyncFence();
new FrameVarnodeValueSetter<ByteBuffer>() {
@Override
protected ByteBuffer evaluateMemory(Address address, int size) {
byte[] bytes = new byte[size];
buf.get(bytes);
if (!language.isBigEndian()) {
ArrayUtils.reverse(bytes);
}
fence.include(registerMap.setVar(editor, address, bytes));
return buf;
}
@Override
protected ByteBuffer catenate(int total, ByteBuffer value, ByteBuffer piece, int size) {
return value;
}
@Override
public ByteBuffer evaluateStorage(VariableStorage storage) {
return evaluateStorage(storage, buf);
}
}.evaluateStorage(storage);
return fence.ready();
}
@Override
public CompletableFuture<Void> setReturnAddress(StateEditor editor, Address addr) {
if (addr.getAddressSpace() != codeSpace) {
throw new IllegalArgumentException("Return address must be in " + codeSpace);
}
BytesPcodeArithmetic bytesArithmetic = BytesPcodeArithmetic.forLanguage(language);
byte[] bytes = bytesArithmetic.fromConst(addr.getOffset(), pc.getNumBytes());
return editor.setVariable(computeAddressOfReturnAddress(), bytes);
}
@Override
public T zext(T value, int length) {
PcodeArithmetic<T> arithmetic = state.getArithmetic();
return arithmetic.unaryOp(PcodeOp.INT_ZEXT, length, (int) arithmetic.sizeOf(value), value);
}
}

View file

@ -0,0 +1,400 @@
/* ###
* 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.stack;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.bookmark.BookmarkNavigator;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.BytesPcodeArithmetic;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.program.model.data.DataTypeConflictHandler;
import ghidra.program.model.data.Structure;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.bookmark.*;
import ghidra.trace.model.listing.TraceData;
import ghidra.trace.model.symbol.TraceReferenceManager;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A frame recovered from analysis of a thread's register bank and stack segment
*
* <p>
* The typical pattern for invoking analysis to unwind an entire stack is to use
* {@link StackUnwinder#start(DebuggerCoordinates, TaskMonitor)} or similar, followed by
* {@link #unwindNext(TaskMonitor)} in a chain until the stack is exhausted or analysis fails to
* unwind a frame. It may be more convenient to use
* {@link StackUnwinder#frames(DebuggerCoordinates, TaskMonitor)}. Its iterator implements that
* pattern. Because unwinding can be expensive, it is recommended to cache the unwound stack when
* possible. A centralized service for stack unwinding may be added later.
*
* @param <T> the type of values retrievable from the unwound frame
*/
public class AnalysisUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
private final StackUnwinder unwinder;
private final int level;
private final Address pcVal;
private final Address spVal;
private final UnwindInfo info;
private final UnwindException infoErr;
private final SavedRegisterMap registerMap;
private final Address base;
/**
* Construct an unwound frame
*
* <p>
* Clients should instead use {@link StackUnwinder#start(DebuggerCoordinates, TaskMonitor)} or
* similar, or {@link #unwindNext(TaskMonitor)}.
*
* @param tool the tool requesting interpretation of the frame, which provides context for
* mapped static programs.
* @param coordinates the coordinates (trace, thread, snap, etc.) to examine
* @param unwinder the unwinder that produced this frame, and may be used to unwind the next
* frame
* @param state the machine state, typically the watch value state for the same coordinates. It
* is the caller's (i.e., subclass') responsibility to ensure the given state
* corresponds to the given coordinates.
* @param level the level of this frame
* @param pcVal the address of the next instruction when this frame becomes the current frame
* @param spVal the address of the top of the stack when this frame becomes the current frame
* @param info the information used to unwind this frame
* @param infoErr if applicable, an error describing why the unwind info is missing or
* incomplete
* @param registerMap a map from registers to the offsets of their saved values on the stack
*/
AnalysisUnwoundFrame(PluginTool tool, DebuggerCoordinates coordinates, StackUnwinder unwinder,
PcodeExecutorState<T> state, int level, Address pcVal, Address spVal, UnwindInfo info,
UnwindException infoErr, SavedRegisterMap registerMap) {
super(tool, coordinates, state);
if ((info == null) == (infoErr == null)) {
throw new AssertionError();
}
this.unwinder = unwinder;
this.level = level;
this.pcVal = pcVal;
this.spVal = spVal;
this.info = info;
this.infoErr = infoErr;
this.registerMap = registerMap;
if (info != null) {
this.base = info.computeBase(spVal);
}
else {
this.base = null;
}
}
@Override
public boolean isFake() {
return false;
}
/**
* Unwind the next frame up
*
* <p>
* Unwind the frame that would become current if the function that allocated this frame were to
* return. For example, if this frame is at level 3, {@code unwindNext} will attempt to unwind
* the frame at level 4.
*
* <p>
* The program counter and stack pointer for the next frame are computed using the state
* originally given in
* {@link StackUnwinder#start(DebuggerCoordinates, PcodeExecutorState, TaskMonitor)} and this
* frame's unwind information. The state is usually the watch-value state bound to the starting
* coordinates. The program counter is evaluated like any other variable. The stack pointer is
* computed by removing the depth of this frame. Then registers are restored and unwinding
* proceeds the same as the starting frame.
*
* @param monitor a monitor for cancellation
* @return the next frame up
* @throws CancelledException if the monitor is cancelled
* @throws UnwindException if unwinding fails
*/
public AnalysisUnwoundFrame<T> unwindNext(TaskMonitor monitor) throws CancelledException {
if (info == null || info.ofReturn() == null) {
throw new NoSuchElementException();
}
SavedRegisterMap registerMap = this.registerMap.fork();
info.mapSavedRegisters(base, registerMap);
Address pcVal = info.computeNextPc(base, state, codeSpace, pc);
Address spVal = info.computeNextSp(base);
return unwinder.unwind(coordinates, level + 1, pcVal, spVal, state, registerMap,
monitor);
}
@Override
protected Address applyBase(long offset) {
if (base == null) {
throw new UnwindException("Cannot compute stack address for offset " + offset,
infoErr);
}
return base.add(offset);
}
@Override
protected SavedRegisterMap computeRegisterMap() {
return registerMap;
}
@Override
protected Address computeAddressOfReturnAddress() {
return info.ofReturn(base);
}
@Override
public Address getReturnAddress() {
return info.computeNextPc(base, state, codeSpace, pc);
}
@Override
public CompletableFuture<Void> setReturnAddress(StateEditor editor, Address addr) {
if (addr.getAddressSpace() != codeSpace) {
throw new IllegalArgumentException("Return address must be in " + codeSpace);
}
BytesPcodeArithmetic bytesArithmetic = BytesPcodeArithmetic.forLanguage(language);
byte[] bytes = bytesArithmetic.fromConst(addr.getOffset(), pc.getNumBytes());
return editor.setVariable(info.ofReturn(base), bytes);
}
@Override
public int getLevel() {
return level;
}
@Override
public String getDescription() {
return String.format("%s %s pc=%s sp=%s base=%s", level, info.function(),
pcVal.toString(false), spVal.toString(false), base.toString(false));
}
@Override
public Address getBasePointer() {
return base;
}
@Override
public Address getProgramCounter() {
return pcVal;
}
public Address getStackPointer() {
return spVal;
}
@Override
public Function getFunction() {
return info.function();
}
/**
* Generate the structure for {@link #resolveStructure(int)}
*
* @param prevParamSize the number of bytes occupied by the parameters for the next frame down.
* @return the generated structure
*/
protected Structure generateStructure(int prevParamSize) {
FrameStructureBuilder builder =
new FrameStructureBuilder(language, pcVal, info, prevParamSize);
return builder.build(StackUnwinder.FRAMES_PATH, "frame_" + pcVal.toString(false),
trace.getDataTypeManager());
}
/**
* Create or resolve the structure data type representing this frame
*
* <p>
* The structure composes a variety of information: 1) The stack variables (locals and
* parameters) of the function that allocated the frame. Note that some variables may be omitted
* if the function has not allocated them or has already freed them relative to the frame's
* program counter. 2) Saved registers. Callee-saved registers will typically appear closer to
* the next frame up. Caller-saved registers, assuming Ghidra hasn't already assigned the stack
* offset to a local variable, will typically appear close to the next frame down. 3) The return
* address, if on the stack.
*
* @param prevParamSize the number of bytes occupied by the parameters for the next frame down.
* Parameters are pushed by the caller, and so appear to be allocated by the caller;
* however, the really belong to the callee, so this specifies the number of bytes to
* "donate" to the callee's frame.
* @return the structure, to be placed {@code prevParamSize} bytes after the frame's stack
* pointer.
*/
public Structure resolveStructure(int prevParamSize) {
Structure structure = generateStructure(prevParamSize);
return structure == null ? null
: trace.getDataTypeManager()
.addType(structure, DataTypeConflictHandler.DEFAULT_HANDLER);
}
/**
* Get or create the bookmark type for warnings
*
* @return the bookmark type
*/
protected TraceBookmarkType getWarningBookmarkType() {
TraceBookmarkType type = trace.getBookmarkManager().getBookmarkType(BookmarkType.WARNING);
if (type != null) {
return type;
}
BookmarkNavigator.defineBookmarkTypes(trace.getProgramView());
return trace.getBookmarkManager().getBookmarkType(BookmarkType.WARNING);
}
protected static void truncateOrDelete(TraceBookmark tb, Lifespan remove) {
List<Lifespan> newLifespan = tb.getLifespan().subtract(remove);
if (newLifespan.isEmpty()) {
tb.delete();
}
else {
tb.setLifespan(newLifespan.get(0));
}
}
/**
* Apply this unwound frame to the trace's listing
*
* <p>
* This performs the following, establishing some conventions for trace stack analysis:
* <ul>
* <li>Places a bookmark at the frame start indicating any warnings encountered while analyzing
* it.</li>
* <li>Places a structure at (or near) the derived stack pointer whose fields denote the various
* stack entries: local variables, saved registers, return address, parameters. The structure
* may be placed a little after the derived stack pointer to accommodate the parameters of an
* inner stack frame. The structure data type will have the category path
* {@link StackUnwinder#FRAMES_PATH}. This allows follow-on analysis to identify data units
* representing unwound frames. See {@link #isFrame(TraceData)}.</li>
* <li>Places a comment at the start of the frame. This is meant for human consumption, so
* follow-on analysis should not attempt to parse or otherwise interpret it. It will indicate
* the frame level (0 being the innermost), the function name, the program counter, the stack
* pointer, and the frame base pointer.</li>
* <li>Places a {@link RefType#DATA} reference from the frame start to its own base address.
* This permits follow-on analysis to derive variable values stored on the stack. See
* {@link #getBase(TraceData)} and {@link #getValue(TraceData, VariableStorage)}.</li>
* <li>Places a {@link RefType#DATA} reference from the program counter to the frame start. This
* allows follow-on analysis to determine the function for the frame. See
* {@link #getProgramCounter(TraceData)} and
* {@link #getFunction(TraceData, DebuggerStaticMappingService)}.</li>
* </ul>
*
* <p>
* The resulting data unit can be retrieved from the trace database and later used to construct
* a {@link ListingUnwoundFrame}. If the frame structure would have length 0 it is not applied.
*
* @param prevParamSize the number of bytes occupied by the parameters for the next frame down.
* See {@link #resolveStructure(int)}.
* @param monitor a monitor for cancellation
* @return the data unit for the frame structure applied, or null
* @throws CancelledException if the monitor is cancelled
*/
public TraceData applyToListing(int prevParamSize, TaskMonitor monitor)
throws CancelledException {
// TODO: Positive stack growth
Address spPlusParams = spVal.add(prevParamSize);
TraceBookmarkManager bm = trace.getBookmarkManager();
TraceBookmarkType btWarn = getWarningBookmarkType();
Lifespan span = Lifespan.nowOnMaybeScratch(viewSnap);
String warnings = info.warnings().summarize();
Structure structure = resolveStructure(prevParamSize);
if (structure == null || structure.isZeroLength()) {
for (TraceBookmark existing : bm.getBookmarksAt(viewSnap, spPlusParams)) {
truncateOrDelete(existing, span);
}
bm.addBookmark(span, spPlusParams, btWarn, "Stack Unwind",
"Frame " + level + " has lenght 0");
return null;
}
for (TraceBookmark existing : bm.getBookmarksIntersecting(span,
new AddressRangeImpl(spPlusParams, spPlusParams.add(structure.getLength() - 1)))) {
truncateOrDelete(existing, span);
}
if (!warnings.isBlank()) {
bm.addBookmark(span, spPlusParams, btWarn, "Unwind Stack", warnings);
}
try {
trace.getCodeManager()
.definedUnits()
.clear(Lifespan.at(viewSnap), new AddressRangeImpl(spPlusParams,
spPlusParams.add(structure.getLength() - 1)), false, monitor);
TraceData frame = trace.getCodeManager()
.definedData()
.create(span, spPlusParams, structure);
frame.setComment(CodeUnit.PRE_COMMENT, getDescription());
TraceReferenceManager refs = trace.getReferenceManager();
refs.clearReferencesFrom(span, frame.getRange());
refs.clearReferencesTo(span, frame.getRange());
frame.addOperandReference(StackUnwinder.BASE_OP_INDEX, base, RefType.DATA,
SourceType.ANALYSIS);
refs.addMemoryReference(span, pcVal, spPlusParams, RefType.DATA, SourceType.ANALYSIS,
StackUnwinder.PC_OP_INDEX);
return frame;
}
catch (CodeUnitInsertionException e) {
throw new AssertionError(e);
}
}
/**
* Get the unwind information from the analysis used to unwind this frame
*
* @return the information
*/
public UnwindInfo getUnwindInfo() {
return info;
}
/**
* If the unwind information is absent or incomplete, get the error explaining why.
*
* <p>
* When analysis is incomplete, the frame may still be partially unwound, meaning only certain
* variables can be evaluated, and the return address may not be available. Typically, a
* partially unwound frame is the last frame that can be recovered in the stack. If the base
* pointer could not be recovered, then only register variables and static variables can be
* evaluated.
*
* @return the error
*/
public UnwindException getError() {
return infoErr;
}
@Override
public String getWarnings() {
if (info == null) {
return "";
}
return info.warnings().summarize();
}
}

View file

@ -0,0 +1,108 @@
/* ###
* 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.stack;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
/**
* A fake frame which can be used to evaluate variables for which an actual frame is not necessary
* or not available.
*
* <p>
* This "frame" can only evaluate static / global variables. Neither register variables nor stack
* variables can be evaluated. The reason for excluding registers is because some register values
* may be saved to the stack, so the values in the bank may not be the correct value in the context
* of a given stack frame. Based on an inspection of a variable's storage, it may not be necessary
* to attempt a stack unwind to evaluate it. If that is the case, this "frame" may be used to
* evaluate it where a frame interface is expected or convenient.
*/
public class FakeUnwoundFrame<T> extends AbstractUnwoundFrame<T> {
private static final SavedRegisterMap IDENTITY_MAP = new SavedRegisterMap();
/**
* Construct a fake "frame"
*
* @param tool the tool requesting interpretation of the frame, which provides context for
* mapped static programs.
* @param coordinates the coordinates (trace, thread, snap, etc.) to examine
* @param state the machine state, typically the watch value state for the same coordinates. It
* is the caller's (i.e., subclass') responsibility to ensure the given state
* corresponds to the given coordinates.
*/
public FakeUnwoundFrame(PluginTool tool, DebuggerCoordinates coordinates,
PcodeExecutorState<T> state) {
super(tool, coordinates, state);
}
@Override
public boolean isFake() {
return true;
}
@Override
public int getLevel() {
throw new UnsupportedOperationException();
}
@Override
public String getDescription() {
return "(No frame required)";
}
@Override
public Address getProgramCounter() {
return null;
}
@Override
public Function getFunction() {
return null;
}
@Override
public Address getBasePointer() {
return null;
}
@Override
public Address getReturnAddress() {
return null;
}
@Override
public String getWarnings() {
return "";
}
@Override
protected SavedRegisterMap computeRegisterMap() {
return IDENTITY_MAP;
}
@Override
protected Address computeAddressOfReturnAddress() {
return null;
}
@Override
protected Address applyBase(long offset) {
throw new UnsupportedOperationException();
}
}

View file

@ -0,0 +1,278 @@
/* ###
* 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.stack;
import java.util.*;
import java.util.Map.Entry;
import generic.ULongSpan;
import generic.ULongSpan.DefaultULongSpanSet;
import generic.ULongSpan.MutableULongSpanSet;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.Varnode;
import ghidra.trace.model.data.TraceBasedDataTypeManager;
import ghidra.util.Msg;
/**
* The implementation of {@link AnalysisUnwoundFrame#generateStructure(int)}
*/
class FrameStructureBuilder {
public static final String RETURN_ADDRESS_FIELD_NAME = "return_address";
public static final String SAVED_REGISTER_FIELD_PREFIX = "saved_";
private record FrameField(Address address, String name, DataType type, int length,
int scopeStart) {
AddressRange range() {
return new AddressRangeImpl(address, address.add(length - 1));
}
boolean overlaps(FrameStructureBuilder.FrameField that) {
return range().intersects(that.range());
}
}
private final AddressSpace codeSpace;
private final Register pc;
private final Address min;
private Address max; // Exclusive
private final long functionOffset;
private final NavigableMap<Address, FrameStructureBuilder.FrameField> fields = new TreeMap<>();
/**
* Builder for a structure based on unwind info and function stack variables
*
* @param language the language defining the program counter register and code address space
* @param pcVal the value of the program counter, used to determine variable scope
* @param info the unwind information
* @param prevParamSize the number of bytes past the stack pointer at entry used by the previous
* frame, typically for its parameters
*/
FrameStructureBuilder(Language language, Address pcVal, UnwindInfo info, int prevParamSize) {
this.codeSpace = language.getDefaultSpace();
this.pc = language.getProgramCounter();
this.min = info.function()
.getProgram()
.getAddressFactory()
.getStackSpace()
.getAddress(info.depth() + prevParamSize);
this.max = min;
this.functionOffset = pcVal.subtract(info.function().getEntryPoint());
processSaved(info.saved());
if (info.ofReturn() != null) {
processOfReturn(info.ofReturn());
}
processFunction(info.function());
}
/**
* Remove overlapping fields
*
* <p>
* Entries with later {@link FrameField#scopeStart()} are preferred. No fields are generated for
* variables whose scope starts come after the current instruction offset, so that should ensure
* the most relevant variables are selected. Variables are always preferred over saved
* registers. Ideally, the return address should not conflict, but if it does, the return
* address is preferred, esp., since it is necessary to unwind the next frame.
*
* @return the list of non-overlapping fields
*/
protected List<FrameStructureBuilder.FrameField> resolveOverlaps() {
List<FrameStructureBuilder.FrameField> result = new ArrayList<>(fields.size());
Entry<Address, FrameStructureBuilder.FrameField> ent1 = fields.pollFirstEntry();
next1: while (ent1 != null) {
FrameStructureBuilder.FrameField field1 = ent1.getValue();
next2: while (true) {
Entry<Address, FrameStructureBuilder.FrameField> ent2 = fields.pollFirstEntry();
if (ent2 == null) {
result.add(field1);
return result;
}
FrameStructureBuilder.FrameField field2 = ent2.getValue();
if (!field1.overlaps(field2)) {
result.add(field1);
ent1 = ent2;
continue next1;
}
if (field1.scopeStart() > field2.scopeStart()) {
// Drop field2, but we still need to check if field1 overlaps the next
continue next2;
}
else if (field1.scopeStart() < field2.scopeStart()) {
// Drop field1
ent1 = ent2;
continue next1;
}
else {
Msg.warn(this,
"Two overlapping variables with equal first use offsets....");
// Prefer field1, I guess
continue next2;
}
}
}
return result;
}
/**
* Build the resulting structure
*
* @param path the category path for the new structure
* @param name the name of the new structure
* @param dtm the data type manager for the structure
* @return the new structure
*/
public Structure build(CategoryPath path, String name, TraceBasedDataTypeManager dtm) {
List<FrameStructureBuilder.FrameField> resolved = resolveOverlaps();
if (resolved.isEmpty()) {
return null;
}
int length = (int) max.subtract(min);
if (length == 0) {
return null;
}
Structure structure = new StructureDataType(path, name, length, dtm);
MutableULongSpanSet undefined = new DefaultULongSpanSet();
undefined.add(ULongSpan.extent(0, structure.getLength()));
for (FrameStructureBuilder.FrameField field : resolved) {
int offset = (int) field.address().subtract(min);
if (offset < 0) {
/**
* No function should reach beyond the current stack pointer, especially near a
* call, since that space is presumed to belong to the callee. When we see variables
* beyond the current depth, it's likely they're just not in scope. For example, a
* local variable may be allocated on the stack when entering a block, so Ghidra
* will show that variable in the frame. However, we may encounter a program counter
* during unwinding that has not entered that block, or if it did, has already
* exited and deallocated the local. We must omit such variables. NOTE: For some
* variables, notably those that re-use storage, we do note the start of it scope,
* but not the end.
*
* The min also accounts for the parameters in the previous frame. We'll observe
* writes to those, but we shouldn't see reads. Depending on changes to the unwind
* static analysis, passed parameters could get mistaken for saved registers.
* Nevertheless, we should prefer to assign overlapped portions of frames to the
* frame that uses them for parameters rather than scratch space. If diagnostics are
* desired, we may need to distinguish min from the SP vs min from SP + prevParams
* so that we can better assess each conflict.
*/
continue;
}
DataType type = field.type();
if (type == IntegerDataType.dataType) {
type = IntegerDataType.getUnsignedDataType(field.length(), dtm);
}
else if (type == PointerDataType.dataType) {
type = new PointerTypedefBuilder(PointerDataType.dataType, dtm)
.addressSpace(codeSpace)
.build();
}
structure.replaceAtOffset(offset, type, field.length(), field.name(), "");
undefined.remove(ULongSpan.extent(offset, field.length()));
}
for (ULongSpan undefSpan : undefined.spans()) {
int spanLength = (int) undefSpan.length();
DataType type = new ArrayDataType(DataType.DEFAULT, spanLength, 1, dtm);
int offset = undefSpan.min().intValue();
Address addr = min.add(offset);
String fieldName = addr.getOffset() < 0
? String.format("offset_0x%x", -addr.getOffset())
: String.format("posOff_0x%x", addr.getOffset());
structure.replaceAtOffset(offset, type, spanLength, fieldName, "");
}
return structure;
}
void processVar(Address address, String name, DataType type, int length, int scopeStart) {
if (!address.isStackAddress()) {
return;
}
Address varMax = address.add(length);
if (varMax.compareTo(max) > 0) {
max = varMax;
}
fields.put(address, new FrameField(address, name, type, length, scopeStart));
}
void processRegisterVar(Address address, String name, DataType dataType,
Register register, int scopeStart) {
processVar(address, name, dataType, register.getNumBytes(), scopeStart);
}
void processSavedRegister(Address address, Register register) {
processRegisterVar(address, SAVED_REGISTER_FIELD_PREFIX + register.getName(),
IntegerDataType.dataType,
register, -1);
}
void processSaved(Map<Register, Address> saved) {
for (Entry<Register, Address> entry : saved.entrySet()) {
processSavedRegister(entry.getValue(), entry.getKey());
}
}
void processOfReturn(Address address) {
processRegisterVar(address, RETURN_ADDRESS_FIELD_NAME, PointerDataType.dataType, pc,
Integer.MAX_VALUE);
}
void processFunction(Function function) {
for (Variable variable : function.getStackFrame().getStackVariables()) {
if (variable.getFirstUseOffset() > functionOffset) {
continue;
}
processVariable(variable);
}
}
String prependIfAbsent(String prefix, String name) {
if (name.startsWith(prefix)) {
return name;
}
return prefix + name;
}
void processVariable(Variable variable) {
Varnode[] varnodes = variable.getVariableStorage().getVarnodes();
String name = variable.getName();
if (variable instanceof Parameter) {
name = prependIfAbsent("param_", name);
}
else if (variable instanceof LocalVariable) {
name = prependIfAbsent("local_", name);
}
else {
throw new AssertionError();
}
if (varnodes.length == 1) {
processVarnode(name, variable.getDataType(), varnodes[0],
variable.getFirstUseOffset());
}
else {
for (int i = 0; i < varnodes.length; i++) {
processVarnode(name + "_pt" + i, IntegerDataType.dataType, varnodes[i],
variable.getFirstUseOffset());
}
}
}
void processVarnode(String name, DataType type, Varnode vn, int scopeStart) {
processVar(vn.getAddress(), name, type, vn.getSize(), scopeStart);
}
}

View file

@ -0,0 +1,348 @@
/* ###
* 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.stack;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.DebuggerPcodeUtils;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.Reference;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.DefaultTraceLocation;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.bookmark.TraceBookmark;
import ghidra.trace.model.listing.TraceData;
import ghidra.trace.model.symbol.TraceReference;
import ghidra.trace.util.TraceRegisterUtils;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
/**
* A frame restored from annotations applied to the trace listing
*
* <p>
* This frame operates on {@link WatchValue}s, which are more than sufficient for most GUI elements.
* The unwinding and display of abstract values introduced by custom emulators is yet to be
* complete.
*
* <p>
* This class may become deprecated. It allowed the GUI to use existing analysis that had been
* annotated in this listing. Certainly, that feature will remain, since the annotations are human
* consumable and help make sense of the stack segment. However, when other features need stack
* frames, they may or may not pull those frames from the listing. The trouble comes when a frame
* has 0 length. This can happen when a function has not pushed anything to the stack. On
* architectures without link registers, it should only happen in contingent cases, e.g., the
* analyzer can't find an exit path from the function, and so the return address location is not
* known. However, an invocation of a leaf function on an architecture with a link register may in
* fact have a 0-length frame for its entire life. Ghidra does not cope well with 0-length
* structures, and for good reason. Thus, in most cases, it is recommended to unwind, using
* {@link StackUnwinder}, and cache frames for later re-use. That pattern may be encapsulated in a
* centralized service later.
*
* @see AnalysisUnwoundFrame#applyToListing(int, TaskMonitor)
*/
public class ListingUnwoundFrame extends AbstractUnwoundFrame<WatchValue> {
/**
* Check if the given data unit conventionally represents a frame
*
* <p>
* This is a simple conventional check, but it should rule out accidents. It checks that the
* unit's data type belongs to the {@link StackUnwinder#FRAMES_PATH} category. If the user or
* something else puts data types in that category, it's likely data units using those types may
* be mistaken for frames....
*
* @param data the candidate frame
* @return true if it is likely a frame
*/
public static boolean isFrame(TraceData data) {
DataType type = data.getDataType();
return type.getCategoryPath().equals(StackUnwinder.FRAMES_PATH);
}
/**
* Get the conventional level of the frame represented by the givn data unit
*
* <p>
* Technically, this violates the convention a little in that it parses the comment to determine
* the frame's level. One alternative is to examine the upper (or lower for positive growth)
* addresses for other frames, but that might require strict absence of gaps between frames.
* Another alternative is to encode the level into a property instead, which is more hidden from
* the user.
*
* @param data the frame
* @return the level
*/
private static Integer getLevel(TraceData data) {
// TODO: Should this go into a property instead?
String comment = data.getComment(CodeUnit.PRE_COMMENT);
if (comment == null) {
return null;
}
String[] parts = comment.split("\\s+");
if (parts.length == 0) {
return null;
}
try {
return Integer.parseInt(parts[0]);
}
catch (NumberFormatException e) {
return null;
}
}
private final TraceData frame;
private final int level;
private final Address pcVal;
private final Function function;
private final Address base;
private SavedRegisterMap registerMap;
/**
* Recover a frame from annotations already in the trace listing
*
* @param tool the tool requesting interpretation of the frame, which provides context for
* mapped static programs.
* @param coordinates the coordinates (trace, thread, snap, etc.) to examine
* @param frame the data unit representing the frame
*/
public ListingUnwoundFrame(PluginTool tool, DebuggerCoordinates coordinates, TraceData frame) {
// NOTE: Always unwinding from frame 0
super(tool, coordinates, DebuggerPcodeUtils.buildWatchState(tool, coordinates.frame(0)));
if (!isFrame(frame)) {
throw new IllegalArgumentException("frame does not appear to represent a frame");
}
this.frame = frame;
this.level = loadLevel();
this.pcVal = loadProgramCounter();
this.function = loadFunction();
this.base = loadBasePointer();
}
@Override
public boolean isFake() {
return false;
}
/**
* Get the data unit representing this frame
*
* @return the data unit
*/
public TraceData getData() {
return frame;
}
@Override
protected Address applyBase(long offset) {
return base.add(offset);
}
private int loadLevel() {
Integer l = getLevel(frame);
if (l == null) {
throw new IllegalStateException("Frame has no comment indicating its level");
}
return l;
}
private Address loadProgramCounter() {
for (Reference ref : frame.getReferenceIteratorTo()) {
if (ref.getReferenceType() != RefType.DATA) {
continue;
}
if (ref.getOperandIndex() != StackUnwinder.PC_OP_INDEX) {
continue;
}
return ref.getFromAddress();
}
throw new UnwindException("The program counter reference is missing for the frame!");
}
private Function loadFunction() {
ProgramLocation staticLoc =
mappingService.getOpenMappedLocation(new DefaultTraceLocation(frame.getTrace(), null,
Lifespan.at(coordinates.getSnap()), pcVal));
if (staticLoc == null) {
throw new UnwindException(
"The program containing the frame's function is unavailable," +
" or the mappings have changed.");
}
Function function = staticLoc.getProgram()
.getFunctionManager()
.getFunctionContaining(staticLoc.getAddress());
if (function == null) {
throw new UnwindException(
"The function for the frame is no longer present in the mapped program.");
}
return function;
}
private Address loadBasePointer() {
for (TraceReference ref : frame.getOperandReferences(StackUnwinder.BASE_OP_INDEX)) {
if (ref.getReferenceType() != RefType.DATA) {
continue;
}
return ref.getToAddress();
}
return null;
}
@Override
public String getDescription() {
return frame.getComment(CodeUnit.PRE_COMMENT);
}
@Override
public int getLevel() {
return level;
}
@Override
public Address getProgramCounter() {
return pcVal;
}
@Override
public Function getFunction() {
return function;
}
@Override
public Address getBasePointer() {
return base;
}
@Override
protected Address computeAddressOfReturnAddress() {
int numComponents = frame.getNumComponents();
for (int i = 0; i < numComponents; i++) {
TraceData component = frame.getComponent(i);
if (FrameStructureBuilder.RETURN_ADDRESS_FIELD_NAME.equals(component.getFieldName())) {
return component.getMinAddress();
}
}
return null;
}
@Override
public Address getReturnAddress() {
int numComponents = frame.getNumComponents();
for (int i = 0; i < numComponents; i++) {
TraceData component = frame.getComponent(i);
if (FrameStructureBuilder.RETURN_ADDRESS_FIELD_NAME.equals(component.getFieldName())) {
Object value = component.getValue();
if (value instanceof Address returnAddress) {
return returnAddress;
}
}
}
return null;
}
@Override
public String getWarnings() {
for (TraceBookmark bookmark : frame.getTrace()
.getBookmarkManager()
.getBookmarksAt(frame.getStartSnap(), frame.getMinAddress())) {
if (bookmark.getTypeString().equals(BookmarkType.WARNING)) {
return bookmark.getComment();
}
}
return null;
}
@Override
protected synchronized SavedRegisterMap computeRegisterMap() {
if (registerMap != null) {
return registerMap;
}
TraceData[] innerFrames = new TraceData[level];
for (TraceData inner : trace.getCodeManager()
.definedData()
.get(viewSnap, frame.getMinAddress(), false)) {
if (inner == frame) {
continue;
}
if (!isFrame(inner)) {
break;
}
Integer il = getLevel(inner);
if (il == null) {
break;
}
innerFrames[il] = inner;
}
registerMap = new SavedRegisterMap();
for (TraceData inner : innerFrames) {
mapSavedRegisters(language, inner, registerMap);
}
return registerMap;
}
/**
* Apply the saved registers for a given frame to the given map
*
* <p>
* To be used effectively, all inner frames, excluding the desired context frame, must be
* visited from innermost to outermost, i.e, from level 0 to level n-1. This will ensure that
* the nearest saved value is used for each register. If a register was never saved, then its
* value is presumed still in the actual register.
*
* @param language the language defining the registers to map
* @param frame the data unit for the frame whose saved registers to consider
* @param map the map to modify with saved register information
*/
protected static void mapSavedRegisters(Language language, TraceData frame,
SavedRegisterMap map) {
int numComponents = frame.getNumComponents();
for (int i = 0; i < numComponents; i++) {
TraceData component = frame.getComponent(i);
String name = component.getFieldName();
if (!name.startsWith(FrameStructureBuilder.SAVED_REGISTER_FIELD_PREFIX)) {
continue;
}
String regName =
name.substring(FrameStructureBuilder.SAVED_REGISTER_FIELD_PREFIX.length());
Register register = language.getRegister(regName);
if (register == null) {
Msg.warn(ListingUnwoundFrame.class,
"Unknown register name in saved_register field: " + regName);
continue;
}
map.put(TraceRegisterUtils.rangeForRegister(register), component.getRange());
}
}
/**
* Get the stack entry containing the given address
*
* @param address the address, must already have base applied
* @return the component, or null
*/
public TraceData getComponentContaining(Address address) {
return frame.getComponentContaining((int) address.subtract(frame.getMinAddress()));
}
}

View file

@ -0,0 +1,333 @@
/* ###
* 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.stack;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.async.AsyncFence;
import ghidra.pcode.eval.ArithmeticVarnodeEvaluator;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.pcode.Varnode;
import ghidra.trace.database.DBTraceUtils.AddressRangeMapSetter;
import ghidra.trace.util.TraceRegisterUtils;
/**
* A map from registers to physical stack addresses
*
* <p>
* This is used by an unwound frame to ensure that register reads are translated to stack reads when
* the register's value was saved to the stack by some inner frame. If a register is not saved to
* the stack by such a frame, then its value is read from the register bank.
*/
public class SavedRegisterMap {
/**
* An entry in the map
*/
record SavedEntry(AddressRange from, Address to) {
/**
* The range in register space to be redirected to the stack
*
* @return the "from" range
*/
public AddressRange from() {
return from;
}
/**
* The physical address in the stack segment to which the register is redirected
*
* <p>
* The length of the "to" range is given by the length of the "from" range
*
* @return the "to" address
*/
public Address to() {
return to;
}
/**
* Check if an access should be redirected according to this entry
*
* @param address the address to be accessed
* @return true to redirect, false otherwise
*/
boolean contains(Address address) {
return from.contains(address);
}
/**
* Produce an equivalent entry that redirects only the given new "from" range
*
* @param range the new "from" range, which must be enclosed by the current "from" range
* @return the same or truncated entry
*/
public SavedEntry truncate(AddressRange range) {
int right = (int) from.getMaxAddress().subtract(range.getMaxAddress());
if (right < 0) {
throw new AssertionError("Cannot grow");
}
int left = (int) from.getMinAddress().subtract(range.getMinAddress());
if (left < 0) {
throw new AssertionError("Cannot grow");
}
if (left == 0 && right == 0) {
return this;
}
return new SavedEntry(range, to.add(left));
}
/**
* Produce the same or equivalent entry that redirects at most the given "from" range
*
* @param range the "from" range to intersect
* @return the same or truncated entry
*/
public SavedEntry intersect(AddressRange range) {
AddressRange intersection = from.intersect(range);
return intersection == null ? null : truncate(intersection);
}
/**
* Produce an equivalent entry which excludes any "from" address beyond the given max
*
* @param max the max "from" address
* @return the same or truncated entry
*/
public SavedEntry truncateMax(Address max) {
if (from.getMaxAddress().compareTo(max) <= 0) {
return this;
}
if (from.getMinAddress().compareTo(max) <= 0) {
return truncate(new AddressRangeImpl(from.getMinAddress(), max));
}
return null;
}
/**
* Produce an equivalent entry which exclude any "from" address before the given min
*
* @param min the min "from" address
* @return the same or truncated entry
*/
public SavedEntry truncateMin(Address min) {
if (from.getMinAddress().compareTo(min) >= 0) {
return this;
}
if (from.getMaxAddress().compareTo(min) >= 0) {
return truncate(new AddressRangeImpl(min, from.getMaxAddress()));
}
return null;
}
/**
* The length of the mapped ranges
*
* @return the length
*/
public int size() {
return (int) from.getLength();
}
}
/**
* A class which can set values over a range, ensuring no overlapping entries
*/
protected class SavedEntrySetter
extends AddressRangeMapSetter<Map.Entry<Address, SavedEntry>, SavedEntry> {
@Override
protected AddressRange getRange(Entry<Address, SavedEntry> entry) {
return entry.getValue().from;
}
@Override
protected SavedEntry getValue(Entry<Address, SavedEntry> entry) {
return entry.getValue();
}
@Override
protected void remove(Entry<Address, SavedEntry> entry) {
saved.remove(entry.getKey());
}
@Override
protected Iterable<Entry<Address, SavedEntry>> getIntersecting(Address lower,
Address upper) {
return subMap(lower, upper).entrySet();
}
@Override
protected Entry<Address, SavedEntry> put(AddressRange range, SavedEntry value) {
saved.put(range.getMinAddress(), value.truncate(range));
return null;
}
}
private final NavigableMap<Address, SavedEntry> saved;
private final SavedEntrySetter setter = new SavedEntrySetter();
/**
* Construct an empty (identity) register map
*/
public SavedRegisterMap() {
this.saved = new TreeMap<>();
}
/**
* Copy a given register map
*
* @param saved the map to copy
*/
public SavedRegisterMap(TreeMap<Address, SavedEntry> saved) {
this.saved = new TreeMap<>();
}
private NavigableMap<Address, SavedEntry> subMap(Address lower, Address upper) {
Entry<Address, SavedEntry> adjEnt = saved.floorEntry(lower);
if (adjEnt != null && adjEnt.getValue().contains(upper)) {
lower = adjEnt.getKey();
}
return saved.subMap(lower, true, upper, true);
}
static AddressRange rangeForVarnode(Varnode vn) {
return new AddressRangeImpl(vn.getAddress(), vn.getAddress().add(vn.getSize() - 1));
}
/**
* Map a register to a stack varnode
*
* @param from the register
* @param stackVar the stack varnode
*/
public void put(Register from, Varnode stackVar) {
put(TraceRegisterUtils.rangeForRegister(from), rangeForVarnode(stackVar));
}
/**
* Map the given ranges, which must have equal lengths
*
* @param from the range in register space
* @param to the range in the stack segment
*/
public void put(AddressRange from, AddressRange to) {
if (from.getLength() != to.getLength()) {
throw new IllegalArgumentException("from and to must match in length");
}
put(from, to.getMinAddress());
}
/**
* Map the given range to the given address
*
* @param from the range in register space
* @param to the address in the stack segment
*/
public void put(AddressRange from, Address to) {
setter.set(from, new SavedEntry(from, to));
}
/**
* Copy this register map
*
* @return the copy
*/
public SavedRegisterMap fork() {
return new SavedRegisterMap(new TreeMap<>(saved));
}
private abstract class PieceVisitor<U> {
public U visitVarnode(Address address, int size, U user) {
AddressRange range = new AddressRangeImpl(address, address.add(size - 1));
SavedEntry identity = new SavedEntry(range, address);
for (SavedEntry se : subMap(range.getMinAddress(), range.getMaxAddress()).values()) {
Address prev = se.from.getMinAddress().previous();
if (prev != null) {
SavedEntry idLeft = identity.truncateMax(prev);
if (idLeft != null) {
user = visitPiece(idLeft.to, idLeft.size(), user);
}
}
SavedEntry piece = se.intersect(range);
user = visitPiece(piece.to, piece.size(), user);
Address next = se.from.getMaxAddress().next();
if (next == null) {
return user;
}
identity = identity.truncateMin(next);
}
if (identity != null) {
user = visitPiece(identity.to, identity.size(), user);
}
return user;
}
abstract U visitPiece(Address address, int size, U user);
}
/**
* Get a variable from the given state wrt. this mapping
*
* <p>
* Register reads are redirected to the mapped addresses when applicable.
*
* @param <T> the type of values in the state
* @param state the state to access
* @param address the address of the variable
* @param size the size of the variable
* @param reason a reason for reading the variable
* @return the variable's value
*/
public <T> T getVar(PcodeExecutorState<T> state, Address address, int size, Reason reason) {
PcodeArithmetic<T> arithmetic = state.getArithmetic();
return new PieceVisitor<T>() {
@Override
T visitPiece(Address address, int sz, T value) {
T piece = state.getVar(address, size, true, reason);
return ArithmeticVarnodeEvaluator.catenate(arithmetic, size, value, piece, sz);
}
}.visitVarnode(address, size, arithmetic.fromConst(0, size));
}
/**
* Set a variable using the given editor wrt. this mapping
*
* @param editor the editor
* @param address the address of the variable
* @param bytes the bytes (in language-dependent endianness) giving the variable's value
* @return a future that completes when all editing commands have completed
*/
public CompletableFuture<Void> setVar(StateEditor editor, Address address, byte[] bytes) {
AsyncFence fence = new AsyncFence();
new PieceVisitor<ByteBuffer>() {
@Override
ByteBuffer visitPiece(Address address, int size, ByteBuffer buf) {
byte[] sub = new byte[size];
buf.get(sub);
fence.include(editor.setVariable(address, sub));
return buf;
}
}.visitVarnode(address, bytes.length, ByteBuffer.wrap(bytes));
return fence.ready();
}
}

View file

@ -0,0 +1,199 @@
/* ###
* 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.stack;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Function;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.PcodeOpAST;
/**
* A warning issued while unwinding a stack
*
* <p>
* This is designed to avoid the untamed bucket of messages that a warning set usually turns into.
* In essence, it's still a bucket of messages; however, each type is curated and has some logic for
* how it interacts with other messages and additional instances of itself.
*/
public interface StackUnwindWarning {
/**
* A warning that can be combined with other instances of itself
*
* @param <T> the same type as me (recursive)
*/
interface Combinable<T extends StackUnwindWarning> {
String summarize(Collection<T> all);
}
/**
* Get the message for display
*
* @return the message
*/
String getMessage();
/**
* Check if the given warning can be omitted on account of this warning
*
* <p>
* Usually, the unwinder should be careful not to emit unnecessary warnings, but at times that
* can be difficult, and its proper implementation may complicate the actual unwind logic. This
* allows the unnecessary warning to be removed afterward.
*
* @param other the other warning
* @return true if this warning deems the other unnecessary
*/
default boolean moots(StackUnwindWarning other) {
return false;
}
/**
* The unwind analyzer could not find an exit path from the frame's program counter.
*/
public record NoReturnPathStackUnwindWarning(Address pc) implements StackUnwindWarning {
@Override
public String getMessage() {
return "Could not find a path from " + pc + " to a return";
}
@Override
public boolean moots(StackUnwindWarning other) {
return other instanceof OpaqueReturnPathStackUnwindWarning;
}
}
/**
* The unwind analyzer discovered at last one exit path, but none could be analyzed.
*/
public record OpaqueReturnPathStackUnwindWarning(Address pc) implements StackUnwindWarning {
@Override
public String getMessage() {
return "Could not analyze any path from " + pc + " to a return";
}
}
/**
* While analyzing instructions, the unwind analyzer encountered a call to a function whose
* effect on the stack is unknown.
*
* <p>
* The analyzer does not descend into calls or otherwise implement inter-procedural analysis.
* Instead, it relies on analysis already performed by Ghidra's other analyzers and/or the human
* user. The analyzer will assume a reasonable default.
*/
public record UnknownPurgeStackUnwindWarning(Function function)
implements StackUnwindWarning, Combinable<UnknownPurgeStackUnwindWarning> {
@Override
public String getMessage() {
return "Function " + function + " has unknown/invalid stack purge";
}
@Override
public String summarize(Collection<UnknownPurgeStackUnwindWarning> all) {
Stream<String> sortedDisplay =
all.stream().map(w -> w.function.getName(false)).sorted();
if (all.size() > 7) {
return "Functions " +
sortedDisplay.limit(7).collect(Collectors.joining(", ")) +
", ... have unknown/invalid stack purge.";
}
return "Functions " + sortedDisplay.collect(Collectors.joining(", ")) +
" have unknown/invalid stack purge.";
}
}
/**
* While analyzing instructions, the unwind analyzer encountered a call to a function whose
* convention is not known.
*
* <p>
* The analyzer will assume the default convention for the program's compiler.
*/
public record UnspecifiedConventionStackUnwindWarning(Function function)
implements StackUnwindWarning, Combinable<UnspecifiedConventionStackUnwindWarning> {
@Override
public String getMessage() {
return "Function " + function + " has unspecified convention. Using default";
}
@Override
public String summarize(Collection<UnspecifiedConventionStackUnwindWarning> all) {
Stream<String> sortedDisplay =
all.stream().map(w -> w.function.getName(false)).sorted();
if (all.size() > 7) {
return "Functions " +
sortedDisplay.limit(7).collect(Collectors.joining(", ")) +
", ... have unspecified convention.";
}
return "Functions " + sortedDisplay.collect(Collectors.joining(", ")) +
" have unspecified convention.";
}
}
/**
* While analyzing an indirect call, using the decompiler, the unwind analyzer obtained multiple
* high {@link PcodeOp#CALL} or {@link PcodeOp#CALLIND} p-code ops.
*
* <p>
* Perhaps this should be replaced by an assertion, but failing fast may not be a good approach
* for this case.
*/
public record MultipleHighCallsStackUnwindWarning(List<PcodeOpAST> found)
implements StackUnwindWarning {
@Override
public String getMessage() {
return "Caller generated multiple decompiled calls. How?: " + found;
}
}
/**
* Similar to {@link MultipleHighCallsStackUnwindWarning}, except no high call p-code ops.
*/
public record NoHighCallsStackUnwindWarning(PcodeOp op) implements StackUnwindWarning {
@Override
public String getMessage() {
return "Caller generated no decompiled calls. How?:" + op;
}
}
/**
* While analyzing an indirect call, the target's type was not a function pointer.
*/
public record UnexpectedTargetTypeStackUnwindWarning(DataType type)
implements StackUnwindWarning {
@Override
public String getMessage() {
return "Indirect call target has unexpected type: " + type;
}
}
/**
* While analyzing an indirect call, the signature could not be derived from call-site context.
*/
public record CouldNotRecoverSignatureStackUnwindWarning(PcodeOpAST op)
implements StackUnwindWarning {
@Override
public String getMessage() {
return "Could not recover signature of indirect call: " + op;
}
}
}

View file

@ -0,0 +1,160 @@
/* ###
* 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.stack;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.Combinable;
/**
* A bucket of warnings
*
* <p>
* This collects stack unwind warnings and then culls, and combines them for display.
*/
public class StackUnwindWarningSet implements Collection<StackUnwindWarning> {
private final Collection<StackUnwindWarning> warnings = new LinkedHashSet<>();
/**
* Create a new empty set
*/
public StackUnwindWarningSet() {
}
/**
* Create a new set with the given initial warnings
*
* @param warnings the warnings
*/
public StackUnwindWarningSet(StackUnwindWarning... warnings) {
this.warnings.addAll(Arrays.asList(warnings));
}
/**
* Copy the given set
*
* @param warnings the other set
*/
public StackUnwindWarningSet(Collection<StackUnwindWarning> warnings) {
this.warnings.addAll(warnings);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof StackUnwindWarningSet that)) {
return false;
}
return this.warnings.equals(that.warnings);
}
@Override
public int size() {
return warnings.size();
}
@Override
public boolean isEmpty() {
return warnings.isEmpty();
}
@Override
public boolean contains(Object o) {
return warnings.contains(o);
}
@Override
public Iterator<StackUnwindWarning> iterator() {
return warnings.iterator();
}
@Override
public Object[] toArray() {
return warnings.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return warnings.toArray(a);
}
@Override
public boolean add(StackUnwindWarning e) {
return warnings.add(e);
}
@Override
public boolean remove(Object o) {
return warnings.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return warnings.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends StackUnwindWarning> c) {
return warnings.addAll(c);
}
@Override
public boolean removeAll(Collection<?> c) {
return warnings.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
return warnings.retainAll(c);
}
@Override
public void clear() {
warnings.clear();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public String summarize() {
Set<Class<? extends StackUnwindWarning>> combined = new LinkedHashSet<>();
List<String> lines = new ArrayList<>();
for (StackUnwindWarning w : warnings) {
if (warnings.stream().anyMatch(mw -> mw.moots(w))) {
// do nothing
}
else if (w instanceof Combinable c) {
Class<? extends StackUnwindWarning> cls = w.getClass();
if (!combined.add(cls)) {
continue;
}
Collection all =
warnings.stream().filter(cw -> cls.isInstance(cw)).collect(Collectors.toList());
if (all.size() == 1) {
lines.add(w.getMessage());
}
else {
lines.add(c.summarize(all));
}
}
else {
lines.add(w.getMessage());
}
}
return lines.stream().collect(Collectors.joining("\n"));
}
}

View file

@ -0,0 +1,316 @@
/* ###
* 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.stack;
import java.util.*;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.DebuggerPcodeUtils;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValuePcodeExecutorState;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Reference;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A mechanism for unwinding the stack or parts of it
*
* <p>
* It can start at any frame for which the program counter and stack pointer are known. The choice
* of starting frame is informed by some tradeoffs. For making sense of a specific frame, it might
* be best to start at the nearest frame with confidently recorded PC and SP values. This will
* ensure there is little room for error unwinding from the known frame to the desired frame. For
* retrieving variable values, esp. variables stored in registers, it might be best to start at the
* innermost frame, unless all registers in a nearer frame are confidently recorded. The registers
* in frame 0 are typically recorded with highest confidence. This will ensure that all saved
* register values are properly restored from the stack into the desired frame.
*
* <p>
* The usage pattern is typically:
*
* <pre>
* StackUnwinder unwinder = new StackUnwinder(tool, coordinates.getPlatform());
* for (AnalysisUnwoundFrame<WatchValue> frame : unwinder.frames(coordinates.frame(0), monitor)) {
* // check and/or cache the frame
* }
* </pre>
*
* <p>
* Typically, a frame is sought either by its level or by its function. Once found, several
* operations can be performed with it, including applying annotations to the listing for the stack
* segment (see {@link AnalysisUnwoundFrame#applyToListing(int, TaskMonitor)}) and computing values
* of variables (see {@link UnwoundFrame}.) The iterator unwinds each frame lazily. If the iterator
* stops sooner than expected, consider using {@link #start(DebuggerCoordinates, TaskMonitor)} and
* {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)} directly to get better diagnostics.
*/
public class StackUnwinder {
public static final CategoryPath FRAMES_PATH = new CategoryPath("/Frames");
public static final int PC_OP_INDEX = Reference.MNEMONIC;
public static final int BASE_OP_INDEX = 0;
private static DebuggerStaticMappingService getMappings(PluginTool tool) {
return tool.getService(DebuggerStaticMappingService.class);
}
private static WatchValuePcodeExecutorState getState(PluginTool tool,
DebuggerCoordinates coordinates) {
return DebuggerPcodeUtils.buildWatchState(tool, coordinates);
}
private final PluginTool tool;
private final DebuggerStaticMappingService mappings;
final TracePlatform platform;
final Trace trace;
final Register pc;
final AddressSpace codeSpace;
private final Register sp;
private final AddressSpace stackSpace;
/**
* Construct an unwinder
*
* @param tool the tool with applicable modules opened as programs
* @param platform the trace platform (for registers, spaces, and stack conventions)
*/
public StackUnwinder(PluginTool tool, TracePlatform platform) {
this.tool = tool;
this.mappings = getMappings(tool);
this.platform = platform;
this.trace = platform.getTrace();
this.pc = Objects.requireNonNull(platform.getLanguage().getProgramCounter(),
"Platform must have a program counter");
this.codeSpace = platform.getLanguage().getDefaultSpace();
CompilerSpec compiler = platform.getCompilerSpec();
this.sp = Objects.requireNonNull(compiler.getStackPointer(),
"Platform must have a stack pointer");
this.stackSpace = compiler.getStackBaseSpace();
}
/**
* Begin unwinding frames that can evaluate variables as {@link WatchValue}s
*
* <p>
* While the returned frame is not technically "unwound," it is necessary to derive its base
* pointer in order to evaluate any of its variables and unwind subsequent frames. The returned
* frame has the {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)} method.
*
* @param coordinates the starting coordinates, particularly the frame level
* @param monitor a monitor for cancellation
* @return the frame for the given level
* @throws CancelledException if the monitor is cancelled
*/
public AnalysisUnwoundFrame<WatchValue> start(DebuggerCoordinates coordinates,
TaskMonitor monitor)
throws CancelledException {
if (coordinates.getPlatform() != platform) {
throw new IllegalArgumentException("Not same platform");
}
return start(coordinates, getState(tool, coordinates), monitor);
}
/**
* Begin unwinding frames that can evaluate variables from the given state
*
* <p>
* If is the caller's responsibility to ensure that the given state corresponds to the given
* coordinates. If they do not, the result is undefined.
*
* <p>
* The starting frame's program counter and stack pointer are derived from the trace (in
* coordinates), not the state. The program counter will be retrieved from the
* {@link TraceStackFrame} if available. Otherwise, it will use the value in the register bank
* for the starting frame level. If it is not known, the unwind fails. The static (module)
* mappings are used to find the function containing the program counter, and that function is
* analyzed for its unwind info, wrt. the mapped program counter. See
* {@link UnwindAnalysis#computeUnwindInfo(Address, TaskMonitor)}. Depending on the complexity
* of the function, that analysis may be expensive. If the function cannot be found, the unwind
* fails. If analysis fails, the resulting frame may be incomplete, or the unwind may fail.
* Subsequent frames are handled similarly. See
* {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)}.
*
* @param <T> the type of values in the state, and the result of variable evaluations
* @param coordinates the starting coordinates, particularly the frame level
* @param state the state, which must correspond to the given coordinates
* @param monitor a monitor for cancellation
* @return the frame for the given level
* @throws CancelledException if the monitor is cancelled
*/
public <T> AnalysisUnwoundFrame<T> start(DebuggerCoordinates coordinates,
PcodeExecutorState<T> state, TaskMonitor monitor) throws CancelledException {
return start(coordinates, coordinates.getFrame(), state, monitor);
}
protected <T> AnalysisUnwoundFrame<T> start(DebuggerCoordinates coordinates, int level,
PcodeExecutorState<T> state, TaskMonitor monitor) throws CancelledException {
Address pcVal = null;
TraceThread thread = coordinates.getThread();
long viewSnap = coordinates.getViewSnap();
TraceStack stack = trace.getStackManager().getLatestStack(thread, viewSnap);
if (stack != null) {
TraceStackFrame frame = stack.getFrame(level, false);
if (frame != null) {
pcVal = frame.getProgramCounter(viewSnap);
}
}
TraceMemorySpace regs = Objects.requireNonNull(
trace.getMemoryManager().getMemoryRegisterSpace(thread, level, false),
"Frame must have a register bank");
if (pcVal == null) {
if (TraceMemoryState.KNOWN != regs.getState(platform, viewSnap, pc)) {
throw new UnwindException("Frame must have KNOWN " + pc + " value");
}
pcVal = codeSpace.getAddress(
regs.getValue(platform, viewSnap, pc).getUnsignedValue().longValue());
}
if (TraceMemoryState.KNOWN != regs.getState(platform, viewSnap, sp)) {
throw new UnwindException("Frame must have KNOWN " + sp + " value");
}
Address spVal = stackSpace.getAddress(
regs.getValue(platform, viewSnap, sp).getUnsignedValue().longValue());
return unwind(coordinates, level, pcVal, spVal, state, new SavedRegisterMap(), monitor);
}
/**
* Compute the unwind information for the given program counter and context
*
* <p>
* For the most part, this just translates the dynamic program counter to a static program
* address and then invokes {@link UnwindAnalysis#computeUnwindInfo(Address, TaskMonitor)}.
*
* @param snap the snapshot key (used for mapping the program counter to a program database)
* @param level the frame level, used only for error messages
* @param pcVal the program counter (dynamic)
* @param monitor a monitor for cancellation
* @return the unwind info, possibly incomplete
* @throws CancelledException if the monitor is cancelled
*/
public UnwindInfo computeUnwindInfo(long snap, int level, Address pcVal, TaskMonitor monitor)
throws CancelledException {
// TODO: Try markup in trace first?
ProgramLocation staticPcLoc = mappings == null ? null
: mappings.getOpenMappedLocation(
new DefaultTraceLocation(trace, null, Lifespan.at(snap), pcVal));
if (staticPcLoc == null) {
throw new UnwindException("Cannot find static program for frame " + level + " (" +
pc + "=" + pcVal + ")");
}
Program program = staticPcLoc.getProgram();
Address staticPc = staticPcLoc.getAddress();
// TODO: Cache these?
UnwindAnalysis ua = new UnwindAnalysis(program);
return ua.computeUnwindInfo(staticPc, monitor);
}
<T> AnalysisUnwoundFrame<T> unwind(DebuggerCoordinates coordinates, int level, Address pcVal,
Address spVal, PcodeExecutorState<T> state, SavedRegisterMap registerMap,
TaskMonitor monitor)
throws CancelledException {
try {
UnwindInfo info = computeUnwindInfo(coordinates.getSnap(), level, pcVal, monitor);
return new AnalysisUnwoundFrame<>(tool, coordinates, this, state, level, pcVal, spVal,
info, null, registerMap);
}
catch (UnwindException e) {
return new AnalysisUnwoundFrame<>(tool, coordinates, this, state, level, pcVal, spVal,
null, e, registerMap);
}
}
/**
* An iterable wrapper for {@link #start(DebuggerCoordinates, PcodeExecutorState, TaskMonitor)}
* and {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)}
*
* @param <T> the type of values in the state
* @param coordinates the starting coordinates
* @param state the state
* @param monitor the monitor
* @return the iterable over unwound frames
*/
public <T> Iterable<AnalysisUnwoundFrame<T>> frames(DebuggerCoordinates coordinates,
PcodeExecutorState<T> state, TaskMonitor monitor) {
return new Iterable<>() {
@Override
public Iterator<AnalysisUnwoundFrame<T>> iterator() {
return new Iterator<>() {
AnalysisUnwoundFrame<T> next = tryStart();
@Override
public boolean hasNext() {
return next != null;
}
@Override
public AnalysisUnwoundFrame<T> next() {
AnalysisUnwoundFrame<T> cur = next;
next = tryNext();
return cur;
}
private AnalysisUnwoundFrame<T> tryStart() {
try {
return start(coordinates, state, monitor);
}
catch (UnwindException | CancelledException e) {
return null;
}
}
private AnalysisUnwoundFrame<T> tryNext() {
try {
return next.unwindNext(monitor);
}
catch (NoSuchElementException | UnwindException | CancelledException e) {
return null;
}
}
};
}
};
}
/**
* An iterable wrapper for {@link #start(DebuggerCoordinates, TaskMonitor)} and
* {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)}
*
* @param coordinates the starting coordinates
* @param monitor the monitor
* @return the iterable over unwound frames
*/
public Iterable<AnalysisUnwoundFrame<WatchValue>> frames(DebuggerCoordinates coordinates,
TaskMonitor monitor) {
return frames(coordinates, getState(tool, coordinates), monitor);
}
}

View file

@ -0,0 +1,300 @@
/* ###
* 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.stack;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.Register;
/**
* A symbolic value tailored for stack unwind analysis
*
* <p>
* The goals of stack unwind analysis are 1) to figure the stack depth at a particular instruction,
* 2) to figure the locations of saved registers on the stack, 3) to figure the location of the
* return address, whether in a register or on the stack, and 4) to figure the change in stack depth
* from calling the function. Not surprisingly, these are the fields of {@link UnwindInfo}. To these
* ends, symbols may have only one of the following forms:
*
* <ul>
* <li>An opaque value: {@link OpaqueSym}, to represent expressions too complex.</li>
* <li>A constant: {@link ConstSym}, to fold constants and use as offsets.</li>
* <li>A register: {@link RegisterSym}, to detect saved registers and to generate stack offsets</li>
* <li>A stack offset, i.e., SP + c: {@link StackOffsetSym}, to fold offsets, detect stack depth,
* and to generate stack dereferences</li>
* <li>A dereference of a stack offset, i.e., *(SP + c): {@link StackDerefSym}, to detect restored
* registers and return address location</li>
* </ul>
*
* <p>
* The rules are fairly straightforward:
*
* <ul>
* <li>a:Opaque + b:Any => Opaque()</li>
* <li>a:Const + b:Const => Const(val=a.val + b.val)</li>
* <li>a:Const + b:Register(reg==SP) => Offset(offset=a.val)</li>
* <li>a:Offset: + b:Const => Offset(offset=a.offset + b.val)</li>
* <li>*a:Offset => Deref(offset=a.offset)</li>
* <li>*a:Register(reg==SP) => Deref(offset=0)</li>
* </ul>
*
* <p>
* Some minute operations are omitted for clarity. Any other operation results in Opaque(). There is
* a small fault in that Register(reg=SP) and Offset(offset=0) represent the same thing, but with
* some extra bookkeeping, it's not too terrible. By interpreting p-code and then examining the
* symbolic machine state, simple movement of data between registers and the stack can be
* summarized.
*/
sealed interface Sym {
/**
* Get the opaque symbol
*
* @return the symbol
*/
static Sym opaque() {
return OpaqueSym.OPAQUE;
}
/**
* Add this and another symbol with the given compiler for context
*
* @param cSpec the compiler specification
* @param in2 the second symbol
* @return the resulting symbol
*/
Sym add(CompilerSpec cSpec, Sym in2);
/**
* Subtract another symbol from this with the given compiler for context
*
* @param cSpec the compiler specification
* @param in2 the second symbol
* @return the resulting symbol
*/
default Sym sub(CompilerSpec cSpec, Sym in2) {
return add(cSpec, in2.twosComp());
}
/**
* Negate this symbol
*
* @return the resulting symbol
*/
Sym twosComp();
/**
* Get the size of this symbol with the given compiler for context
*
* @param cSpec the compiler specification
* @return the size in bytes
*/
long sizeOf(CompilerSpec cSpec);
/**
* Get a constant symbol
*
* @param value the value
* @return the constant (with size 8 bytes)
*/
static Sym constant(long value) {
return new ConstSym(value, 8);
}
/**
* When this symbol is used as the offset in a given address space, translate it to the address
* if possible
*
* <p>
* The address will be used by the state to retrieve the appropriate (symbolic) value, possibly
* generating a fresh symbol. If the address is {@link Address#NO_ADDRESS}, then the state will
* yield the opaque symbol. For sets, the state will store the given symbolic value at the
* address. If it is {@link Address#NO_ADDRESS}, then the value is ignored.
*
* @param space the space being dereferenced
* @param cSpec the compiler specification
* @return the address, or {@link Address#NO_ADDRESS}
*/
Address addressIn(AddressSpace space, CompilerSpec cSpec);
/**
* The singleton opaque symbol
*/
public enum OpaqueSym implements Sym {
/**
* Singleton instance
*/
OPAQUE;
@Override
public Sym add(CompilerSpec cSpec, Sym in2) {
return this;
}
@Override
public Sym twosComp() {
return this;
}
@Override
public long sizeOf(CompilerSpec cSpec) {
throw new UnsupportedOperationException();
}
@Override
public Address addressIn(AddressSpace space, CompilerSpec cSpec) {
return Address.NO_ADDRESS;
}
}
/**
* A constant symbol
*/
public record ConstSym(long value, int size) implements Sym {
@Override
public Sym add(CompilerSpec cSpec, Sym in2) {
if (in2 instanceof ConstSym const2) {
return new ConstSym(value + const2.value, size);
}
if (in2 instanceof RegisterSym reg2) {
if (reg2.register() == cSpec.getStackPointer()) {
return new StackOffsetSym(value);
}
return Sym.opaque();
}
if (in2 instanceof StackOffsetSym off2) {
return new StackOffsetSym(value + off2.offset);
}
return Sym.opaque();
}
@Override
public Sym twosComp() {
return new ConstSym(-value, size);
}
@Override
public long sizeOf(CompilerSpec cSpec) {
return size;
}
@Override
public Address addressIn(AddressSpace space, CompilerSpec cSpec) {
if (space.isConstantSpace() || space.isRegisterSpace() || space.isUniqueSpace()) {
return space.getAddress(value);
}
return Address.NO_ADDRESS;
}
}
/**
* A register symbol
*/
public record RegisterSym(Register register) implements Sym {
@Override
public Sym add(CompilerSpec cSpec, Sym in2) {
if (in2 instanceof ConstSym const2) {
return const2.add(cSpec, this);
}
return Sym.opaque();
}
@Override
public Sym twosComp() {
return Sym.opaque();
}
@Override
public long sizeOf(CompilerSpec cSpec) {
return register.getMinimumByteSize();
}
@Override
public Address addressIn(AddressSpace space, CompilerSpec cSpec) {
if (register != cSpec.getStackPointer()) {
return Address.NO_ADDRESS;
}
if (space != cSpec.getStackBaseSpace()) {
return Address.NO_ADDRESS;
}
return cSpec.getStackSpace().getAddress(0);
}
}
/**
* A stack offset symbol
*
* <p>
* This represents a value in the form SP + c, where SP is the stack pointer register and c is a
* constant.
*/
public record StackOffsetSym(long offset) implements Sym {
@Override
public Sym add(CompilerSpec cSpec, Sym in2) {
if (in2 instanceof ConstSym const2) {
return new StackOffsetSym(offset + const2.value());
}
return Sym.opaque();
}
@Override
public Sym twosComp() {
return Sym.opaque();
}
@Override
public long sizeOf(CompilerSpec cSpec) {
return cSpec.getStackPointer().getMinimumByteSize();
}
@Override
public Address addressIn(AddressSpace space, CompilerSpec cSpec) {
if (space != cSpec.getStackBaseSpace()) {
return Address.NO_ADDRESS;
}
return cSpec.getStackSpace().getAddress(offset);
}
}
/**
* A stack dereference symbol
*
* <p>
* This represents a dereferenced {@link StackOffsetSym} (or the dereferenced stack pointer
* register, in which is treated as a stack offset of 0).
*/
public record StackDerefSym(long offset, int size) implements Sym {
@Override
public Sym add(CompilerSpec cSpec, Sym in2) {
return Sym.opaque();
}
@Override
public Sym twosComp() {
return Sym.opaque();
}
@Override
public long sizeOf(CompilerSpec cSpec) {
return size;
}
@Override
public Address addressIn(AddressSpace space, CompilerSpec cSpec) {
return Address.NO_ADDRESS;
}
}
}

View file

@ -0,0 +1,102 @@
/* ###
* 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.stack;
import ghidra.app.plugin.core.debug.stack.Sym.ConstSym;
import ghidra.pcode.exec.ConcretionError;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.*;
import ghidra.program.model.pcode.PcodeOp;
/**
* The interpretation of arithmetic p-code ops in the domain of {@link Sym} for a specific compiler
* specification
*/
class SymPcodeArithmetic implements PcodeArithmetic<Sym> {
private final Language language;
private final CompilerSpec cSpec;
/**
* Construct the arithmetic
*
* @param cSpec the compiler specification
*/
public SymPcodeArithmetic(CompilerSpec cSpec) {
this.cSpec = cSpec;
this.language = cSpec.getLanguage();
}
@Override
public Endian getEndian() {
return language.isBigEndian() ? Endian.BIG : Endian.LITTLE;
}
@Override
public Sym unaryOp(int opcode, int sizeout, int sizein1, Sym in1) {
switch (opcode) {
case PcodeOp.COPY:
return in1;
default:
return Sym.opaque();
}
}
@Override
public Sym binaryOp(int opcode, int sizeout, int sizein1, Sym in1, int sizein2,
Sym in2) {
switch (opcode) {
case PcodeOp.INT_ADD:
return in1.add(cSpec, in2);
case PcodeOp.INT_SUB:
return in1.sub(cSpec, in2);
default:
return Sym.opaque();
}
}
@Override
public Sym modBeforeStore(int sizeout, int sizeinAddress, Sym inAddress,
int sizeinValue, Sym inValue) {
return inValue;
}
@Override
public Sym modAfterLoad(int sizeout, int sizeinAddress, Sym inAddress,
int sizeinValue, Sym inValue) {
return inValue;
}
@Override
public Sym fromConst(byte[] value) {
return new ConstSym(Utils.bytesToLong(value, value.length, language.isBigEndian()),
value.length);
}
@Override
public byte[] toConcrete(Sym value, Purpose purpose) {
if (value instanceof ConstSym constVal) {
return Utils.longToBytes(constVal.value(), constVal.size(), language.isBigEndian());
}
throw new ConcretionError("Not a constant: " + value, purpose);
}
@Override
public long sizeOf(Sym value) {
return value.sizeOf(cSpec);
}
}

View file

@ -0,0 +1,392 @@
/* ###
* 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.stack;
import java.util.*;
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileResults;
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.*;
import ghidra.app.plugin.processors.sleigh.SleighException;
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.data.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.*;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
/**
* The interpreter of p-code ops in the domain of {@link Sym}
*
* <p>
* This is used for static analysis by executing specific basic blocks. As such, it should never be
* expected to interpret a conditional jump. (TODO: This rule might be violated if a fall-through
* instruction has internal conditional branches.... To fix would require breaking the p-code down
* into basic blocks.) We also do not want it to descend into subroutines. Thus, we must treat calls
* differently. Most of the implementation of this class is to attend to function calls, especially,
* indirect calls. For direct calls, it attempts to find the function in the same program (possibly
* in its import table) and derive the resulting stack effects from the database. Failing that, it
* issues warnings and makes reasonable assumptions. For indirect calls, it attempts to decompile
* the caller and examines the call site. If the target's type is known (presumably a function
* pointer), then the stack effects are derived from the signature and its calling convention. If
* not, then it examines the inputs and output (if applicable) to derive a signature and then
* figures the stack effects. In many cases, the stack adjustment is defined solely by the compiler,
* but for the {@code __stdcall} convention prominent in 32-bit x86 binaries for Windows, the input
* parameters must also be examined.
*/
class SymPcodeExecutor extends PcodeExecutor<Sym> {
/**
* Construct an executor for performing stack unwind analysis of a given program
*
* @param program the program to analyze
* @param state the symbolic state
* @param reason a reason to give when reading state
* @param warnings a place to emit warnings
* @param monitor a monitor for analysis, usually decompilation
* @return the executor
*/
public static SymPcodeExecutor forProgram(Program program, SymPcodeExecutorState state,
Reason reason, Set<StackUnwindWarning> warnings, TaskMonitor monitor) {
CompilerSpec cSpec = program.getCompilerSpec();
SleighLanguage language = (SleighLanguage) cSpec.getLanguage();
SymPcodeArithmetic arithmetic = new SymPcodeArithmetic(cSpec);
return new SymPcodeExecutor(program, cSpec, language, arithmetic, state, reason,
warnings, monitor);
}
private final Program program;
private final Register sp;
private final Set<StackUnwindWarning> warnings;
private final TaskMonitor monitor;
private final DecompInterface decomp = new DecompInterface();
// TODO: This could perhaps be moved into AnalysisForPC?
// Meh, as it is, it should only have at most 1 entry
private final Map<Function, HighFunction> decompCache = new HashMap<>();
public SymPcodeExecutor(Program program, CompilerSpec cSpec, SleighLanguage language,
SymPcodeArithmetic arithmetic, SymPcodeExecutorState state, Reason reason,
Set<StackUnwindWarning> warnings, TaskMonitor monitor) {
super(language, arithmetic, state, reason);
this.program = program;
this.sp = cSpec.getStackPointer();
this.warnings = warnings;
this.monitor = monitor;
}
@Override
public void executeCallother(PcodeOp op, PcodeFrame frame,
PcodeUseropLibrary<Sym> library) {
// Do nothing
// TODO: Is there a way to know if a userop affects the stack?
}
/**
* Attempt to figure the stack depth change for a given function
*
* @param function the function whose depth change to compute
* @param warnings a place to emit warnings
* @return the depth change, i.e., change to SP
*/
public static int computeStackChange(Function function, Set<StackUnwindWarning> warnings) {
// TODO: How does this work for varargs functions with stack parameters?
// Seems not much heed is given to signature or call site
// Analyzers set stackPurgeSize, but still, that's on the function, not the site.
// NOTE: It seems stdcall doesn't support varargs, so this issue should not arise.
PrototypeModel convention = function.getCallingConvention();
if (convention == null) {
if (warnings != null) {
warnings.add(new UnspecifiedConventionStackUnwindWarning(function));
}
convention = function.getProgram().getCompilerSpec().getDefaultCallingConvention();
}
int extrapop = convention.getExtrapop();
if (extrapop == PrototypeModel.UNKNOWN_EXTRAPOP) {
throw new PcodeExecutionException(
"Cannot get stack change for function " + function);
}
if (function.isStackPurgeSizeValid()) {
return extrapop + function.getStackPurgeSize();
}
if (warnings != null) {
warnings.add(new UnknownPurgeStackUnwindWarning(function));
}
return extrapop;
}
/**
* Attempt to figure the stack depth change for a given function
*
* @param callee the function being called
* @return the depth change, i.e., change to SP
*/
public int computeStackChange(Function callee) {
return computeStackChange(callee, warnings);
}
@Override
public void executeCall(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<Sym> library) {
Address target = op.getInput(0).getAddress();
Function callee = program.getFunctionManager().getFunctionAt(target);
if (callee == null) {
throw new PcodeExecutionException("Callee at " + target + " is not a function.", frame);
}
String fixupName = callee.getCallFixup();
if (fixupName != null && !"".equals(fixupName)) {
PcodeProgram snippet =
PcodeProgram.fromInject(program, fixupName, InjectPayload.CALLFIXUP_TYPE);
execute(snippet, library);
return;
}
int change = computeStackChange(callee);
adjustStack(change);
}
/**
* Decompile the given low p-code op to its high p-code op
*
* <p>
* Note this is not decompilation of the op in isolation. Decompilation usually requires a
* complete function for context. This will decompile the full containing function then examine
* the resulting high p-code ops at the same address as the given op, which are presumably those
* derived from it. It then seeks a unique call (or call indirect) op.
*
* @param op the low p-code op
* @return the high p-code op
*/
protected PcodeOpAST getHighCallOp(PcodeOp op) {
Address callSite = op.getSeqnum().getTarget();
Function caller = program.getFunctionManager().getFunctionContaining(callSite);
HighFunction hfunc = decompCache.computeIfAbsent(caller, c -> {
decomp.openProgram(program);
DecompileResults results = decomp.decompileFunction(c, 3, monitor);
return results.getHighFunction();
});
List<PcodeOpAST> found = new ArrayList<>();
Iterator<PcodeOpAST> oit = hfunc.getPcodeOps(callSite);
while (oit.hasNext()) {
PcodeOpAST hop = oit.next();
if (hop.getOpcode() == PcodeOp.CALLIND || hop.getOpcode() == PcodeOp.CALL) {
found.add(hop);
}
}
if (found.size() == 1) {
return found.get(0);
}
if (found.size() > 1) {
warnings.add(new MultipleHighCallsStackUnwindWarning(found));
return found.get(0);
}
warnings.add(new NoHighCallsStackUnwindWarning(op));
return null;
}
/**
* Derive the signature from the call op's target (first input) type
*
* @param op the call or call indirect op
* @return the signature if successful, or null
*/
protected FunctionSignature getSignatureFromTargetPointerType(PcodeOpAST op) {
VarnodeAST target = (VarnodeAST) op.getInput(0);
DataType dataType = target.getHigh().getDataType();
if (!(dataType instanceof Pointer ptrType)) {
warnings.add(new UnexpectedTargetTypeStackUnwindWarning(dataType));
return null;
}
if (!(ptrType.getDataType() instanceof FunctionSignature sigType)) {
warnings.add(new UnexpectedTargetTypeStackUnwindWarning(dataType));
return null;
}
return sigType;
}
/**
* Derive the signature from the call op's parameters (second and on inputs) types
*
* @param op the call or call indirect op
* @return the signature if successful, or null
*/
protected FunctionSignature getSignatureFromContextAtCallSite(PcodeOpAST op) {
FunctionDefinitionDataType sig = new FunctionDefinitionDataType("__indirect");
sig.setReturnType(op.getOutput().getHigh().getDataType());
// input 0 is the target, so drop it.
int numInputs = op.getNumInputs();
Parameter[] params = new Parameter[numInputs - 1];
ParameterDefinition[] arguments = new ParameterDefinition[numInputs - 1];
for (int i = 1; i < numInputs; i++) {
Varnode input = op.getInput(i);
HighVariable highVar = input.getHigh();
try {
/**
* NOTE: Not specifying storage, since: 1) It's not germane to the function
* signature, and 2) It may require chasing use-def chains through uniques.
*/
params[i - 1] = new ParameterImpl("param_" + i, highVar.getDataType(),
/*new VariableStorage(program, input),*/ program);
}
catch (InvalidInputException e) {
throw new AssertionError(e);
}
arguments[i - 1] = new ParameterDefinitionImpl("param_" + i,
input.getHigh().getDataType(), "generated");
}
sig.setArguments(arguments);
sig.setComment("generated");
// TODO: Does the decompiler communicate the inferred calling convention?
try {
PrototypeModel convention = program.getCompilerSpec().findBestCallingConvention(params);
sig.setGenericCallingConvention(convention.getGenericCallingConvention());
}
catch (SleighException e) {
// Whatever, just leave sig at "unknown"
}
return sig;
}
/**
* Derive the function signature for an indirect call
*
* <p>
* This first examines the target's type. Failing that, it examines the parameter and return
* types at the call site.
*
* @param lowOp the low p-code op
* @return the signature if successful, or null
*/
protected FunctionSignature getSignatureOfIndirectCall(PcodeOp lowOp) {
PcodeOpAST callOp = getHighCallOp(lowOp);
if (callOp == null) {
return null;
}
FunctionSignature signature = getSignatureFromTargetPointerType(callOp);
if (signature != null) {
return signature;
}
signature = getSignatureFromContextAtCallSite(callOp);
if (signature != null) {
return signature;
}
warnings.add(new CouldNotRecoverSignatureStackUnwindWarning(callOp));
return null;
}
/**
* Assuming the convention represents {@code __stdcall} determine the stack depth change for the
* given signature
*
* @param convention the convention, which must represent {@code __stdcall}
* @param sig the signature
* @return the depth
*/
protected int computeStdcallExtrapop(PrototypeModel convention, FunctionSignature sig) {
ParameterDefinition[] arguments = sig.getArguments();
DataType[] types = new DataType[arguments.length + 1];
types[0] = sig.getReturnType();
for (int i = 0; i < arguments.length; i++) {
types[i + 1] = arguments[0].getDataType();
}
VariableStorage[] vsLocs = convention.getStorageLocations(program, types, false);
Address min = null;
Address max = null; // Exclusive
for (VariableStorage vs : vsLocs) {
if (vs == null) {
continue;
}
for (Varnode vn : vs.getVarnodes()) {
if (!vn.getAddress().isStackAddress()) {
continue;
}
Address vnMin = vn.getAddress();
Address vnMax = vnMin.add(vn.getSize());
min = min == null || vnMin.compareTo(min) < 0 ? vnMin : min;
max = max == null || vnMax.compareTo(max) > 0 ? vnMax : max;
}
}
int purge = max == null ? 0 : (int) max.subtract(min);
// AFAIK, this stdcall only applies to x86, so presume return address on stack
return purge + program.getLanguage().getProgramCounter().getNumBytes();
}
/**
* Compute the stack change for an indirect call
*
* @param op the low p-code op
* @return the depth change
*/
protected int computeStackChangeIndirect(PcodeOp op) {
FunctionSignature sig = getSignatureOfIndirectCall(op);
if (sig == null) {
int extrapop = program.getCompilerSpec().getDefaultCallingConvention().getExtrapop();
if (extrapop != PrototypeModel.UNKNOWN_EXTRAPOP) {
return extrapop;
}
throw new PcodeExecutionException("Cannot get stack change for indirect call: " + op);
}
PrototypeModel convention =
program.getCompilerSpec().matchConvention(sig.getGenericCallingConvention());
if (convention == null) {
warnings.add(new UnspecifiedConventionStackUnwindWarning(null));
convention = program.getCompilerSpec().getDefaultCallingConvention();
}
int extrapop = convention.getExtrapop();
if (extrapop != PrototypeModel.UNKNOWN_EXTRAPOP) {
return extrapop;
}
return computeStdcallExtrapop(convention, sig);
}
/**
* Apply the given stack change to the machine state
*
* <p>
* The overall effect is simply: {@code SP = SP + change}
*
* @param change the change
*/
protected void adjustStack(int change) {
Sym spVal = state.getVar(sp, reason);
int size = sp.getNumBytes();
Sym spChanged = arithmetic.binaryOp(PcodeOp.INT_ADD, size, size, spVal, size,
arithmetic.fromConst(change, size));
state.setVar(sp, spChanged);
}
@Override
public void executeIndirectCall(PcodeOp op, PcodeFrame frame) {
int change = computeStackChangeIndirect(op);
assert change != PrototypeModel.UNKNOWN_EXTRAPOP;
adjustStack(change);
}
@Override
public void executeConditionalBranch(PcodeOp op, PcodeFrame frame) {
// This should always end a basic block, so just do nothing
}
@Override
protected void doExecuteIndirectBranch(PcodeOp op, PcodeFrame frame) {
// This should always end a basic block, so just do nothing
}
}

View file

@ -0,0 +1,282 @@
/* ###
* 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.stack;
import java.util.HashMap;
import java.util.Map;
import ghidra.app.plugin.core.debug.stack.Sym.*;
import ghidra.app.plugin.core.debug.stack.SymStateSpace.SymEntry;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.mem.MemoryBufferImpl;
import ghidra.util.Msg;
/**
* A symbolic state for stack unwind analysis
*
* <p>
* This state can store symbols in stack, register, and unique spaces. It ignores physical memory,
* since that is not typically used as temporary storage when moving values between registers and
* stack. When an address is read that does not have an entry, the state will generate a fresh
* symbol representing that address, if applicable.
*/
public class SymPcodeExecutorState implements PcodeExecutorState<Sym> {
private final Program program;
private final CompilerSpec cSpec;
private final Language language;
private final SymPcodeArithmetic arithmetic;
private final SymStateSpace stackSpace;
private final SymStateSpace registerSpace;
private final SymStateSpace uniqueSpace;
/**
* Construct a new state for the given program
*/
public SymPcodeExecutorState(Program program) {
this.program = program;
this.cSpec = program.getCompilerSpec();
this.language = cSpec.getLanguage();
this.arithmetic = new SymPcodeArithmetic(cSpec);
this.stackSpace = new SymStateSpace();
this.registerSpace = new SymStateSpace();
this.uniqueSpace = new SymStateSpace();
}
protected SymPcodeExecutorState(Program program, SymPcodeArithmetic arithmetic,
SymStateSpace stackSpace, SymStateSpace registerSpace, SymStateSpace uniqueSpace) {
this.program = program;
this.cSpec = program.getCompilerSpec();
this.language = cSpec.getLanguage();
this.arithmetic = new SymPcodeArithmetic(cSpec);
this.stackSpace = stackSpace;
this.registerSpace = registerSpace;
this.uniqueSpace = uniqueSpace;
}
@Override
public String toString() {
return String.format("""
%s[
cSpec=%s
stack=%s
registers=%s
unique=%s
]
""", getClass().getSimpleName(),
cSpec.toString(),
stackSpace.toString(" ", language),
registerSpace.toString(" ", language),
uniqueSpace.toString(" ", language));
}
@Override
public Language getLanguage() {
return language;
}
@Override
public PcodeArithmetic<Sym> getArithmetic() {
return arithmetic;
}
@Override
public void setVar(AddressSpace space, Sym offset, int size, boolean quantize,
Sym val) {
Address address = offset.addressIn(space, cSpec);
if (address.isRegisterAddress()) {
registerSpace.set(address, size, val);
}
else if (address.isUniqueAddress()) {
uniqueSpace.set(address, size, val);
}
else if (address.isConstantAddress()) {
throw new IllegalArgumentException();
}
else if (address.isStackAddress()) {
stackSpace.set(address, size, val);
}
else {
Msg.trace(this, "Ignoring set: space=" + space + ",offset=" + offset + ",size=" + size +
",val=" + val);
}
}
@Override
public Sym getVar(AddressSpace space, Sym offset, int size, boolean quantize,
Reason reason) {
Address address = offset.addressIn(space, cSpec);
if (address.isRegisterAddress()) {
return registerSpace.get(address, size, arithmetic, language);
}
else if (address.isUniqueAddress()) {
return uniqueSpace.get(address, size, arithmetic, language);
}
else if (address.isConstantAddress()) {
return offset;
}
else if (address.isStackAddress()) {
return stackSpace.get(address, size, arithmetic, language);
}
return Sym.opaque();
}
@Override
public Map<Register, Sym> getRegisterValues() {
return Map.of();
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return new MemoryBufferImpl(program.getMemory(), address);
}
@Override
public void clear() {
registerSpace.clear();
stackSpace.clear();
}
@Override
public SymPcodeExecutorState fork() {
return new SymPcodeExecutorState(program, arithmetic, stackSpace.fork(),
registerSpace.fork(), uniqueSpace.fork());
}
/**
* Create a new state whose registers are forked from those of this state
*/
public SymPcodeExecutorState forkRegs() {
return new SymPcodeExecutorState(program, arithmetic, new SymStateSpace(),
registerSpace.fork(), new SymStateSpace());
}
public void dump() {
System.err.println("Registers: ");
registerSpace.dump(" ", language);
System.err.println("Unique: ");
uniqueSpace.dump(" ", language);
System.err.println("Stack: ");
stackSpace.dump(" ", language);
}
/**
* Examine this state's SP for the overall change in stack depth
*
* <p>
* There are two cases:
* <ul>
* <li>SP:Register(reg==SP) => depth is 0</li>
* <li>SP:Offset => depth is SP.offset</li>
* </ul>
*
* <p>
* If SP has any other form, the depth is unknown
*
* @return the depth, or null if not known
*/
public Long computeStackDepth() {
Register sp = cSpec.getStackPointer();
Sym expr = getVar(sp, Reason.INSPECT);
if (expr instanceof RegisterSym regVar && regVar.register() == sp) {
return 0L;
}
if (expr instanceof StackOffsetSym stackOff) {
return stackOff.offset();
}
return null;
}
/**
* Examine this state's PC for the location of the return address
*
* <p>
* There are two cases:
* <ul>
* <li>PC:Register => location is PC.reg.address
* <li>PC:Deref => location is [Stack]:PC.offset
* </ul>
*
* @return
*/
public Address computeAddressOfReturn() {
Sym expr = getVar(language.getProgramCounter(), Reason.INSPECT);
if (expr instanceof StackDerefSym stackVar) {
return cSpec.getStackSpace().getAddress(stackVar.offset());
}
if (expr instanceof RegisterSym regVar) {
return regVar.register().getAddress();
}
return null;
}
/**
* Compute a map of (saved) registers
*
* <p>
* Any entry of the form (addr, v:Register) is collected as (v.register, addr). Note that the
* size of the stack entry is implied by the size of the register.
*
* @return the map from register to address
*/
public Map<Register, Address> computeMapUsingStack() {
Map<Register, Address> result = new HashMap<>();
for (SymEntry ent : stackSpace.map.values()) {
if (ent.isTruncated()) {
continue;
}
if (!(ent.sym() instanceof RegisterSym regVar)) {
continue;
}
result.put(regVar.register(), ent.entRange().getMinAddress());
}
return result;
}
/**
* Compute the map of (restored) registers
*
* <p>
* Any entry of the form (reg, v:Deref) is collected as (reg, [Stack]:v.offset). Note that the
* size of the stack entry is implied by the size of the register.
*
* @return
*/
public Map<Register, Address> computeMapUsingRegisters() {
Map<Register, Address> result = new HashMap<>();
for (SymEntry ent : registerSpace.map.values()) {
if (ent.isTruncated()) {
continue;
}
if (!(ent.sym() instanceof StackDerefSym stackVar)) {
continue;
}
Register register = ent.getRegister(language);
if (register == null) {
continue;
}
result.put(register, cSpec.getStackSpace().getAddress(stackVar.offset()));
}
return result;
}
}

View file

@ -0,0 +1,322 @@
/* ###
* 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.stack;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.stack.Sym.RegisterSym;
import ghidra.app.plugin.core.debug.stack.Sym.StackDerefSym;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.trace.database.DBTraceUtils.AddressRangeMapSetter;
import ghidra.util.Msg;
/**
* The portion of a {@link SymPcodeExecutorState} associated with a specific {@link AddressSpace}.
*/
public class SymStateSpace {
/**
* A symbolic entry in the state
*
* <p>
* It's possible the entry becomes truncated if another entry set later would overlap. Thus, it
* is necessary to remember the original range and the effective range as well as the symbol.
*/
record SymEntry(AddressRange entRange, AddressRange symRange, Sym sym) {
/**
* Create a new entry for the given range and symbol
*
* @param range the range
* @param sym the symbol
*/
SymEntry(AddressRange range, Sym sym) {
this(range, range, sym);
}
/**
* Create a new entry for the given range and symbol
*
* @param start the min address of the range
* @param size the size in bytes of the range
* @param sym the symbol
* @throws AddressOverflowException
*/
SymEntry(Address start, int size, Sym sym) throws AddressOverflowException {
this(new AddressRangeImpl(start, size), sym);
}
/**
* Render a human-friendly string, substituting register names for ranges where appropriate
*
* @param language optional language. If omitted, no register names are substituted
* @return the string
*/
public String toString(Language language) {
Register reg = getRegister(language);
if (reg == null) {
return toString();
}
return String.format("%s[entRanage=%s,symRange=%s,sym=%s]", getClass().getSimpleName(),
reg, symRange, sym);
}
/**
* Check if this entry has been truncated
*
* @return true if the effective range is equal to the original range
*/
boolean isTruncated() {
return !entRange.equals(symRange);
}
/**
* Get the register in the language this entry's range represents
*
* @param language the language
* @return the register, or null
*/
Register getRegister(Language language) {
return language.getRegister(entRange.getMinAddress(), (int) entRange.getLength());
}
/**
* Create a new entry that represents a truncation of this entry
*
* @param range the subrange
* @return the new entry
*/
SymEntry truncate(AddressRange range) {
if (entRange.getMinAddress().compareTo(range.getMinAddress()) > 0) {
throw new AssertionError();
}
if (entRange.getMaxAddress().compareTo(range.getMaxAddress()) < 0) {
throw new AssertionError();
}
return new SymEntry(range, symRange, sym);
}
/**
* Check if the effective range contains the given address
*
* @param address the address
* @return true if contained by this entry
*/
boolean contains(Address address) {
return entRange.contains(address);
}
/**
* Get the symbol from this entry, applying appropriate arithmetic for truncation, if
* applicable.
*
* @param range the range to extract
* @param arithmetic the arithmetic for extracting the appropriate bytes
* @return the symbol
*/
Sym extract(AddressRange range, SymPcodeArithmetic arithmetic) {
if (symRange.equals(range)) {
return sym;
}
// TODO: Implement the extraction logic. Not sure it matters, anyway
return Sym.opaque();
/* long shift = arithmetic.getEndian().isBigEndian()
? symRange.getMaxAddress().subtract(range.getMaxAddress())
: range.getMinAddress().subtract(symRange.getMinAddress()); */
}
}
/**
* A setter that knows how to remove or truncate overlapping entries
*/
protected class ExprMapSetter
extends AddressRangeMapSetter<Map.Entry<Address, SymEntry>, SymEntry> {
@Override
protected AddressRange getRange(Entry<Address, SymEntry> entry) {
return entry.getValue().entRange;
}
@Override
protected SymEntry getValue(Entry<Address, SymEntry> entry) {
return entry.getValue();
}
@Override
protected void remove(Entry<Address, SymEntry> entry) {
map.remove(entry.getKey());
}
@Override
protected Iterable<Entry<Address, SymEntry>> getIntersecting(Address lower,
Address upper) {
return subMap(lower, upper).entrySet();
}
@Override
protected Entry<Address, SymEntry> put(AddressRange range, SymEntry value) {
map.put(range.getMinAddress(), value.truncate(range));
return null;
}
}
final NavigableMap<Address, SymEntry> map;
private final ExprMapSetter setter = new ExprMapSetter();
/**
* Construct a new empty space
*/
public SymStateSpace() {
this.map = new TreeMap<>();
}
/**
* Construct a space with the given map (for forking)
*
* @param map the map
*/
protected SymStateSpace(NavigableMap<Address, SymEntry> map) {
this.map = map;
}
@Override
public String toString() {
return toString("", null);
}
/**
* Render a human-friendly string showing this state space
*
* @param indent the indentation
* @param language the language, optional, for register substitution
* @return the string
*/
public String toString(String indent, Language language) {
return map.values()
.stream()
.map(se -> se.toString(language))
.collect(Collectors.joining("\n" + indent, indent + "{", "\n" + indent + "}"));
}
private NavigableMap<Address, SymEntry> subMap(Address lower, Address upper) {
Entry<Address, SymEntry> adjEnt = map.floorEntry(lower);
if (adjEnt != null && adjEnt.getValue().contains(upper)) {
lower = adjEnt.getKey();
}
return map.subMap(lower, true, upper, true);
}
/**
* Set a value in this space
*
* @param address the address of the entry
* @param size the size of the entry
* @param sym the symbol
*/
public void set(Address address, int size, Sym sym) {
SymEntry entry;
try {
entry = new SymEntry(address, size, sym);
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
setter.set(entry.entRange, entry);
}
/**
* Get a value from this space
*
* @param address the address of the value
* @param size the size of the value
* @param arithmetic the arithmetic, in case truncation is necessary
* @param language the language, for generating symbols
* @return the symbol
*/
public Sym get(Address address, int size, SymPcodeArithmetic arithmetic, Language language) {
AddressRange range;
range = new AddressRangeImpl(address, address.add(size - 1));
Sym result = null;
Address expectedNext = null;
for (SymEntry ent : subMap(range.getMinAddress(), range.getMaxAddress()).values()) {
if (ent.entRange.equals(range)) {
return ent.extract(range, arithmetic);
}
AddressRange intersection = range.intersect(ent.entRange);
if (expectedNext != null && !expectedNext.equals(intersection.getMinAddress())) {
return Sym.opaque();
}
expectedNext = intersection.getMaxAddress().next();
Sym piece = ent.extract(intersection, arithmetic);
piece =
arithmetic.unaryOp(PcodeOp.INT_ZEXT, size, (int) intersection.getLength(), piece);
if (result == null) {
result = piece;
continue;
}
result = arithmetic.binaryOp(PcodeOp.INT_OR, size, size, piece, size, result);
}
if (result != null) {
return result;
}
if (address.isRegisterAddress()) {
Register register = language.getRegister(address, size);
if (register == null) {
Msg.warn(this, "Could not figure register: address=" + address + ",size=" + size);
return Sym.opaque();
}
return new RegisterSym(register);
}
if (address.isStackAddress()) {
return new StackDerefSym(address.getOffset(), size);
}
return Sym.opaque();
}
/**
* Reset this state
*
* <p>
* Clears the state as if it were new. That is, it will generate fresh symbols for reads without
* existing entries.
*/
public void clear() {
map.clear();
}
public void dump(String prefix, Language language) {
for (SymEntry ent : map.values()) {
Register register = ent.getRegister(language);
if (register != null) {
System.err.println(prefix + register + " = " + ent.sym);
continue;
}
System.err.println(prefix + ent);
}
}
/**
* Copy this state
*
* @return the new state
*/
public SymStateSpace fork() {
return new SymStateSpace(new TreeMap<>(map));
}
}

View file

@ -0,0 +1,516 @@
/* ###
* 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.stack;
import java.util.*;
import java.util.stream.Collectors;
import generic.Unique;
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.NoReturnPathStackUnwindWarning;
import ghidra.app.plugin.core.debug.stack.StackUnwindWarning.OpaqueReturnPathStackUnwindWarning;
import ghidra.graph.*;
import ghidra.graph.algo.DijkstraShortestPathsAlgorithm;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeProgram;
import ghidra.pcode.exec.PcodeUseropLibrary;
import ghidra.program.model.address.*;
import ghidra.program.model.block.*;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.FlowType;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A class for analyzing a given program's functions as a means of unwinding their stack frames in
* traces, possibly for live debug sessions.
*
* @see StackUnwinder
*/
public class UnwindAnalysis {
/**
* A graph used for finding execution paths from function entry through the program counter to a
* return.
*
* <p>
* This just wraps {@link UnwindAnalysis#blockModel} in a {@link GImplicitDirectedGraph}.
*/
class BlockGraph implements GImplicitDirectedGraph<BlockVertex, BlockEdge> {
final TaskMonitor monitor;
public BlockGraph(TaskMonitor monitor) {
this.monitor = monitor;
}
List<BlockEdge> toEdgeList(CodeBlockReferenceIterator it) throws CancelledException {
List<BlockEdge> result = new ArrayList<>();
while (it.hasNext()) {
CodeBlockReference ref = it.next();
if (ref.getFlowType().isCall()) {
continue;
}
result.add(new BlockEdge(ref));
}
return result;
}
@Override
public Collection<BlockEdge> getInEdges(BlockVertex v) {
try {
return toEdgeList(blockModel.getSources(v.block, monitor));
}
catch (CancelledException e) {
throw new AssertionError(e);
}
}
@Override
public Collection<BlockEdge> getOutEdges(BlockVertex v) {
try {
return toEdgeList(blockModel.getDestinations(v.block, monitor));
}
catch (CancelledException e) {
throw new AssertionError(e);
}
}
@Override
public GDirectedGraph<BlockVertex, BlockEdge> copy() {
throw new UnsupportedOperationException();
}
}
/**
* Wrap a {@link CodeBlock}
*/
record BlockVertex(CodeBlock block) {
}
/**
* Wrap a {@link CodeBlockReference}
*/
record BlockEdge(CodeBlockReference ref)
implements GEdge<BlockVertex> {
@Override
public BlockVertex getStart() {
return new BlockVertex(ref.getSourceBlock());
}
@Override
public BlockVertex getEnd() {
return new BlockVertex(ref.getDestinationBlock());
}
}
private final Program program;
private final CodeBlockModel blockModel;
/**
* Prepare analysis on the given program
*
* @param program the program
*/
public UnwindAnalysis(Program program) {
this.program = program;
// PartitionCodeSubModel seems to call each subroutine a block
this.blockModel = new BasicBlockModel(program);
}
/**
* The analysis surrounding a single frame for a given program counter, i.e., instruction
* address
*/
class AnalysisForPC {
private final Address pc;
private final TaskMonitor monitor;
private final Function function;
private final BlockGraph graph;
private final BlockVertex pcBlock;
private final DijkstraShortestPathsAlgorithm<BlockVertex, BlockEdge> pathFinder;
private final Set<StackUnwindWarning> warnings = new HashSet<>();
/**
* Begin analysis for unwinding a frame, knowing only the program counter for that frame
*
* <p>
* This will look up the function containing the program counter. If there's isn't one, then
* this analysis cannot proceed.
*
* @param pc the program counter
* @param monitor a monitor for progress and cancellation
* @throws CancelledException if the monitor cancels the analysis
*/
public AnalysisForPC(Address pc, TaskMonitor monitor) throws CancelledException {
this.pc = pc;
this.function = program.getFunctionManager().getFunctionContaining(pc);
if (function == null) {
throw new UnwindException("No function contains " + pc);
}
this.monitor = monitor;
this.graph = new BlockGraph(monitor);
this.pathFinder =
new DijkstraShortestPathsAlgorithm<>(graph, GEdgeWeightMetric.unitMetric());
this.pcBlock = new BlockVertex(
Unique.assertAtMostOne(blockModel.getCodeBlocksContaining(pc, monitor)));
}
/**
* Compute the shortest path(s) from function entry to the program counter
*
* @return the paths. There's usually only one
* @throws CancelledException if the monitor cancels the analysis
*/
public Collection<Deque<BlockEdge>> getEntryPaths() throws CancelledException {
BlockVertex entryBlock = new BlockVertex(Unique.assertAtMostOne(
blockModel.getCodeBlocksContaining(function.getEntryPoint(), monitor)));
return pathFinder.computeOptimalPaths(entryBlock, pcBlock);
}
/**
* Find terminating blocks that return from the function
*
* <p>
* If there are none, then the function is presumed non-returning. Analysis will not be
* complete.
*
* <p>
* For non-returning functions, we can still use the entry path. From limited
* experimentation, it seems the extra saved-register entries are not problematic. One case
* is register parameters that the function saves to the stack for its own sake. While
* restoring those would technically be incorrect, it doesn't seem problematic to do so.
* This doesn't help us compute {@link UnwindInfo#adjust}, but that might just be
* {@link PrototypeModel#getExtrapop()}....
*
* @return the blocks
* @throws CancelledException if the monitor cancels the analysis
*/
public Collection<BlockVertex> getReturnBlocks()
throws CancelledException {
// TODO: What to do if function is non-returning?
List<BlockVertex> returns = new ArrayList<>();
for (CodeBlock funcBlock : blockModel.getCodeBlocksContaining(function.getBody(),
monitor)) {
FlowType flowType = funcBlock.getFlowType();
// Omit CALL_TERMINATORs, since those are calls to non-returning functions
// TODO: This also omits tail calls by JMP
if (flowType.isTerminal() && !flowType.isCall()) {
returns.add(new BlockVertex(funcBlock));
}
}
return returns;
}
/**
* Compute the shortest path(s) from the program counter to a function return
*
* <p>
* Because the shortest-path API does not readily permit the searching for the shortest path
* from one vertex to many vertices, we instead search for the shortest path from the
* program counter to each of the found function returns, collect all the resulting paths,
* and sort. Still, usually only the first (shortest of all) is needed.
*
* @return the paths sorted shortest first
* @throws CancelledException if the monitor cancels the analysis
*/
public Collection<Deque<BlockEdge>> getExitsPaths() throws CancelledException {
return getReturnBlocks().stream()
.flatMap(rb -> pathFinder.computeOptimalPaths(pcBlock, rb).stream())
.sorted(Comparator.comparing(d -> d.size()))
.collect(Collectors.toList());
}
/**
* Execute the instructions, ordered by address, in the given address set
*
* @param exec the executor
* @param set the address set indicating the instructions to execute
* @throws CancelledException if the monitor cancels the analysis
*/
public void executeSet(SymPcodeExecutor exec, AddressSetView set)
throws CancelledException {
for (Instruction i : program.getListing().getInstructions(set, true)) {
monitor.checkCanceled();
exec.execute(PcodeProgram.fromInstruction(i, true), PcodeUseropLibrary.nil());
}
}
/**
* Execute the instructions in the given block preceding the given address
*
* <p>
* The instruction at {@code to} is omitted.
*
* @param exec the executor
* @param block the block whose instructions to execute
* @param to the ending address, usually the program counter
* @throws CancelledException if the monitor cancels the analysis
*/
public void executeBlockTo(SymPcodeExecutor exec, CodeBlock block, Address to)
throws CancelledException {
AddressSet set =
block.intersectRange(to.getAddressSpace().getMinAddress(), to.previous());
executeSet(exec, set);
}
/**
* Execute the instructions in the given block
*
* @param exec the executor
* @param block the block whose instructions to execute
* @throws CancelledException if the monitor cancels the analysis
*/
public void executeBlock(SymPcodeExecutor exec, CodeBlock block)
throws CancelledException {
executeSet(exec, block);
}
/**
* Execute the instructions in the given block starting at the given address
*
* <p>
* Instructions preceding the given address are omitted.
*
* @param exec the executor
* @param block the block whose instructions to execute
* @param from the starting address, usually the program counter
* @throws CancelledException if the monitor cancels the analysis
*/
public void executeBlockFrom(SymPcodeExecutor exec, CodeBlock block, Address from)
throws CancelledException {
AddressSet set = block.intersectRange(from, from.getAddressSpace().getMaxAddress());
executeSet(exec, set);
}
/**
* Execute the instructions along the given path to a destination block, omitting the final
* destination block.
*
* <p>
* The given path is usually from the function entry to the block containing the program
* counter. The final block is omitted, since it should only be partially executed, i.e.,
* using {@link #executeBlockTo(SymPcodeExecutor, CodeBlock, Address)}.
*
* @param exec the executor
* @param to the path to the program counter
* @see #executeToPc(Deque)
* @throws CancelledException if the monitor cancels the analysis
*/
public void executePathTo(SymPcodeExecutor exec, Deque<BlockEdge> to)
throws CancelledException {
for (BlockEdge et : to) {
executeBlock(exec, et.ref.getSourceBlock());
}
}
/**
* Execute the instructions along the given path from a source block, omitting the initial
* source block.
*
* <p>
* The given path us usually from the block containing the program counter to a function
* return. The initial source is omitted, since it should only be partially executed, i.e.,
* using {@link #executeBlockFrom(SymPcodeExecutor, CodeBlock, Address)}.
*
* @param exec the executor
* @param from the path from the program counter
* @see #executeFromPc(SymPcodeExecutorState, Deque)
* @throws CancelledException if the monitor cancels the analysis
*/
public void executePathFrom(SymPcodeExecutor exec, Deque<BlockEdge> from)
throws CancelledException {
for (BlockEdge ef : from) {
executeBlock(exec, ef.ref.getDestinationBlock());
}
}
/**
* Execute the instructions from entry to the program counter, using the given path
*
* <p>
* This constructs a new symbolic state for stack analysis, performs the execution, and
* returns the state. The state can then be analyzed before finishing execution to a
* function return and analyzing it again.
*
* @param to the path from entry to the program counter
* @return the resulting state
* @throws CancelledException if the monitor cancels the analysis
*/
public SymPcodeExecutorState executeToPc(Deque<BlockEdge> to) throws CancelledException {
SymPcodeExecutorState state = new SymPcodeExecutorState(program);
SymPcodeExecutor exec =
SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE, warnings, monitor);
executePathTo(exec, to);
executeBlockTo(exec, pcBlock.block, pc);
return state;
}
/**
* Finish execution from the program counter to a function return, using the given path
*
* <p>
* This returns the same (but mutated) state as passed to it. The state should be forked
* from the result of {@link #executeToPc(Deque)}, but resetting the stack portion.
*
* @param state the state, whose registers are forked from the result of
* {@link #executeToPc(Deque)}.
* @param from the path from the program counter to a return
* @return the resulting state
* @throws CancelledException if the monitor cancels the analysis
*/
public SymPcodeExecutorState executeFromPc(SymPcodeExecutorState state,
Deque<BlockEdge> from) throws CancelledException {
SymPcodeExecutor exec =
SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE, warnings, monitor);
executeBlockFrom(exec, pcBlock.block, pc);
executePathFrom(exec, from);
return state;
}
/**
* Compute the unwinding information for a frame presumably produced by executing the
* current function up to but excluding the program counter
*
* <p>
* The goal is to compute a base pointer for the current frame so that the values of stack
* variables can be retrieved from a dynamic trace, as well as enough information to unwind
* and achieve the same for the next frame up on the stack. That is, the frame of the
* function that called the current function. We'll also need to figure out what registers
* were saved where on the stack so that the values of register variables can be retrieved
* from a dynamic trace. For architectures with a link register, register restoration is
* necessary to unwind the next frame, since that register holds its program counter.
* Ideally, this unwinding can be applied iteratively, until we reach the process' entry
* point.
*
* <p>
* The analytic strategy is fairly straightforward and generalized, though not universally
* applicable. It employs a somewhat rudimentary symbolic interpretation. A symbol can be a
* constant, a register's initial value at function entry, a stack offset relative to the
* stack pointer at function entry, a dereferenced stack offset, or an opaque value. See
* {@link Sym}.
*
* <ol>
* <li>Interpret the instructions along the shortest path from function entry to the program
* counter.</li>
* <li>Examine the symbol in the stack pointer register. It should be a stack offset. That
* offset is the "stack depth." See {@link UnwindInfo#depth()},
* {@link UnwindInfo#computeBase(Address)}, and
* {@link SymPcodeExecutorState#computeStackDepth()}.</li>
* <li>Search the stack for register symbols, creating an offset-register map. A subset of
* these are the saved registers on the stack. See {@link UnwindInfo#saved} and
* {@link SymPcodeExecutorState#computeMapUsingStack()}.</li>
* <li>Reset the stack state. (This implies stack dereferences from further interpretation
* refer to their values at the program counter rather than function entry.) See
* {@link SymPcodeExecutorState#forkRegs()}.</li>
* <li>Interpret the instructions along the shortest path from the program counter to a
* function return.</li>
* <li>Examine the symbol in the program counter register. This gives the location (register
* or stack offset) of the return address. This strategy should work whether or not a link
* register is involved. See {@link SymPcodeExecutorState#computeAddressOfReturn()}.
* <li>Examine the symbol in the stack pointer register, again. It should be a stack offset.
* That offset is the "stack adjustment." See {@link UnwindInfo#adjust()},
* {@link UnwindInfo#computeNextSp(Address)}, and
* {@link SymPcodeExecutorState#computeStackDepth()}.
* <li>Search the registers for stack dereference symbols, creating an offset-register map.
* This intersected with the same from entry to program counter is the saved registers map.
* See {@link UnwindInfo#saved()},
* {@link UnwindInfo#mapSavedRegisters(Address, SavedRegisterMap)}, and
* {@link SymPcodeExecutorState#computeMapUsingRegisters()}.
* </ol>
*
* <p>
* This strategy does make some assumptions:
* <ul>
* <li>The function returns.</li>
* <li>For every edge in the basic block graph, the stack depth at the end of its source
* block is equal to the stack depth at the start of its destination block.</li>
* <li>The function follows a "sane" convention. While it doesn't have to be any particular
* convention, it does need to restore its saved registers, and those registers should be
* saved to the stack in a straightforward manner.</li>
* </ul>
*
* @return the unwind information
* @throws CancelledException if the monitor cancels the analysis
*/
public UnwindInfo computeUnwindInfo() throws CancelledException {
// TODO: Find out to what other pc values this applies and cache?
Collection<Deque<BlockEdge>> entryPaths = getEntryPaths();
if (entryPaths.isEmpty()) {
throw new UnwindException(
"Could not find a path from " + function + " entry to " + pc);
}
Collection<Deque<BlockEdge>> exitsPaths = getExitsPaths();
// TODO: Proper exceptions for useless results
for (Deque<BlockEdge> entryPath : entryPaths) {
SymPcodeExecutorState entryState = executeToPc(entryPath);
Long depth = entryState.computeStackDepth();
if (depth == null) {
continue;
}
if (exitsPaths.isEmpty()) {
warnings.add(new NoReturnPathStackUnwindWarning(pc));
}
Map<Register, Address> mapByEntry = entryState.computeMapUsingStack();
for (Deque<BlockEdge> exitPath : exitsPaths) {
SymPcodeExecutorState exitState =
executeFromPc(entryState.forkRegs(), exitPath);
Address addressOfReturn = exitState.computeAddressOfReturn();
Long adjust = exitState.computeStackDepth();
if (addressOfReturn == null || adjust == null) {
continue;
}
Map<Register, Address> mapByExit = exitState.computeMapUsingRegisters();
mapByExit.entrySet().retainAll(mapByEntry.entrySet());
return new UnwindInfo(function, depth, adjust, addressOfReturn, mapByExit,
new StackUnwindWarningSet(warnings));
}
warnings.add(new OpaqueReturnPathStackUnwindWarning(pc));
long adjust = SymPcodeExecutor.computeStackChange(function, warnings);
return new UnwindInfo(function, depth, adjust, null, mapByEntry,
new StackUnwindWarningSet(warnings));
}
throw new UnwindException(
"Could not analyze any path from " + function + " entry to " + pc);
}
}
/**
* Start analysis at the given program counter
*
* @param pc the program counter
* @param monitor a monitor for all the analysis that follows
* @return the pc-specific analyzer
* @throws CancelledException if the monitor cancels the analysis
*/
AnalysisForPC start(Address pc, TaskMonitor monitor) throws CancelledException {
return new AnalysisForPC(pc, monitor);
}
/**
* Compute the unwind information for the given program counter
*
* @param pc the program counter
* @param monitor a monitor for progress and cancellation
* @return the unwind information
* @throws CancelledException if the monitor cancels the analysis
*/
public UnwindInfo computeUnwindInfo(Address pc, TaskMonitor monitor)
throws CancelledException {
AnalysisForPC analysis = start(pc, monitor);
return analysis.computeUnwindInfo();
}
}

View file

@ -13,15 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets;
package ghidra.app.plugin.core.debug.stack;
public interface ExpanderArrowExpansionListener {
/**
* @throws ExpanderArrowExpansionVetoException
*/
default void changing(boolean expanding) throws ExpanderArrowExpansionVetoException {
// Nothing
/**
* An exception to indicate failed or incomplete stack uwinding
*/
public class UnwindException extends RuntimeException {
public UnwindException(String message) {
super(message);
}
void changed(boolean expanded);
public UnwindException(String message, UnwindException cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,250 @@
/* ###
* 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.stack;
import java.util.Map;
import java.util.Map.Entry;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Variable;
import ghidra.trace.util.TraceRegisterUtils;
import ghidra.util.task.TaskMonitor;
/**
* Information for interpreting the current stack frame and unwinding to the next
*/
public record UnwindInfo(Function function, long depth, long adjust, Address ofReturn,
Map<Register, Address> saved, StackUnwindWarningSet warnings) {
/**
* The function that was analyzed
*
* @return the function
*/
public Function function() {
return function;
}
/**
* The change in the stack pointer from function entry to the given program counter
*
* <p>
* This is necessary to retrieve stack variables from the current frame. By subtracting this
* from the current stack pointer, the frame's base address is computed. See
* {@link #computeBase(Address)}. The offsets of stack variables are all relative to that base
* address. See {@link AnalysisUnwoundFrame#getValue(Variable)}.
*
* @return the depth
*/
public long depth() {
return depth;
}
/**
* The adjustment to the stack pointer, at function entry, to return from this function
*
* <p>
* This is used to unwind the stack pointer value for the next frame.
*
* @return the adjustment
*/
public long adjust() {
return adjust;
}
/**
* The <em>address of</em> the return address
*
* <p>
* The address may be a register or a stack offset, relative to the stack pointer at function
* entry.
*
* @return the address of the return address
*/
public Address ofReturn() {
return ofReturn;
}
/**
* The <em>address of</em> the return address, given a stack base
*
* <p>
* The address may be a register or a stack offset, relative to the stack pointer at function
* entry, i.e., base. If it's the latter, then this will resolve it with respect to the given
* base. The result can be used to retrieve the return address from a state. See
* {@link #computeNextPc(Address, PcodeExecutorState, Register)}.
*
* @param base the stack pointer at function entry
* @return the address of the return address
*/
public Address ofReturn(Address base) {
if (ofReturn.isRegisterAddress()) {
return ofReturn;
}
else if (ofReturn.isStackAddress()) {
return base.add(ofReturn.getOffset());
}
throw new AssertionError();
}
/**
* The map of registers to stack offsets for saved registers
*
* <p>
* This is not necessary until its time to unwind the next frame. The saved registers should be
* restored, then the next PC and SP computed, then the next frame unwound. See
* {@link AnalysisUnwoundFrame#unwindNext(TaskMonitor)}.
*
* @return the map of registers to stack addresses
*/
public Map<Register, Address> saved() {
return saved;
}
/**
* The list of warnings issues during analysis
*
* @return the warnings
*/
public StackUnwindWarningSet warnings() {
return warnings;
}
/**
* Compute the current frame's base address given the current (or unwound) stack pointer.
*
* <p>
* This is used to retrieve variable values for the current frame.
*
* @param spVal the stack pointer
* @return the base address
*/
public Address computeBase(Address spVal) {
return spVal.subtract(depth);
}
/**
* Restore saved registers in the given state
*
* <p>
* This is used as part of unwinding the next frame.
*
* @param <T> the type of values in the state
* @param base the current frame's base pointer, as in {@link #computeBase(Address)}.
* @param state the state to modify, usually forked from the current frame's state
* @see AnalysisUnwoundFrame#unwindNext(TaskMonitor)
*/
public <T> void restoreRegisters(Address base, PcodeExecutorState<T> state) {
for (Entry<Register, Address> ent : saved.entrySet()) {
Register reg = ent.getKey();
Address offset = ent.getValue();
assert offset.isStackAddress();
Address address = base.add(offset.getOffset());
T value = state.getVar(address, reg.getNumBytes(), true, Reason.INSPECT);
state.setVar(reg, value);
}
}
/**
* Add register map entries for the saved registers in this frame
*
* @param base the current frame's base pointer, as in {@link #computeBase(Address)}
* @param registerMap the register map of the stack to this point, to be modified
*/
public void mapSavedRegisters(Address base, SavedRegisterMap map) {
for (Entry<Register, Address> ent : saved.entrySet()) {
Register reg = ent.getKey();
Address offset = ent.getValue();
assert offset.isStackAddress();
Address address = base.add(offset.getOffset());
map.put(TraceRegisterUtils.rangeForRegister(reg), address);
}
}
/**
* Compute the return address of the current frame, giving the unwound program counter of the
* next frame
*
* <p>
* This is used as part of unwinding the next frame.
*
* @param <T> the type of values in the state
* @param base the current frame's base pointer, as in {@link #computeBase(Address)}
* @param state the state of the next frame, whose program counter this method is computing
* @param pc the program counter register, used for its size
* @return the value of the program counter for the next frame
* @see AnalysisUnwoundFrame#unwindNext(TaskMonitor)
*/
public <T> T computeNextPc(Address base, PcodeExecutorState<T> state, Register pc) {
return state.getVar(ofReturn(base), pc.getNumBytes(), true, Reason.INSPECT);
}
/**
* Compute the return address of the current frame, giving the unwound program counter (as a
* code address) of the next frame.
*
* <p>
* This is used as part of unwinding the next frame.
*
* @param <T> the type of values in the state
* @param base the current frame's base pointer, as in {@link #computeBase(Address)}
* @param state the state of the next frame, whose program counter this method is computing
* @param codeSpace the address space where the program counter points
* @param pc the program counter register, used for its size
* @return the address of the next instruction for the next frame
* @see AnalysisUnwoundFrame#unwindNext(TaskMonitor)
*/
public <T> Address computeNextPc(Address base, PcodeExecutorState<T> state,
AddressSpace codeSpace, Register pc) {
T value = computeNextPc(base, state, pc);
long concrete = state.getArithmetic().toLong(value, Purpose.INSPECT);
return codeSpace.getAddress(concrete);
}
/**
* Compute the unwound stack pointer for the next frame
*
* <p>
* This is used as part of unwinding the next frame.
*
* @param base the current frame's based pointer, as in {@link #computeBase(Address)}
* @return the stack pointer for the next frame
* @see AnalysisUnwoundFrame#unwindNext(TaskMonitor)
*/
public Address computeNextSp(Address base) {
return base.add(adjust);
}
/**
* Get the number of bytes in the parameter portion of the frame
*
* <p>
* These are the entries on the opposite side of the base pointer from the rest of the frame. In
* fact, these are pushed onto the stack by the caller, so these slots should be "stolen" from
* the caller's frame and given to the callee's frame.
*
* @return the total parameter size in bytes
*/
public int computeParamSize() {
return function.getStackFrame().getParameterSize();
}
}

View file

@ -0,0 +1,63 @@
/* ###
* 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.stack;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.framework.cmd.TypedBackgroundCommand;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
import ghidra.trace.model.Trace;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A command to unwind as much of the stack as possible and annotate the resulting frame in the
* dynamic listing
*/
public class UnwindStackCommand extends TypedBackgroundCommand<Trace> {
private final PluginTool tool;
private final DebuggerCoordinates where;
public UnwindStackCommand(PluginTool tool, DebuggerCoordinates where) {
super("Unwind Stack", false, true, false);
this.tool = tool;
this.where = where;
}
@Override
public boolean applyToTyped(Trace obj, TaskMonitor monitor) {
try {
StackUnwinder unwinder = new StackUnwinder(tool, where.getPlatform());
int prevParamSize = 0;
for (AnalysisUnwoundFrame<WatchValue> frame : unwinder.frames(where.frame(0),
monitor)) {
UnwindInfo info = frame.getUnwindInfo();
if (info != null) {
frame.applyToListing(prevParamSize, monitor);
prevParamSize = info.computeParamSize();
}
else {
tool.setStatusInfo(frame.getError().getMessage());
}
}
return true;
}
catch (CancelledException e) {
return true;
}
}
}

View file

@ -0,0 +1,286 @@
/* ###
* 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.stack;
import java.math.BigInteger;
import java.util.concurrent.CompletableFuture;
import ghidra.app.decompiler.ClangLine;
import ghidra.app.plugin.core.debug.gui.stack.vars.VariableValueUtils;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.PcodeOp;
/**
* A frame that has been unwound through analysis or annotated in the listing
*
* <p>
* An unwound frame can be obtained via {@link StackUnwinder} or {@link ListingUnwoundFrame}. The
* former is used when stack unwind analysis has not yet been applied to the current trace snapshot.
* It actually returns a {@link AnalysisUnwoundFrame}, which can apply the resulting analysis to the
* snapshot. The latter is used when those annotations are already present.
*
* @param <T> the type of values retrievable from the unwound frame
*/
public interface UnwoundFrame<T> {
/**
* Check if this is an actual frame
*
* @see FakeUnwoundFrame
* @return true if fake
*/
boolean isFake();
/**
* Get the level of this frame, 0 being the innermost
*
* @return the level
*/
int getLevel();
/**
* Get a description of this frame, for display purposes
*
* @return the description
*/
String getDescription();
/**
* Get the frame's program counter
*
* <p>
* If this is the innermost frame, this is the next instruction to be executed. Otherwise, this
* is the return address of the next inner frame, i.e., the instruction to be executed after
* control is returned to the function that allocated this frame.
*
* @return the frame's program counter
*/
Address getProgramCounter();
/**
* Get the function that allocated this frame
*
* <p>
* This is the function whose body contains the program counter
*
* @return the frame's allocating function
*/
Function getFunction();
/**
* Get the base pointer for this frame
*
* <p>
* This is the value of the stack pointer at entry of the allocating function. Note while
* related, this is a separate thing from the "base pointer" register. Not all architectures
* offer one, and even on those that do, not all functions use it. Furthermore, a function that
* does use it may place a different value in the than we define as the base pointer. The value
* here is that recovered from an examination of stack operations from the function's entry to
* the program counter. It is designed such that varnodes with stack offsets can be located in
* this frame by adding the offset to this base pointer.
*
* @return the frame's base pointer
*/
Address getBasePointer();
/**
* Get the frame's return address
*
* <p>
* The address of the return address is determined by an examination of stack and register
* operations from the program counter to a return of the function allocating this frame. Three
* cases are known:
* <ol>
* <li>The return address is on the stack. This happens for architectures where the caller must
* push the return address to the stack. It can also happen on architectures with a link
* register if the callee saves that register to the stack.</li>
* <li>The return address is in a register. This happens for architectures with a link register
* assuming the callee has not saved that register to the stack.</li>
* <li>The return address cannot be recovered. This happens when the function appears to be non
* returning, or the analysis otherwise fails to recover the return address. In this case, this
* method will throw an exception.
* </ol>
*
* @return the return address
*/
Address getReturnAddress();
/**
* Get the warnings generated during analysis
*
* <p>
* Several warnings may be returned, each on its own line.
*
* @return the warnings
*/
String getWarnings();
/**
* Get the value of the storage from the frame
*
* <p>
* Each varnode in the storage is retrieved and concatenated together. The lower-indexed
* varnodes have higher significance -- like big endian. A varnode is retrieved from the state,
* with register accesses potentially redirected to a location where its value has been saved to
* the stack.
*
* <p>
* Each varnode's value is simply retrieved from the state, in contrast to
* {@link #evaluate(VariableStorage, AddressSetView)}, which ascends to varnodes' defining
* p-code ops.
*
* <p>
* <b>WARNING:</b> Never invoke this method from the Swing thread. The state could be associated
* with a live session, and this may block to retrieve live state.
*
* @param storage the storage
* @return the value
*/
T getValue(VariableStorage storage);
/**
* Get the value of the variable from the frame
*
* <p>
* <b>WARNING:</b> Never invoke this method from the Swing thread. The state could be associated
* with a live session, and this may block to retrieve live state.
*
* @see #getValue(VariableStorage)
* @param variable the variable
* @return the value
*/
default T getValue(Variable variable) {
return getValue(variable.getVariableStorage());
}
/**
* Get the value of the register, possible saved elsewhere on the stack, relative to this frame
*
* <p>
* <b>WARNING:</b> Never invoke this method from the Swing thread. The state could be associated
* with a live session, and this may block to retrieve live state.
*
* @param register the register
* @return the value
*/
T getValue(Register register);
/**
* Evaluate the given storage, following defining p-code ops until symbol storage is reached
*
* <p>
* This behaves similarly to {@link #getValue(VariableStorage)}, except this one will ascend
* recursively to each varnode's defining p-code op. The recursion terminates when a varnode is
* contained in the given symbol storage. The symbol storage is usually collected by examining
* the tokens on the same line, searching for ones that represent "high symbols." This ensures
* that any temporary storage used by the original program in the evaluation of, e.g., a field
* access, are not read from the current state but re-evaluated in terms of the symbols' current
* values.
*
* <p>
* <b>WARNING:</b> Never invoke this method from the Swing thread. The state could be associated
* with a live session, and this may block to retrieve live state.
*
* @see VariableValueUtils#collectSymbolStorage(ClangLine)
* @param storage the storage to evaluate
* @param symbolStorage the terminal storage, usually that of symbols
* @return the value
*/
T evaluate(VariableStorage storage, AddressSetView symbolStorage);
/**
* Evaluate the output for the given p-code op, ascending until symbol storage is reached
*
* <p>
* <b>WARNING:</b> Never invoke this method from the Swing thread. The state could be associated
* with a live session, and this may block to retrieve live state.
*
* @see #evaluate(VariableStorage, AddressSetView)
* @param program the program containing the op
* @param op the op
* @param symbolStorage the terminal storage, usually that of symbols
* @return the value
*/
T evaluate(Program program, PcodeOp op, AddressSetView symbolStorage);
/**
* Set the value of the given storage
*
* <p>
* Register accesses may be redirected to the location where its current value is saved to the
* stack.
*
* @param editor the editor for setting values
* @param storage the storage to modify
* @param value the desired value
* @return a future which completes when the necessary commands have all completed
*/
CompletableFuture<Void> setValue(StateEditor editor, VariableStorage storage, BigInteger value);
/**
* Set the value of the given variable
*
* @see #setValue(StateEditor, VariableStorage, BigInteger)
* @param editor the editor for setting values
* @param variable the variable to modify
* @param value the desired value
* @return a future which completes when the necessary commands have all completed
*/
default CompletableFuture<Void> setValue(StateEditor editor, Variable variable,
BigInteger value) {
return setValue(editor, variable.getVariableStorage(), value);
}
/**
* Set the return address of this frame
*
* <p>
* This is typically used to set up a mechanism in pure emulation that traps execution when the
* entry function has returned. For example, to emulate a target function in isolation, a script
* could load or map the target program into a trace, initialize a thread at the target
* function's entry, allocate a stack, and "unwind" that stack. Then, it can initialize the
* function's parameters and return address. The return address is usually a fake but
* recognizable address, such as {@code 0xdeadbeef}. The script would then place a breakpoint at
* that address and allow the emulator to run. Once it breaks at {@code 0xdeadbeef}, the script
* can read the return value, if applicable.
*
* @param editor the editor for setting values
* @param address the desired return address
* @return a future which completes when the necessary commands have all completed
*/
CompletableFuture<Void> setReturnAddress(StateEditor editor, Address address);
/**
* Match length by zero extension or truncation
*
* <p>
* This is to cope with a small imperfection in field expression evaluation: Fields are
* evaluated using the high p-code from the decompiled function that yielded the expression.
* That code is likely loading the value into a register, which is likely a machine word in
* size, even if the field being accessed is smaller. Thus, the type of a token's high variable
* may disagree in size with the output varnode of the token's associated high p-code op. To
* rectify this discrepancy during evaluation, the type's size is assumed correct, and the
* output value is resized to match.
*
* @param value the value
* @param length the desired length
* @return the extended or truncated value
*/
T zext(T value, int length);
}

View file

@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.debug.utils;
import java.util.List;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.BiFunction;
import java.util.function.Function;
@ -166,20 +166,24 @@ public enum BackgroundUtils {
}
public static class PluginToolExecutorService extends AbstractExecutorService {
private final PluginTool tool;
private String name;
private boolean canCancel;
private boolean hasProgress;
private boolean isModal;
private final int delay;
public enum TaskOpt {
CAN_CANCEL, HAS_PROGRESS, IS_MODAL, IS_BACKGROUND;
}
public PluginToolExecutorService(PluginTool tool, String name, boolean canCancel,
boolean hasProgress, boolean isModal, int delay) {
private final PluginTool tool;
private final String name;
private final UndoableDomainObject obj;
private final int delay;
private final EnumSet<TaskOpt> opts;
private TaskMonitor lastMonitor;
public PluginToolExecutorService(PluginTool tool, String name, UndoableDomainObject obj,
int delay, TaskOpt... opts) {
this.tool = tool;
this.name = name;
this.canCancel = canCancel;
this.hasProgress = hasProgress;
this.isModal = isModal;
this.obj = obj;
this.opts = EnumSet.copyOf(Arrays.asList(opts));
this.delay = delay;
}
@ -210,13 +214,45 @@ public enum BackgroundUtils {
@Override
public void execute(Runnable command) {
Task task = new Task(name, canCancel, hasProgress, isModal) {
if (opts.contains(TaskOpt.IS_BACKGROUND)) {
executeBackground(command);
}
else {
executeForeground(command);
}
}
protected void executeForeground(Runnable command) {
Task task = new Task(name,
opts.contains(TaskOpt.CAN_CANCEL),
opts.contains(TaskOpt.HAS_PROGRESS),
opts.contains(TaskOpt.IS_MODAL)) {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
lastMonitor = monitor;
command.run();
}
};
tool.execute(task, delay);
}
protected void executeBackground(Runnable command) {
BackgroundCommand cmd = new BackgroundCommand(name,
opts.contains(TaskOpt.HAS_PROGRESS),
opts.contains(TaskOpt.CAN_CANCEL),
opts.contains(TaskOpt.IS_MODAL)) {
@Override
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
lastMonitor = monitor;
command.run();
return true;
}
};
tool.executeBackgroundCommand(cmd, obj);
}
public TaskMonitor getLastMonitor() {
return lastMonitor;
}
}
}

View file

@ -15,6 +15,13 @@
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.bouncycastle.util.Arrays;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.service.emulation.*;
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
@ -26,13 +33,14 @@ import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.trace.*;
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.*;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.util.NumericUtilities;
/**
* Utilities for evaluating or executing Sleigh/p-code in the Debugger
@ -102,10 +110,160 @@ public enum DebuggerPcodeUtils {
}
/**
* The value of a watch expression including its state, address, and addresses read
* A wrapper on a byte array to pretty print it
*/
public record WatchValue(byte[] bytes, TraceMemoryState state, Address address,
public record PrettyBytes(boolean bigEndian, byte[] bytes) {
@Override
public byte[] bytes() {
return Arrays.copyOf(bytes, bytes.length);
}
@Override
public String toString() {
return "PrettyBytes[bigEndian=" + bigEndian + ",bytes=" +
NumericUtilities.convertBytesToString(bytes, ":") + ",value=" +
toBigInteger(false) + "]";
}
/**
* Render at most 256 bytes in lines of 16 space-separated bytes each
*
* <p>
* If the total exceeds 256 bytes, the last line will contain ellipses and indicate the
* total size in bytes.
*
* @return the rendered string
*/
public String toBytesString() {
StringBuffer buf = new StringBuffer();
boolean first = true;
for (int i = 0; i < bytes.length; i += 16) {
if (i >= 256) {
buf.append("\n... (count=");
buf.append(bytes.length);
buf.append(")");
break;
}
if (first) {
first = false;
}
else {
buf.append('\n');
}
int len = Math.min(16, bytes.length - i);
buf.append(NumericUtilities.convertBytesToString(bytes, i, len, " "));
}
return buf.toString();
}
/**
* Render the bytes as an unsigned decimal integer
*
* <p>
* The endianness is taken from {@link #bigEndian()}
*
* @return the rendered string
*/
public String toIntegerString() {
return toBigInteger(false).toString();
}
/**
* Collect various integer representations: signed, unsigned; decimal, hexadecimal
*
* <p>
* This only presents those forms that differ from those already offered. The preferred form
* is unsigned decimal. If all four differ, then they are formatted on two lines: unsigned
* then signed.
*
* @return the rendered string
*/
public String collectDisplays() {
BigInteger unsigned = toBigInteger(false);
StringBuffer sb = new StringBuffer();
String uDec = unsigned.toString();
sb.append(uDec);
String uHex = unsigned.toString(16);
boolean radixMatters = !uHex.equals(uDec);
if (radixMatters) {
sb.append(", 0x");
sb.append(uHex);
}
BigInteger signed = toBigInteger(true);
if (!signed.equals(unsigned)) {
sb.append(radixMatters ? "\n" : ", ");
String sDec = signed.toString();
sb.append(sDec);
String sHex = signed.toString(16);
if (!sHex.equals(sDec)) {
sb.append(", -0x");
sb.append(sHex.subSequence(1, sHex.length()));
}
}
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof PrettyBytes that)) {
return false;
}
if (this.bigEndian != that.bigEndian) {
return false;
}
return Arrays.areEqual(this.bytes, that.bytes);
}
/**
* Convert the array to a big integer with the given signedness
*
* @param signed true for signed, false for unsigned
* @return the big integer
*/
public BigInteger toBigInteger(boolean signed) {
return Utils.bytesToBigInteger(bytes, bytes.length, bigEndian, signed);
}
/**
* Get the number of bytes
*
* @return the count
*/
public int length() {
return bytes.length;
}
}
/**
* The value of a watch expression including its state, location, and addresses read
*/
public record WatchValue(PrettyBytes bytes, TraceMemoryState state, ValueLocation location,
AddressSetView reads) {
/**
* Get the value as a big integer with the given signedness
*
* @param signed true for signed, false for unsigned
* @return the big integer
*/
public BigInteger toBigInteger(boolean signed) {
return bytes.toBigInteger(signed);
}
public Address address() {
return location == null ? null : location.getAddress();
}
/**
* Get the number of bytes
*
* @return the count
*/
public int length() {
return bytes.length();
}
}
/**
@ -116,8 +274,8 @@ public enum DebuggerPcodeUtils {
* unwieldy.
*/
public enum WatchValuePcodeArithmetic implements PcodeArithmetic<WatchValue> {
BIG_ENDIAN(BytesPcodeArithmetic.BIG_ENDIAN),
LITTLE_ENDIAN(BytesPcodeArithmetic.LITTLE_ENDIAN);
BIG_ENDIAN(BytesPcodeArithmetic.BIG_ENDIAN, LocationPcodeArithmetic.BIG_ENDIAN),
LITTLE_ENDIAN(BytesPcodeArithmetic.LITTLE_ENDIAN, LocationPcodeArithmetic.LITTLE_ENDIAN);
public static WatchValuePcodeArithmetic forEndian(boolean isBigEndian) {
return isBigEndian ? BIG_ENDIAN : LITTLE_ENDIAN;
@ -129,15 +287,16 @@ public enum DebuggerPcodeUtils {
private static final TraceMemoryStatePcodeArithmetic STATE =
TraceMemoryStatePcodeArithmetic.INSTANCE;
private static final AddressOfPcodeArithmetic ADDRESS =
AddressOfPcodeArithmetic.INSTANCE;
private static final AddressesReadPcodeArithmetic READS =
AddressesReadPcodeArithmetic.INSTANCE;
private final BytesPcodeArithmetic bytes;
private final LocationPcodeArithmetic location;
private WatchValuePcodeArithmetic(BytesPcodeArithmetic bytes) {
private WatchValuePcodeArithmetic(BytesPcodeArithmetic bytes,
LocationPcodeArithmetic location) {
this.bytes = bytes;
this.location = location;
}
@Override
@ -148,9 +307,10 @@ public enum DebuggerPcodeUtils {
@Override
public WatchValue unaryOp(int opcode, int sizeout, int sizein1, WatchValue in1) {
return new WatchValue(
bytes.unaryOp(opcode, sizeout, sizein1, in1.bytes),
new PrettyBytes(getEndian().isBigEndian(),
bytes.unaryOp(opcode, sizeout, sizein1, in1.bytes.bytes)),
STATE.unaryOp(opcode, sizeout, sizein1, in1.state),
ADDRESS.unaryOp(opcode, sizeout, sizein1, in1.address),
location.unaryOp(opcode, sizeout, sizein1, in1.location),
READS.unaryOp(opcode, sizeout, sizein1, in1.reads));
}
@ -158,9 +318,11 @@ public enum DebuggerPcodeUtils {
public WatchValue binaryOp(int opcode, int sizeout, int sizein1, WatchValue in1,
int sizein2, WatchValue in2) {
return new WatchValue(
bytes.binaryOp(opcode, sizeout, sizein1, in1.bytes, sizein2, in2.bytes),
new PrettyBytes(getEndian().isBigEndian(),
bytes.binaryOp(opcode, sizeout, sizein1, in1.bytes.bytes, sizein2,
in2.bytes.bytes)),
STATE.binaryOp(opcode, sizeout, sizein1, in1.state, sizein2, in2.state),
ADDRESS.binaryOp(opcode, sizeout, sizein1, in1.address, sizein2, in2.address),
location.binaryOp(opcode, sizeout, sizein1, in1.location, sizein2, in2.location),
READS.binaryOp(opcode, sizeout, sizein1, in1.reads, sizein2, in2.reads));
}
@ -168,12 +330,13 @@ public enum DebuggerPcodeUtils {
public WatchValue modBeforeStore(int sizeout, int sizeinAddress, WatchValue inAddress,
int sizeinValue, WatchValue inValue) {
return new WatchValue(
bytes.modBeforeStore(sizeout, sizeinAddress, inAddress.bytes,
sizeinValue, inValue.bytes),
new PrettyBytes(inValue.bytes.bigEndian,
bytes.modBeforeStore(sizeout, sizeinAddress, inAddress.bytes.bytes,
sizeinValue, inValue.bytes.bytes)),
STATE.modBeforeStore(sizeout, sizeinAddress, inAddress.state,
sizeinValue, inValue.state),
ADDRESS.modBeforeStore(sizeout, sizeinAddress, inAddress.address,
sizeinValue, inValue.address),
location.modBeforeStore(sizeout, sizeinAddress, inAddress.location,
sizeinValue, inValue.location),
READS.modBeforeStore(sizeout, sizeinAddress, inAddress.reads,
sizeinValue, inValue.reads));
}
@ -182,12 +345,13 @@ public enum DebuggerPcodeUtils {
public WatchValue modAfterLoad(int sizeout, int sizeinAddress, WatchValue inAddress,
int sizeinValue, WatchValue inValue) {
return new WatchValue(
bytes.modAfterLoad(sizeout, sizeinAddress, inAddress.bytes,
sizeinValue, inValue.bytes),
new PrettyBytes(getEndian().isBigEndian(),
bytes.modAfterLoad(sizeout, sizeinAddress, inAddress.bytes.bytes,
sizeinValue, inValue.bytes.bytes)),
STATE.modAfterLoad(sizeout, sizeinAddress, inAddress.state,
sizeinValue, inValue.state),
ADDRESS.modAfterLoad(sizeout, sizeinAddress, inAddress.address,
sizeinValue, inValue.address),
location.modAfterLoad(sizeout, sizeinAddress, inAddress.location,
sizeinValue, inValue.location),
READS.modAfterLoad(sizeout, sizeinAddress, inAddress.reads,
sizeinValue, inValue.reads));
}
@ -195,20 +359,20 @@ public enum DebuggerPcodeUtils {
@Override
public WatchValue fromConst(byte[] value) {
return new WatchValue(
bytes.fromConst(value),
new PrettyBytes(getEndian().isBigEndian(), bytes.fromConst(value)),
STATE.fromConst(value),
ADDRESS.fromConst(value),
location.fromConst(value),
READS.fromConst(value));
}
@Override
public byte[] toConcrete(WatchValue value, Purpose purpose) {
return bytes.toConcrete(value.bytes, purpose);
return bytes.toConcrete(value.bytes.bytes, purpose);
}
@Override
public long sizeOf(WatchValue value) {
return bytes.sizeOf(value.bytes);
return bytes.sizeOf(value.bytes.bytes);
}
}
@ -216,7 +380,7 @@ public enum DebuggerPcodeUtils {
implements PcodeExecutorStatePiece<byte[], WatchValue> {
private final PcodeExecutorStatePiece<byte[], byte[]> bytesPiece;
private final PcodeExecutorStatePiece<byte[], TraceMemoryState> statePiece;
private final PcodeExecutorStatePiece<byte[], Address> addressPiece;
private final PcodeExecutorStatePiece<byte[], ValueLocation> locationPiece;
private final PcodeExecutorStatePiece<byte[], AddressSetView> readsPiece;
private final PcodeArithmetic<WatchValue> arithmetic;
@ -224,11 +388,11 @@ public enum DebuggerPcodeUtils {
public WatchValuePcodeExecutorStatePiece(
PcodeExecutorStatePiece<byte[], byte[]> bytesPiece,
PcodeExecutorStatePiece<byte[], TraceMemoryState> statePiece,
PcodeExecutorStatePiece<byte[], Address> addressPiece,
PcodeExecutorStatePiece<byte[], ValueLocation> locationPiece,
PcodeExecutorStatePiece<byte[], AddressSetView> readsPiece) {
this.bytesPiece = bytesPiece;
this.statePiece = statePiece;
this.addressPiece = addressPiece;
this.locationPiece = locationPiece;
this.readsPiece = readsPiece;
this.arithmetic = WatchValuePcodeArithmetic.forLanguage(bytesPiece.getLanguage());
}
@ -248,12 +412,18 @@ public enum DebuggerPcodeUtils {
return arithmetic;
}
@Override
public WatchValuePcodeExecutorStatePiece fork() {
return new WatchValuePcodeExecutorStatePiece(
bytesPiece.fork(), statePiece.fork(), locationPiece.fork(), readsPiece.fork());
}
@Override
public void setVar(AddressSpace space, byte[] offset, int size, boolean quantize,
WatchValue val) {
bytesPiece.setVar(space, offset, size, quantize, val.bytes);
bytesPiece.setVar(space, offset, size, quantize, val.bytes.bytes);
statePiece.setVar(space, offset, size, quantize, val.state);
addressPiece.setVar(space, offset, size, quantize, val.address);
locationPiece.setVar(space, offset, size, quantize, val.location);
readsPiece.setVar(space, offset, size, quantize, val.reads);
}
@ -261,12 +431,30 @@ public enum DebuggerPcodeUtils {
public WatchValue getVar(AddressSpace space, byte[] offset, int size, boolean quantize,
Reason reason) {
return new WatchValue(
bytesPiece.getVar(space, offset, size, quantize, reason),
new PrettyBytes(getLanguage().isBigEndian(),
bytesPiece.getVar(space, offset, size, quantize, reason)),
statePiece.getVar(space, offset, size, quantize, reason),
addressPiece.getVar(space, offset, size, quantize, reason),
locationPiece.getVar(space, offset, size, quantize, reason),
readsPiece.getVar(space, offset, size, quantize, reason));
}
@Override
public Map<Register, WatchValue> getRegisterValues() {
Map<Register, WatchValue> 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 WatchValue(
new PrettyBytes(getLanguage().isBigEndian(), entry.getValue()),
statePiece.getVar(space, offset, size, false, Reason.INSPECT),
locationPiece.getVar(space, offset, size, false, Reason.INSPECT),
readsPiece.getVar(space, offset, size, false, Reason.INSPECT)));
}
return result;
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return bytesPiece.getConcreteBuffer(address, purpose);
@ -276,7 +464,7 @@ public enum DebuggerPcodeUtils {
public void clear() {
bytesPiece.clear();
statePiece.clear();
addressPiece.clear();
locationPiece.clear();
readsPiece.clear();
}
}
@ -298,16 +486,26 @@ public enum DebuggerPcodeUtils {
return piece.arithmetic;
}
@Override
public WatchValuePcodeExecutorState fork() {
return new WatchValuePcodeExecutorState(piece.fork());
}
@Override
public void setVar(AddressSpace space, WatchValue offset, int size, boolean quantize,
WatchValue val) {
piece.setVar(space, offset.bytes, size, quantize, val);
piece.setVar(space, offset.bytes.bytes, size, quantize, val);
}
@Override
public WatchValue getVar(AddressSpace space, WatchValue offset, int size, boolean quantize,
Reason reason) {
return piece.getVar(space, offset.bytes, size, quantize, reason);
return piece.getVar(space, offset.bytes.bytes, size, quantize, reason);
}
@Override
public Map<Register, WatchValue> getRegisterValues() {
return piece.getRegisterValues();
}
@Override
@ -330,7 +528,7 @@ public enum DebuggerPcodeUtils {
return new WatchValuePcodeExecutorState(new WatchValuePcodeExecutorStatePiece(
bytesState,
new TraceMemoryStatePcodeExecutorStatePiece(data),
new AddressOfPcodeExecutorStatePiece(data.getLanguage()),
new LocationPcodeExecutorStatePiece(data.getLanguage()),
new AddressesReadTracePcodeExecutorStatePiece(data)));
}

View file

@ -15,31 +15,63 @@
*/
package ghidra.app.plugin.core.debug.gui.stack;
import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import java.util.Set;
import org.junit.*;
import generic.Unique;
import ghidra.app.plugin.assembler.*;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.action.SPLocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin;
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.plugin.core.debug.stack.*;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.async.AsyncTestUtils;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
import ghidra.program.database.ProgramDB;
import ghidra.program.disassemble.Disassembler;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.FunctionManager;
import ghidra.program.model.listing.Program;
import ghidra.program.model.data.UnsignedIntegerDataType;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function.FunctionUpdateType;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.util.ProgramLocation;
import ghidra.test.ToyProgramBuilder;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.DefaultTraceLocation;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.Scheduler;
import ghidra.util.InvalidNameException;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.ConsoleTaskMonitor;
import ghidra.util.task.TaskMonitor;
import help.screenshot.GhidraScreenShotGenerator;
public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator {
public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator
implements AsyncTestUtils {
ProgramManager programManager;
DebuggerTraceManagerService traceManager;
@ -125,4 +157,182 @@ public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator {
captureIsolatedProvider(DebuggerStackProvider.class, 600, 300);
}
protected ConsoleTaskMonitor monitor = new ConsoleTaskMonitor();
// 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;
}
});
}
protected void createProgram(Language lang, CompilerSpec cSpec) throws IOException {
program = new ProgramDB("fibonacci", lang, cSpec, this);
}
protected void createProgram(String languageID, String cSpecID) throws IOException {
Language language = getLanguageService().getLanguage(new LanguageID(languageID));
CompilerSpec cSpec = cSpecID == null ? language.getDefaultCompilerSpec()
: language.getCompilerSpecByID(new CompilerSpecID(cSpecID));
createProgram(language, cSpec);
}
Address retInstr;
protected Register register(String name) {
return program.getLanguage().getRegister(name);
}
protected Function createFibonacciProgramX86_32() throws Throwable {
createProgram("x86:LE:32:default", "gcc");
intoProject(program);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Assemble")) {
Address entry = addr(program, 0x00400000);
program.getMemory()
.createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false);
Assembler asm =
Assemblers.getAssembler(program.getLanguage(), StackUnwinderTest.NO_16BIT_CALLS);
AssemblyBuffer buf = new AssemblyBuffer(asm, entry);
buf.assemble("PUSH EBP");
buf.assemble("MOV EBP, ESP");
buf.assemble("CMP dword ptr [EBP+8], 1");
Address jumpBase = buf.getNext();
buf.assemble("JBE 0x" + buf.getNext());
// Recursive case. Let EDX be sum
// sum = fib(n - 1)
buf.assemble("MOV ECX, dword ptr [EBP+8]");
buf.assemble("DEC ECX");
buf.assemble("PUSH ECX"); // pass n - 1
buf.assemble("CALL 0x" + entry);
buf.assemble("ADD ESP, 4"); // Clear parameters
buf.assemble("MOV EDX, EAX");
// sum += fib(n - 2)
buf.assemble("MOV ECX, dword ptr [EBP+8]");
buf.assemble("SUB ECX, 2");
buf.assemble("PUSH EDX"); // Caller Save EDX
buf.assemble("PUSH ECX"); // pass n - 2
buf.assemble("CALL 0x" + entry);
buf.assemble("ADD ESP, 4"); // Clear parameters
buf.assemble("POP EDX"); // Restore EDX
buf.assemble("ADD EAX, EDX");
Address labelRet = buf.getNext();
buf.assemble("LEAVE");
retInstr = buf.getNext();
buf.assemble("RET");
Address labelBase = buf.getNext();
buf.assemble(jumpBase, "JBE 0x" + labelBase);
buf.assemble("MOV EAX, dword ptr [EBP+8]");
buf.assemble("JMP 0x" + labelRet);
byte[] bytes = buf.getBytes();
program.getMemory().setBytes(entry, bytes);
Disassembler dis = Disassembler.getDisassembler(program, monitor, null);
dis.disassemble(entry, null);
Function function = program.getFunctionManager()
.createFunction("fib", entry,
new AddressSet(entry, entry.add(bytes.length - 1)),
SourceType.USER_DEFINED);
function.updateFunction("__cdecl",
new ReturnParameterImpl(UnsignedIntegerDataType.dataType, program),
List.of(
new ParameterImpl("n", UnsignedIntegerDataType.dataType, program)),
FunctionUpdateType.DYNAMIC_STORAGE_FORMAL_PARAMS, true, SourceType.ANALYSIS);
// NOTE: The decompiler doesn't actually use sum.... For some reason, it re-uses n
// Still, in the tests, I can use uVar1 (EAX) as a register variable
function.addLocalVariable(
new LocalVariableImpl("sum", 0, UnsignedIntegerDataType.dataType, register("EDX"),
program),
SourceType.USER_DEFINED);
return function;
}
}
@Test
public void testCaptureDebuggerStackUnwindInListing() throws Throwable {
addPlugin(tool, DebuggerListingPlugin.class);
DebuggerStateEditingService editingService =
addPlugin(tool, DebuggerStateEditingServicePlugin.class);
DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class);
Function function = createFibonacciProgramX86_32();
Address entry = function.getEntryPoint();
programManager.openProgram(program);
tb.close();
tb = new ToyDBTraceBuilder(
ProgramEmulationUtils.launchEmulationTrace(program, entry, this));
tb.trace.release(this);
TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads());
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
DebuggerCoordinates atSetup = traceManager.getCurrent();
StackUnwinder unwinder = new StackUnwinder(tool, atSetup.getPlatform());
AnalysisUnwoundFrame<WatchValue> frameAtSetup = unwinder.start(atSetup, monitor);
Parameter param1 = function.getParameter(0);
waitOn(frameAtSetup.setValue(editor, param1, BigInteger.valueOf(9)));
waitOn(frameAtSetup.setReturnAddress(editor, tb.addr(0xdeadbeef)));
waitForTasks();
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getBreakpointManager()
.addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), retInstr,
Set.of(),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "unwind stack");
}
EmulationResult result = emuService.run(atSetup.getPlatform(), atSetup.getTime(), monitor,
Scheduler.oneThread(thread));
Msg.debug(this, "Broke after " + result.schedule());
traceManager.activateTime(result.schedule());
waitForTasks();
DebuggerCoordinates tallest = traceManager.getCurrent();
try (UndoableTransaction tid = tb.startTransaction()) {
new UnwindStackCommand(tool, tallest).applyTo(tb.trace, monitor);
}
waitForDomainObject(tb.trace);
DebuggerListingProvider listingProvider =
waitForComponentProvider(DebuggerListingProvider.class);
listingProvider.setTrackingSpec(SPLocationTrackingSpec.INSTANCE);
waitForSwing();
captureIsolatedProvider(listingProvider, 800, 600);
}
}

View file

@ -0,0 +1,391 @@
/* ###
* 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.stack.vars;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.*;
import org.junit.Test;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.support.FieldLocation;
import generic.Unique;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.plugin.assembler.*;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin;
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.plugin.core.debug.stack.*;
import ghidra.app.plugin.core.debug.stack.StackUnwinderTest.HoverLocation;
import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.AsyncTestUtils;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject;
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
import ghidra.program.database.ProgramDB;
import ghidra.program.disassemble.Disassembler;
import ghidra.program.model.address.*;
import ghidra.program.model.data.UnsignedIntegerDataType;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function.FunctionUpdateType;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.util.GhidraProgramUtilities;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.Scheduler;
import ghidra.util.InvalidNameException;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.ConsoleTaskMonitor;
import help.screenshot.GhidraScreenShotGenerator;
public class VariableValueHoverPluginScreenShots extends GhidraScreenShotGenerator
implements AsyncTestUtils {
ProgramManager programManager;
DebuggerTraceManagerService traceManager;
DebuggerStaticMappingService mappingService;
ToyDBTraceBuilder tb;
Program program;
protected ConsoleTaskMonitor monitor = new ConsoleTaskMonitor();
// 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;
}
});
}
protected void createProgram(Language lang, CompilerSpec cSpec) throws IOException {
program = new ProgramDB("fibonacci", lang, cSpec, this);
}
protected void createProgram(String languageID, String cSpecID) throws IOException {
Language language = getLanguageService().getLanguage(new LanguageID(languageID));
CompilerSpec cSpec = cSpecID == null ? language.getDefaultCompilerSpec()
: language.getCompilerSpecByID(new CompilerSpecID(cSpecID));
createProgram(language, cSpec);
}
Map<Address, Integer> stackRefInstrs = new HashMap<>();
Address registerRefInstr;
Address retInstr;
protected Register register(String name) {
return program.getLanguage().getRegister(name);
}
private static Address addr(Program program, long offset) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
}
protected Function createFibonacciProgramX86_32() throws Throwable {
createProgram("x86:LE:32:default", "gcc");
intoProject(program);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Assemble")) {
Address entry = addr(program, 0x00400000);
program.getMemory()
.createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false);
Assembler asm =
Assemblers.getAssembler(program.getLanguage(), StackUnwinderTest.NO_16BIT_CALLS);
AssemblyBuffer buf = new AssemblyBuffer(asm, entry);
buf.assemble("PUSH EBP");
buf.assemble("MOV EBP, ESP");
stackRefInstrs.put(buf.getNext(), 0);
buf.assemble("CMP dword ptr [EBP+8], 1");
Address jumpBase = buf.getNext();
buf.assemble("JBE 0x" + buf.getNext());
// Recursive case. Let EDX be sum
// sum = fib(n - 1)
stackRefInstrs.put(buf.getNext(), 1);
buf.assemble("MOV ECX, dword ptr [EBP+8]");
buf.assemble("DEC ECX");
buf.assemble("PUSH ECX"); // pass n - 1
buf.assemble("CALL 0x" + entry);
buf.assemble("ADD ESP, 4"); // Clear parameters
registerRefInstr = buf.getNext();
buf.assemble("MOV EDX, EAX");
// sum += fib(n - 2)
stackRefInstrs.put(buf.getNext(), 1);
buf.assemble("MOV ECX, dword ptr [EBP+8]");
buf.assemble("SUB ECX, 2");
buf.assemble("PUSH EDX"); // Caller Save EDX
buf.assemble("PUSH ECX"); // pass n - 2
buf.assemble("CALL 0x" + entry);
buf.assemble("ADD ESP, 4"); // Clear parameters
buf.assemble("POP EDX"); // Restore EDX
buf.assemble("ADD EAX, EDX");
Address labelRet = buf.getNext();
buf.assemble("LEAVE");
retInstr = buf.getNext();
buf.assemble("RET");
Address labelBase = buf.getNext();
buf.assemble(jumpBase, "JBE 0x" + labelBase);
stackRefInstrs.put(buf.getNext(), 1);
buf.assemble("MOV EAX, dword ptr [EBP+8]");
buf.assemble("JMP 0x" + labelRet);
byte[] bytes = buf.getBytes();
program.getMemory().setBytes(entry, bytes);
Disassembler dis = Disassembler.getDisassembler(program, monitor, null);
dis.disassemble(entry, null);
Function function = program.getFunctionManager()
.createFunction("fib", entry,
new AddressSet(entry, entry.add(bytes.length - 1)),
SourceType.USER_DEFINED);
function.updateFunction("__cdecl",
new ReturnParameterImpl(UnsignedIntegerDataType.dataType, program),
List.of(
new ParameterImpl("n", UnsignedIntegerDataType.dataType, program)),
FunctionUpdateType.DYNAMIC_STORAGE_FORMAL_PARAMS, true, SourceType.ANALYSIS);
// NOTE: The decompiler doesn't actually use sum.... For some reason, it re-uses n
// Still, in the tests, I can use uVar1 (EAX) as a register variable
function.addLocalVariable(
new LocalVariableImpl("sum", 0, UnsignedIntegerDataType.dataType, register("EDX"),
program),
SourceType.USER_DEFINED);
AddressSpace stack = program.getAddressFactory().getStackSpace();
for (Map.Entry<Address, Integer> ent : stackRefInstrs.entrySet()) {
Instruction ins = program.getListing().getInstructionAt(ent.getKey());
ins.addOperandReference(ent.getValue(), stack.getAddress(4), RefType.READ,
SourceType.ANALYSIS);
}
return function;
}
}
protected void prepareContext() throws Throwable {
programManager = addPlugin(tool, ProgramManagerPlugin.class);
programManager.closeAllPrograms(true);
traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class);
mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class);
addPlugin(tool, DebuggerListingPlugin.class);
addPlugin(tool, VariableValueHoverPlugin.class);
DebuggerStateEditingService editingService =
addPlugin(tool, DebuggerStateEditingServicePlugin.class);
DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class);
Function function = createFibonacciProgramX86_32();
GhidraProgramUtilities.setAnalyzedFlag(program, true);
Address entry = function.getEntryPoint();
programManager.openProgram(program);
tb = new ToyDBTraceBuilder(
ProgramEmulationUtils.launchEmulationTrace(program, entry, this));
tb.trace.release(this);
TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads());
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
try (UndoableTransaction tid = tb.startTransaction()) {
MemoryBlock block = program.getMemory().getBlock(".text");
byte[] text = new byte[(int) block.getSize()];
block.getBytes(block.getStart(), text);
tb.trace.getMemoryManager().putBytes(0, block.getStart(), ByteBuffer.wrap(text));
Disassembler dis =
Disassembler.getDisassembler(tb.trace.getProgramView(), monitor, null);
dis.disassemble(entry, null);
}
waitForDomainObject(tb.trace);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
DebuggerCoordinates atSetup = traceManager.getCurrent();
StackUnwinder unwinder = new StackUnwinder(tool, atSetup.getPlatform());
AnalysisUnwoundFrame<WatchValue> frameAtSetup = unwinder.start(atSetup, monitor);
Parameter param1 = function.getParameter(0);
waitOn(frameAtSetup.setValue(editor, param1, BigInteger.valueOf(9)));
waitOn(frameAtSetup.setReturnAddress(editor, tb.addr(0xdeadbeef)));
waitForTasks();
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getBreakpointManager()
.addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), retInstr,
Set.of(),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "unwind stack");
}
EmulationResult result = emuService.run(atSetup.getPlatform(), atSetup.getTime(), monitor,
Scheduler.oneThread(thread));
Msg.debug(this, "Broke after " + result.schedule());
traceManager.activateTime(result.schedule());
waitForTasks();
DebuggerCoordinates tallest = traceManager.getCurrent();
try (UndoableTransaction tid = tb.startTransaction()) {
new UnwindStackCommand(tool, tallest).applyTo(tb.trace, monitor);
}
waitForDomainObject(tb.trace);
}
@Test
public void testCaptureVariableValueHoverPluginListing() throws Throwable {
prepareContext();
// We're cheating the address game, but both use the same language without relocation
Instruction ins = tb.trace.getCodeManager().instructions().getAt(0, registerRefInstr);
DebuggerListingProvider listingProvider =
waitForComponentProvider(DebuggerListingProvider.class);
ListingPanel listingPanel = listingProvider.getListingPanel();
Window window = moveProviderToItsOwnWindow(listingProvider, 900, 600);
window.toFront();
waitForSwing();
HoverLocation loc =
StackUnwinderTest.findOperandLocation(listingPanel, ins, register("EDX"));
FieldLocation fLoc = loc.fLoc();
BigInteger refIndex = listingPanel.getAddressIndexMap().getIndex(registerRefInstr);
FieldPanel fieldPanel = listingPanel.getFieldPanel();
runSwing(() -> fieldPanel.goTo(refIndex, fLoc.fieldNum, fLoc.row, fLoc.col, false));
waitForSwing();
Rectangle rect = listingPanel.getCursorBounds();
MouseEvent event =
new MouseEvent(fieldPanel, 0, System.currentTimeMillis(), 0, rect.x, rect.y, 0, false);
fieldPanel.getHoverHandler().mouseHovered(event);
waitForSwing();
captureProviderWithScreenShot(listingProvider);
}
@Test
public void testCaptureVariableValueHoverPluginBrowser() throws Throwable {
CodeViewerProvider browserProvider = waitForComponentProvider(CodeViewerProvider.class);
prepareContext();
List<Address> stackRefs = new ArrayList<>(stackRefInstrs.keySet());
Address refAddr = stackRefs.get(2);
Instruction ins = program.getListing().getInstructionAt(refAddr);
ListingPanel listingPanel = browserProvider.getListingPanel();
Window window = moveProviderToItsOwnWindow(browserProvider, 1000, 600);
window.toFront();
waitForSwing();
HoverLocation loc =
StackUnwinderTest.findOperandLocation(listingPanel, ins, new Scalar(32, 8));
FieldLocation fLoc = loc.fLoc();
BigInteger refIndex = listingPanel.getAddressIndexMap().getIndex(refAddr);
FieldPanel fieldPanel = listingPanel.getFieldPanel();
runSwing(() -> fieldPanel.goTo(refIndex, fLoc.fieldNum, fLoc.row, fLoc.col, false));
waitForSwing();
Rectangle rect = listingPanel.getCursorBounds();
MouseEvent event =
new MouseEvent(fieldPanel, 0, System.currentTimeMillis(), 0, rect.x, rect.y, 0, false);
fieldPanel.getHoverHandler().mouseHovered(event);
waitForSwing();
captureProviderWithScreenShot(browserProvider);
}
@Test
public void testCaptureVariableValueHoverPluginDecompiler() throws Throwable {
DecompilerProvider decompilerProvider = waitForComponentProvider(DecompilerProvider.class);
tool.showComponentProvider(decompilerProvider, true);
prepareContext();
Function function = program.getFunctionManager().getFunctionContaining(registerRefInstr);
DecompilerPanel decompilerPanel = decompilerProvider.getDecompilerPanel();
Window window = moveProviderToItsOwnWindow(decompilerProvider, 600, 600);
window.toFront();
waitForSwing();
HoverLocation loc =
StackUnwinderTest.findTokenLocation(decompilerPanel, function, "n", "if (1 < n) {");
runSwing(() -> decompilerPanel.goToToken(loc.token()));
waitForSwing();
FieldPanel fieldPanel = decompilerPanel.getFieldPanel();
Rectangle rect = fieldPanel.getCursorBounds();
MouseEvent event =
new MouseEvent(fieldPanel, 0, System.currentTimeMillis(), 0, rect.x, rect.y, 0, false);
fieldPanel.getHoverHandler().mouseHovered(event);
waitForSwing();
captureProviderWithScreenShot(decompilerProvider);
}
}

View file

@ -15,7 +15,10 @@
*/
package ghidra.pcode.exec.trace;
import java.util.Map;
import generic.ULongSpan.ULongSpanSet;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
@ -38,6 +41,17 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiec
super(language, space, backing);
}
protected CheckedCachedSpace(Language language, AddressSpace space,
PcodeTraceDataAccess backing, SemisparseByteArray bytes, AddressSet written) {
super(language, space, backing, bytes, written);
}
@Override
public CachedSpace fork() {
return new CheckedCachedSpace(language, space, backing, bytes.fork(),
new AddressSet(written));
}
@Override
public byte[] read(long offset, int size, Reason reason) {
ULongSpanSet uninitialized =
@ -59,14 +73,34 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiec
super(data);
}
protected AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data,
AbstractSpaceMap<CachedSpace> spaceMap) {
super(data, spaceMap);
}
protected class CheckedCachedSpaceMap extends TraceBackedSpaceMap {
public CheckedCachedSpaceMap() {
super();
}
protected CheckedCachedSpaceMap(Map<AddressSpace, CachedSpace> spaces) {
super(spaces);
}
@Override
protected CachedSpace newSpace(AddressSpace space, PcodeTraceDataAccess backing) {
return new CheckedCachedSpace(language, space, backing);
}
@Override
public CheckedCachedSpaceMap fork() {
return new CheckedCachedSpaceMap(fork(spaces));
}
}
@Override
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TraceBackedSpaceMap() {
@Override
protected CachedSpace newSpace(AddressSpace space, PcodeTraceDataAccess backing) {
return new CheckedCachedSpace(language, space, backing);
}
};
return new CheckedCachedSpaceMap();
}
/**

View file

@ -15,8 +15,7 @@
*/
package ghidra.pcode.exec.trace;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import javax.help.UnsupportedOperationException;
@ -24,6 +23,7 @@ import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
/**
@ -43,7 +43,7 @@ public class AddressesReadTracePcodeExecutorStatePiece
implements TracePcodeExecutorStatePiece<byte[], AddressSetView> {
protected final PcodeTraceDataAccess data;
private final Map<Long, AddressSetView> unique = new HashMap<>();
private final Map<Long, AddressSetView> unique;
/**
* Construct the state piece
@ -54,6 +54,15 @@ public class AddressesReadTracePcodeExecutorStatePiece
super(data.getLanguage(), BytesPcodeArithmetic.forLanguage(data.getLanguage()),
AddressesReadPcodeArithmetic.INSTANCE);
this.data = data;
this.unique = new HashMap<>();
}
protected AddressesReadTracePcodeExecutorStatePiece(PcodeTraceDataAccess data,
Map<Long, AddressSetView> unique) {
super(data.getLanguage(), BytesPcodeArithmetic.forLanguage(data.getLanguage()),
AddressesReadPcodeArithmetic.INSTANCE);
this.data = data;
this.unique = unique;
}
@Override
@ -66,11 +75,27 @@ public class AddressesReadTracePcodeExecutorStatePiece
return data;
}
@Override
public AddressesReadTracePcodeExecutorStatePiece fork() {
return new AddressesReadTracePcodeExecutorStatePiece(data, new HashMap<>(unique));
}
@Override
public void writeDown(PcodeTraceDataAccess into) {
throw new UnsupportedOperationException();
}
@Override
protected Map<Register, AddressSetView> getRegisterValuesFromSpace(AddressSpace s,
List<Register> registers) {
return Map.of();
}
@Override
public Map<Register, AddressSetView> getRegisterValues() {
return Map.of();
}
@Override
protected AddressSpace getForSpace(AddressSpace space, boolean toWrite) {
return space;

View file

@ -29,4 +29,13 @@ public class BytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState
public BytesTracePcodeExecutorState(PcodeTraceDataAccess data) {
super(new BytesTracePcodeExecutorStatePiece(data));
}
protected BytesTracePcodeExecutorState(TracePcodeExecutorStatePiece<byte[], byte[]> piece) {
super(piece);
}
@Override
public BytesTracePcodeExecutorState fork() {
return new BytesTracePcodeExecutorState(piece.fork());
}
}

View file

@ -16,11 +16,12 @@
package ghidra.pcode.exec.trace;
import java.nio.ByteBuffer;
import java.util.Map;
import generic.ULongSpan;
import generic.ULongSpan.ULongSpanSet;
import ghidra.pcode.exec.AbstractBytesPcodeExecutorStatePiece;
import ghidra.pcode.exec.BytesPcodeExecutorStateSpace;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece.CachedSpace;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*;
@ -41,11 +42,23 @@ public class BytesTracePcodeExecutorStatePiece
protected static class CachedSpace
extends BytesPcodeExecutorStateSpace<PcodeTraceDataAccess> {
protected final AddressSet written = new AddressSet();
protected final AddressSet written;
public CachedSpace(Language language, AddressSpace space, PcodeTraceDataAccess backing) {
// Backing could be null, so we need language parameter
super(language, space, backing);
this.written = new AddressSet();
}
protected CachedSpace(Language language, AddressSpace space, PcodeTraceDataAccess backing,
SemisparseByteArray bytes, AddressSet written) {
super(language, space, backing, bytes);
this.written = written;
}
@Override
public CachedSpace fork() {
return new CachedSpace(language, space, backing, bytes.fork(), new AddressSet(written));
}
@Override
@ -118,11 +131,22 @@ public class BytesTracePcodeExecutorStatePiece
this.data = data;
}
protected BytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data,
AbstractSpaceMap<CachedSpace> spaceMap) {
super(data.getLanguage(), spaceMap);
this.data = data;
}
@Override
public PcodeTraceDataAccess getData() {
return data;
}
@Override
public BytesTracePcodeExecutorStatePiece fork() {
return new BytesTracePcodeExecutorStatePiece(data, spaceMap.fork());
}
@Override
public void writeDown(PcodeTraceDataAccess into) {
if (into.getLanguage() != language) {
@ -139,6 +163,14 @@ public class BytesTracePcodeExecutorStatePiece
*/
protected class TraceBackedSpaceMap
extends CacheingSpaceMap<PcodeTraceDataAccess, CachedSpace> {
public TraceBackedSpaceMap() {
super();
}
protected TraceBackedSpaceMap(Map<AddressSpace, CachedSpace> spaces) {
super(spaces);
}
@Override
protected PcodeTraceDataAccess getBacking(AddressSpace space) {
return data;
@ -148,6 +180,16 @@ public class BytesTracePcodeExecutorStatePiece
protected CachedSpace newSpace(AddressSpace space, PcodeTraceDataAccess backing) {
return new CachedSpace(language, space, backing);
}
@Override
public TraceBackedSpaceMap fork() {
return new TraceBackedSpaceMap(fork(spaces));
}
@Override
public CachedSpace fork(CachedSpace s) {
return s.fork();
}
}
@Override

View file

@ -44,6 +44,11 @@ public class DefaultTracePcodeExecutorState<T> extends DefaultPcodeExecutorState
return piece.getData();
}
@Override
public DefaultTracePcodeExecutorState<T> fork() {
return new DefaultTracePcodeExecutorState<>(piece.fork());
}
@Override
public void writeDown(PcodeTraceDataAccess into) {
piece.writeDown(into);

View file

@ -60,6 +60,11 @@ public class DirectBytesTracePcodeExecutorState extends DefaultTracePcodeExecuto
super(new DirectBytesTracePcodeExecutorStatePiece(data));
}
protected DirectBytesTracePcodeExecutorState(
TracePcodeExecutorStatePiece<byte[], byte[]> piece) {
super(piece);
}
/**
* Create the state
*
@ -83,4 +88,9 @@ public class DirectBytesTracePcodeExecutorState extends DefaultTracePcodeExecuto
return new PairedPcodeExecutorState<>(this,
new TraceMemoryStatePcodeExecutorStatePiece(getData()));
}
@Override
public DirectBytesTracePcodeExecutorState fork() {
return new DirectBytesTracePcodeExecutorState(piece.fork());
}
}

View file

@ -16,6 +16,8 @@
package ghidra.pcode.exec.trace;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import javax.help.UnsupportedOperationException;
@ -27,6 +29,7 @@ import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.memory.TraceMemoryState;
@ -48,7 +51,7 @@ public class DirectBytesTracePcodeExecutorStatePiece
protected final PcodeTraceDataAccess data;
protected final SemisparseByteArray unique = new SemisparseByteArray();
protected final SemisparseByteArray unique;
/**
* Construct a piece
@ -57,9 +60,10 @@ public class DirectBytesTracePcodeExecutorStatePiece
* @param data the trace-data access shim
*/
protected DirectBytesTracePcodeExecutorStatePiece(PcodeArithmetic<byte[]> arithmetic,
PcodeTraceDataAccess data) {
PcodeTraceDataAccess data, SemisparseByteArray unique) {
super(data.getLanguage(), arithmetic, arithmetic);
this.data = data;
this.unique = unique;
}
/**
@ -68,7 +72,7 @@ public class DirectBytesTracePcodeExecutorStatePiece
* @param data the trace-data access shim
*/
public DirectBytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
this(BytesPcodeArithmetic.forLanguage(data.getLanguage()), data);
this(BytesPcodeArithmetic.forLanguage(data.getLanguage()), data, new SemisparseByteArray());
}
@Override
@ -76,6 +80,11 @@ public class DirectBytesTracePcodeExecutorStatePiece
return data;
}
@Override
public DirectBytesTracePcodeExecutorStatePiece fork() {
return new DirectBytesTracePcodeExecutorStatePiece(arithmetic, data, unique.fork());
}
/**
* Create a state which computes an expression's {@link TraceMemoryState} as an auxiliary
* attribute
@ -129,6 +138,17 @@ public class DirectBytesTracePcodeExecutorStatePiece
return buf.array();
}
@Override
protected Map<Register, byte[]> getRegisterValuesFromSpace(AddressSpace s,
List<Register> registers) {
return Map.of();
}
@Override
public Map<Register, byte[]> getRegisterValues() {
return Map.of();
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
throw new UnsupportedOperationException();

View file

@ -30,30 +30,30 @@ import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
public class PairedTracePcodeExecutorState<L, R> extends PairedPcodeExecutorState<L, R>
implements TracePcodeExecutorState<Pair<L, R>> {
private final TracePcodeExecutorStatePiece<L, L> left;
private final TracePcodeExecutorStatePiece<L, R> right;
private final PairedTracePcodeExecutorStatePiece<L, L, R> piece;
public PairedTracePcodeExecutorState(PairedTracePcodeExecutorStatePiece<L, L, R> piece) {
super(piece);
this.left = piece.getLeft();
this.right = piece.getRight();
this.piece = piece;
}
public PairedTracePcodeExecutorState(TracePcodeExecutorState<L> left,
TracePcodeExecutorStatePiece<L, R> right) {
super(left, right);
this.left = left;
this.right = right;
this(new PairedTracePcodeExecutorStatePiece<>(left, right));
}
@Override
public PcodeTraceDataAccess getData() {
return left.getData();
return piece.getData();
}
@Override
public PairedTracePcodeExecutorState<L, R> fork() {
return new PairedTracePcodeExecutorState<>(piece.fork());
}
@Override
public void writeDown(PcodeTraceDataAccess into) {
left.writeDown(into);
right.writeDown(into);
piece.writeDown(into);
}
}

View file

@ -56,6 +56,12 @@ public class PairedTracePcodeExecutorStatePiece<A, L, R>
return left.getData();
}
@Override
public PairedTracePcodeExecutorStatePiece<A, L, R> fork() {
return new PairedTracePcodeExecutorStatePiece<>(left.fork(), right.fork(),
getAddressArithmetic(), getArithmetic());
}
@Override
public void writeDown(PcodeTraceDataAccess into) {
left.writeDown(into);

View file

@ -31,4 +31,14 @@ public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorState
public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(PcodeTraceDataAccess data) {
super(new RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(data));
}
protected RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(
TracePcodeExecutorStatePiece<byte[], byte[]> piece) {
super(piece);
}
@Override
public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState fork() {
return new RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(piece.fork());
}
}

View file

@ -40,6 +40,17 @@ public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece
super(data);
}
protected RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data,
AbstractSpaceMap<CachedSpace> spaceMap) {
super(data, spaceMap);
}
@Override
public RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece fork() {
return new RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(data,
spaceMap.fork());
}
@Override
protected AddressSetView getKnown(PcodeTraceDataAccess backing) {
return backing.getKnownBefore();

View file

@ -31,4 +31,14 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorState
public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(PcodeTraceDataAccess data) {
super(new RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(data));
}
protected RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(
TracePcodeExecutorStatePiece<byte[], byte[]> piece) {
super(piece);
}
@Override
public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState fork() {
return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(piece.fork());
}
}

View file

@ -16,6 +16,7 @@
package ghidra.pcode.exec.trace;
import ghidra.pcode.exec.AccessPcodeExecutionException;
import ghidra.pcode.exec.PcodeExecutorStatePiece;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*;
import ghidra.trace.model.memory.TraceMemorySpace;
@ -34,6 +35,17 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece
super(data);
}
protected RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data,
AbstractSpaceMap<CachedSpace> spaceMap) {
super(data, spaceMap);
}
@Override
public RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece fork() {
return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(data,
spaceMap.fork());
}
/**
* Construct a piece
*

View file

@ -15,6 +15,8 @@
*/
package ghidra.pcode.exec.trace;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import generic.ULongSpan;
@ -23,6 +25,7 @@ import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
@ -42,7 +45,7 @@ import ghidra.trace.model.memory.TraceMemoryState;
public class TraceMemoryStatePcodeExecutorStatePiece extends
AbstractLongOffsetPcodeExecutorStatePiece<byte[], TraceMemoryState, AddressSpace> {
protected final MutableULongSpanMap<TraceMemoryState> unique = new DefaultULongSpanMap<>();
protected final MutableULongSpanMap<TraceMemoryState> unique;
protected final PcodeTraceDataAccess data;
/**
@ -55,6 +58,22 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
BytesPcodeArithmetic.forLanguage(data.getLanguage()),
TraceMemoryStatePcodeArithmetic.INSTANCE);
this.data = data;
this.unique = new DefaultULongSpanMap<>();
}
protected TraceMemoryStatePcodeExecutorStatePiece(PcodeTraceDataAccess data,
MutableULongSpanMap<TraceMemoryState> unique) {
super(data.getLanguage(), BytesPcodeArithmetic.forLanguage(data.getLanguage()),
TraceMemoryStatePcodeArithmetic.INSTANCE);
this.data = data;
this.unique = unique;
}
@Override
public TraceMemoryStatePcodeExecutorStatePiece fork() {
MutableULongSpanMap<TraceMemoryState> copyUnique = new DefaultULongSpanMap<>();
copyUnique.putAll(unique);
return new TraceMemoryStatePcodeExecutorStatePiece(data, copyUnique);
}
protected AddressRange range(AddressSpace space, long offset, int size) {
@ -107,6 +126,17 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
return TraceMemoryState.UNKNOWN;
}
@Override
protected Map<Register, TraceMemoryState> getRegisterValuesFromSpace(AddressSpace s,
List<Register> registers) {
return Map.of();
}
@Override
public Map<Register, TraceMemoryState> getRegisterValues() {
return Map.of();
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
throw new ConcretionError("Cannot make TraceMemoryState into a concrete buffer", purpose);

View file

@ -23,13 +23,14 @@ import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
*
* <p>
* In particular, because this derives from {@link TracePcodeExecutorStatePiece}, such states are
* required to implement {@link #writeDown(PcodeTraceDataAccess)}. This interface also
* derives from {@link PcodeExecutorState} so that, as the name implies, they can be used where a
* state is required.
* required to implement {@link #writeDown(PcodeTraceDataAccess)}. This interface also derives from
* {@link PcodeExecutorState} so that, as the name implies, they can be used where a state is
* required.
*
* @param <T> the type of values
*/
public interface TracePcodeExecutorState<T>
extends PcodeExecutorState<T>, TracePcodeExecutorStatePiece<T, T> {
// Nothing to add. Simply a composition of interfaces.
@Override
TracePcodeExecutorState<T> fork();
}

View file

@ -38,6 +38,9 @@ public interface TracePcodeExecutorStatePiece<A, T> extends PcodeExecutorStatePi
*/
PcodeTraceDataAccess getData();
@Override
TracePcodeExecutorStatePiece<A, T> fork();
/**
* Write the accumulated values (cache) into the given trace
*

View file

@ -256,7 +256,7 @@ public class UndefinedDBTraceData implements DBTraceDataAdapter, DBTraceSpaceKey
}
@Override
public Data getComponentContaining(int offset) {
public TraceData getComponentContaining(int offset) {
return null;
}

View file

@ -42,7 +42,8 @@ public interface DBTraceDelegatingManager<M> {
default void checkIsInMemory(AddressSpace space) {
if (!space.isMemorySpace() && space != Address.NO_ADDRESS.getAddressSpace()) {
throw new IllegalArgumentException("Address must be in memory or NO_ADDRESS");
throw new IllegalArgumentException(
"Address must be in memory or NO_ADDRESS. Got " + space);
}
}

View file

@ -29,6 +29,9 @@ public interface TraceData extends TraceCodeUnit, Data {
@Override
TraceData getComponentAt(int offset);
@Override
TraceData getComponentContaining(int offset);
@Override
TraceData getComponent(int[] componentPath);

View file

@ -333,6 +333,29 @@ public interface TraceMemoryOperations {
Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(long snap,
AddressRange range);
/**
* Check if a range addresses are all known
*
* @param snap the time
* @param range the range to examine
* @return true if the entire range is {@link TraceMemoryState#KNOWN}
*/
default boolean isKnown(long snap, AddressRange range) {
Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> states = getStates(snap, range);
if (states.isEmpty()) {
return false;
}
if (states.size() != 1) {
return false;
}
AddressRange entryRange = states.iterator().next().getKey().getRange();
if (!entryRange.contains(range.getMinAddress()) ||
!entryRange.contains(range.getMaxAddress())) {
return false;
}
return true;
}
/**
* Break a range of addresses into smaller ranges each mapped to its most recent state at the
* given time

View file

@ -118,6 +118,9 @@ public interface Scheduler {
TickStep slice = nextSlice(trace);
eventThread = slice.getThread(tm, eventThread);
emuThread = machine.getThread(eventThread.getPath(), true);
if (emuThread.getFrame() != null) {
emuThread.finishInstruction();
}
for (int i = 0; i < slice.tickCount; i++) {
monitor.checkCanceled();
emuThread.stepInstruction();

View file

@ -200,7 +200,7 @@ public enum TraceRegisterUtils {
public static void requireByteBound(Register register) {
if (!isByteBound(register)) {
throw new IllegalArgumentException(
"Cannot work with sub-byte registers. Consider a parent, instead.");
"Cannot work with sub-byte registers. Consider a parent instead.");
}
}
}

View file

@ -1,20 +0,0 @@
/* ###
* 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 docking.widgets;
public class ExpanderArrowExpansionVetoException extends Exception {
}

View file

@ -1,155 +0,0 @@
/* ###
* 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 docking.widgets;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.CompletableFuture;
import javax.swing.JPanel;
import ghidra.util.datastruct.ListenerSet;
public class ExpanderArrowPanel extends JPanel {
// TODO: Can I make this consistent with the UI LaF
protected final static Polygon ARROW =
new Polygon(new int[] { 5, -5, -5 }, new int[] { 0, -5, 5 }, 3);
protected final static Dimension SIZE = new Dimension(16, 16);
protected final static int ANIM_MILLIS = 80;
protected final static int FRAME_MILLIS = 30; // Approx 30 fps
private final ListenerSet<ExpanderArrowExpansionListener> listeners =
new ListenerSet<>(ExpanderArrowExpansionListener.class);
private boolean expanded = false;
private double animTheta;
private boolean animActive = false;
private long animTimeEnd;
private double animThetaEnd;
private double animThetaOverTimeRate;
private final MouseListener mouseListener = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
toggle();
}
};
{
addMouseListener(mouseListener);
}
public void addExpansionListener(ExpanderArrowExpansionListener listener) {
listeners.add(listener);
}
public void removeExpansionListener(ExpanderArrowExpansionListener listener) {
listeners.remove(listener);
}
protected synchronized void animateTheta(double destTheta) {
animTimeEnd = System.currentTimeMillis() + ANIM_MILLIS;
animThetaEnd = destTheta;
animThetaOverTimeRate = (destTheta - animTheta) / ANIM_MILLIS;
animActive = true;
scheduleNextFrame();
}
public void toggle() {
setExpanded(!expanded);
}
protected boolean fireChanging(boolean newExpanded) {
try {
listeners.fire.changing(newExpanded);
}
catch (ExpanderArrowExpansionVetoException e) {
return false;
}
return true;
}
protected void fireChanged() {
listeners.fire.changed(expanded);
}
public void setExpanded(boolean expanded) {
if (this.expanded == expanded) {
return;
}
if (!fireChanging(expanded)) {
return;
}
double destTheta = expanded ? Math.PI / 2 : 0;
animateTheta(destTheta);
this.expanded = expanded;
fireChanged();
}
public boolean isExpanded() {
return expanded;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.translate((double) SIZE.width / 2, (double) SIZE.height / 2);
g2.rotate(animTheta);
g2.fillPolygon(ARROW);
if (!animActive) {
return;
}
long time = System.currentTimeMillis();
double timeDiff = Math.max(0, animTimeEnd - time);
if (timeDiff != 0) {
double thetaDiff = timeDiff * animThetaOverTimeRate;
animTheta = animThetaEnd - thetaDiff;
scheduleNextFrame();
return;
}
animActive = false;
if (animTheta != animThetaEnd) {
animTheta = animThetaEnd;
scheduleNextFrame();
}
}
@Override
public Dimension getPreferredSize() {
return SIZE;
}
@Override
public Dimension getMinimumSize() {
return SIZE;
}
protected void scheduleNextFrame() {
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(FRAME_MILLIS);
}
catch (InterruptedException e) {
// Whatever. Render early.
}
repaint();
});
}
}

View file

@ -912,7 +912,7 @@ public interface Span<N, S extends Span<N, S>> extends Comparable<S> {
* @param max the upper endpoint of the span
* @return the sub map
*/
protected SortedMap<N, Entry<S, V>> subMap(N min, N max) {
protected NavigableMap<N, Entry<S, V>> subMap(N min, N max) {
Entry<N, Entry<S, V>> adjEnt = spanTree.floorEntry(min);
if (adjEnt != null && adjEnt.getValue().getKey().contains(min)) {
min = adjEnt.getKey();

View file

@ -24,6 +24,16 @@ import java.util.stream.Stream;
*/
public interface Unique {
static <T> T assertAtMostOne(T[] arr) {
if (arr.length == 0) {
return null;
}
if (arr.length == 1) {
return arr[0];
}
throw new AssertionError("Expected at most one. Got many: " + List.of(arr));
}
/**
* Assert that exactly one element is in an iterable and get that element
*

View file

@ -17,8 +17,9 @@ package ghidra.generic.util.datastruct;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import generic.ULongSpan;
import generic.ULongSpan.*;
@ -62,8 +63,33 @@ public class SemisparseByteArray {
/** The size of blocks used internally to store array values */
public static final int BLOCK_SIZE = 0x1000;
private final Map<Long, byte[]> blocks = new HashMap<>();
private final MutableULongSpanSet defined = new DefaultULongSpanSet();
private final Map<Long, byte[]> blocks;
private final MutableULongSpanSet defined;
public SemisparseByteArray() {
this.blocks = new HashMap<>();
this.defined = new DefaultULongSpanSet();
}
protected SemisparseByteArray(Map<Long, byte[]> blocks, MutableULongSpanSet defined) {
this.blocks = blocks;
this.defined = defined;
}
static byte[] copyArr(Map.Entry<?, byte[]> ent) {
byte[] b = ent.getValue();
return Arrays.copyOf(b, b.length);
}
public synchronized SemisparseByteArray fork() {
// TODO Could use some copy-on-write optimization here and in parents
Map<Long, byte[]> copyBlocks = blocks.entrySet()
.stream()
.collect(Collectors.toMap(Entry::getKey, SemisparseByteArray::copyArr));
MutableULongSpanSet copyDefined = new DefaultULongSpanSet();
copyDefined.addAll(defined);
return new SemisparseByteArray(copyBlocks, copyDefined);
}
/**
* Clear the array

View file

@ -15,7 +15,7 @@
*/
package ghidra.pcode.emu;
import java.util.Objects;
import java.util.*;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
@ -23,6 +23,7 @@ import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
/**
@ -62,6 +63,11 @@ public class ThreadPcodeExecutorState<T> implements PcodeExecutorState<T> {
return arithmetic;
}
@Override
public ThreadPcodeExecutorState<T> fork() {
return new ThreadPcodeExecutorState<>(sharedState.fork(), localState.fork());
}
/**
* Decide whether or not access to the given space is directed to thread-local state
*
@ -89,6 +95,14 @@ public class ThreadPcodeExecutorState<T> implements PcodeExecutorState<T> {
return sharedState.getVar(space, offset, size, quantize, reason);
}
@Override
public Map<Register, T> getRegisterValues() {
Map<Register, T> result = new HashMap<>();
result.putAll(localState.getRegisterValues());
result.putAll(sharedState.getRegisterValues());
return result;
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
assert !isThreadLocalSpace(address.getAddressSpace());

View file

@ -0,0 +1,430 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.eval;
import java.util.HashMap;
import java.util.Map;
import ghidra.pcode.error.LowlevelError;
import ghidra.pcode.exec.*;
import ghidra.pcode.opbehavior.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
/**
* An abstract implementation of {@link VarnodeEvaluator}
*
* <p>
* Unlike {@link PcodeExecutor} this abstract class is not explicitly bound to a p-code state nor
* arithmetic. Instead it defines abstract methods for accessing "leaf" varnodes and evaluating ops.
* To evaluate a varnode, it first checks if the varnode is a leaf, which is defined by an extension
* class. If it is, it converts the static address to a dynamic one and invokes the appropriate
* value getter. An extension class would likely implement those getters using a
* {@link PcodeExecutorState}. If the varnode is not a leaf, the evaluator will ascend by examining
* its defining p-code op, evaluate its input varnodes recursively and then compute the output using
* the provided p-code arithmetic. This implementation maintains a map of evaluated varnodes and
* their values so that any intermediate varnode is evaluated just once. Note that the evaluation
* algorithm assumes their are no cycles in the AST, which should be the case by definition.
*
* @param <T> the type of values resulting from evaluation
*/
public abstract class AbstractVarnodeEvaluator<T> implements VarnodeEvaluator<T> {
/**
* Concatenate the given values
*
* @param sizeTotal the expected output size in bytes
* @param upper the value of the left (more significant) piece
* @param lower the value of the right (less significant) piece
* @param sizeLower the size of the lower piece
* @return the result of concatenation
*/
protected abstract T catenate(int sizeTotal, T upper, T lower, int sizeLower);
/**
* Check if the given varnode is a leaf in the evaluation
*
* <p>
* This allows the extension class to determine the base case when recursively ascending the
* AST.
*
* @param vn the varnode
* @return true to treat the varnode as a base case, or false to ascend to its defining p-code
* op
*/
protected abstract boolean isLeaf(Varnode vn);
/**
* Resolve a (static) stack offset to its physical (dynamic) address in the frame
*
* <p>
* When a leaf varnode is a stack address, this is used to map it to a physical address before
* invoking {@link #evaluateMemory(Address, int)}.
*
* @param offset the offset
* @return the address in target memory
*/
protected abstract Address applyBase(long offset);
/**
* Map the given static address to dynamic
*
* <p>
* When a leaf varnode is a memory address, this is used to map it to a dynamic address before
* invoking {@link #evaluateMemory(Address, int)}. This is needed in case the module has been
* relocated in the dynamic context. Note this is not used to translate register or stack
* addresses, since those are abstract concepts. Stack addresses are translated using
* {@link #applyBase(long)}, the result of which should already be a dynamic address.
*
* @param program the program specifying the static context
* @param address the address in the static context
* @return the address in the dynamic context
*/
protected Address translateMemory(Program program, Address address) {
return address;
}
/**
* Evaluate a leaf varnode
*
* <p>
* This method translates the varnode accordingly and delegates the evaluation, indirectly, to
* {@link #evaluateMemory(Address, int)}. Notable exceptions are constants, which are just
* evaluated to their immediate value, and unique variables, which cannot ordinarily be leaves.
*
* @param program the program defining the static context
* @param vn the varnode
* @return the value obtained from the dynamic context
*/
protected T evaluateLeaf(Program program, Varnode vn) {
Address address = vn.getAddress();
if (address.isConstantAddress()) {
return evaluateConstant(vn.getOffset(), vn.getSize());
}
else if (address.isRegisterAddress()) {
return evaluateRegister(address, vn.getSize());
}
else if (address.isStackAddress()) {
return evaluateStack(address.getOffset(), vn.getSize());
}
else if (address.isMemoryAddress()) {
return evaluateMemory(translateMemory(program, address), vn.getSize());
}
else if (address.isUniqueAddress()) {
return evaluateUnique(vn.getOffset(), vn.getSize());
}
else {
throw new PcodeExecutionException("Unrecognized address space in " + vn);
}
}
/**
* Evaluate a varnode, which could be either a leaf or a branch
*
* <p>
* This method is invoked by {@link #evaluateVarnode(Program, Varnode, Map)} when the value has
* not already been computed. Only that method should invoke this one directly.
*
* @param program the program defining the static context
* @param vn the varnode
* @param already a cache of already-evaluated varnodes and their values
* @return the value
*/
protected T doEvaluateVarnode(Program program, Varnode vn, Map<Varnode, T> already) {
if (isLeaf(vn)) {
return evaluateLeaf(program, vn);
}
return evaluateBranch(program, vn, already);
}
/**
* Evaluate a varnode, which could be either a leaf or a branch, taking its cached value if
* available
*
* @param program the program defining the static context
* @param vn the varnode
* @param already a cache of already-evaluated varnodes and their values
* @return the value
*/
protected T evaluateVarnode(Program program, Varnode vn, Map<Varnode, T> already) {
// computeIfAbsent does nto work because of the recursion. Will get CME.
if (already.containsKey(vn)) {
return already.get(vn);
}
T result = doEvaluateVarnode(program, vn, already);
already.put(vn, result);
return result;
}
/**
* Evaluate a varnode
*
* @param program the program containing the varnode
* @param vn the varnode to evaluate
* @return the value of the varnode
*/
@Override
public T evaluateVarnode(Program program, Varnode vn) {
return evaluateVarnode(program, vn, new HashMap<>());
}
/**
* Evaluate variable storage, providing an "identity" value
*
* @param storage the storage to evaluate
* @param identity the value if storage had no varnodes
* @return the value of the storage
*/
protected T evaluateStorage(VariableStorage storage, T identity) {
Program program = storage.getProgram();
int total = storage.size();
T value = identity;
for (Varnode vn : storage.getVarnodes()) {
T piece = evaluateVarnode(program, vn);
value = catenate(total, value, piece, vn.getSize());
}
return value;
}
/**
* Evaluate the given varnode's defining p-code op
*
* @param program the program defining the static context
* @param vn the varnode
* @param already a cache of already-evaluated varnodes and their values
* @return the value
*/
protected T evaluateBranch(Program program, Varnode vn, Map<Varnode, T> already) {
PcodeOp def = vn.getDef();
if (def == null || def.getOutput() != vn) {
throw new PcodeExecutionException("No defining p-code op for " + vn);
}
return evaluateOp(program, def, already);
}
/**
* Evaluate a constant
*
* @param value the constant value
* @param size the size of the value in bytes
* @return the value as a {@link T}
*/
protected abstract T evaluateConstant(long value, int size);
/**
* Evaluate the given register variable
*
* @param address the address of the register
* @param size the size of the variable in bytes
* @return the value
*/
protected T evaluateRegister(Address address, int size) {
return evaluateMemory(address, size);
}
/**
* Evaluate the given stack variable
*
* @param offset the stack offset of the variable
* @param size the size of the variable in bytes
* @return the value
*/
protected T evaluateStack(long offset, int size) {
return evaluateMemory(applyBase(offset), size);
}
/**
* Evaluate a variable in memory
*
* <p>
* By default all register, stack, and memory addresses are directed here. Register addresses
* are passed through without modification. Stack addresses will have the frame base applied via
* {@link #applyBase(long)}, and memory addresses will be mapped through
* {@link #translateMemory(Program, Address)}.
*
* @param address the address of the variable
* @param size the size of the variable in bytes
* @return the value
*/
protected abstract T evaluateMemory(Address address, int size);
/**
* Evaluate a unique variable
*
* <p>
* This is only invoked when trying to evaluate a leaf, which should never occur for a unique
* variable. Thus, by default, this throws a {@link PcodeExecutionException}.
*
* @param long the offset of the variable
* @param size the size of the variable in bytes
* @return the value
*/
protected T evaluateUnique(long offset, int size) {
throw new PcodeExecutionException(
String.format("Cannot evaluate unique $U%x:%d", offset, size));
}
/**
* Evaluate a variable whose offset is of type {@link T}
*
* <p>
* The three parameters {@code space}, {@code offset}, and {@code size} imitate the varnode
* triple, except that the offset is abstract. This is typically invoked for a
* {@link PcodeOp#LOAD}, i.e., a dereference.
*
* @param program the program defining the static context
* @param space the address space of the variable
* @param offset the offset of the variable
* @param size the size of the variable in bytes
* @param already a cache of already-evaluated varnodes and their values
* @return the value
*/
protected abstract T evaluateAbstract(Program program, AddressSpace space, T offset, int size,
Map<Varnode, T> already);
/**
* Evaluate a unary op
*
* <p>
* This evaluates the input varnode then computes the output value.
*
* @param program the program defining the static context
* @param op the op whose output to evaluate
* @param unOp the concrete behavior of the op
* @param already a cache of already-evaluated varnodes and their values
* @return the output value
*/
protected abstract T evaluateUnaryOp(Program program, PcodeOp op, UnaryOpBehavior unOp,
Map<Varnode, T> already);
/**
* Evaluate a binary op
*
* <p>
* This evaluates the input varnodes then computes the output value.
*
* @param program the program defining the static context
* @param op the op whose output to evaluate
* @param binOp the concrete behavior of the op
* @param already a cache of already-evaluated varnodes and their values
* @return the output value
*/
protected abstract T evaluateBinaryOp(Program program, PcodeOp op, BinaryOpBehavior binOp,
Map<Varnode, T> already);
/**
* Evaluate a {@link PcodeOp#PTRADD} op
*
* @param program the program defining the static context
* @param op the op whose output to evaluate
* @param already a cache of already-evaluated varnodes and their values
* @return the output value
*/
protected abstract T evaluatePtrAdd(Program program, PcodeOp op, Map<Varnode, T> already);
/**
* Evaluate a {@link PcodeOp#PTRSUB} op
*
* @param program the program defining the static context
* @param op the op whose output to evaluate
* @param already a cache of already-evaluated varnodes and their values
* @return the output value
*/
protected abstract T evaluatePtrSub(Program program, PcodeOp op, Map<Varnode, T> already);
/**
* Assert that a varnode is constant and get its value as an integer.
*
* <p>
* Here "constant" means a literal or immediate value. It does not read from the state.
*
* @param vn the varnode
* @return the value
*/
protected int getIntConst(Varnode vn) {
if (!vn.isConstant()) {
throw new IllegalArgumentException(vn + " is not a constant");
}
return (int) vn.getAddress().getOffset();
}
/**
* Evaluate a {@link PcodeOp#LOAD} op
*
* @param program the program defining the static context
* @param op the op whose output to evaluate
* @param already a cache of already-evaluated varnodes and their values
* @return the output value
*/
protected abstract T evaluateLoad(Program program, PcodeOp op, Map<Varnode, T> already);
@Override
public T evaluateOp(Program program, PcodeOp op) {
return evaluateOp(program, op, new HashMap<>());
}
/**
* Like {@link #evaluateOp(Program, PcodeOp)}, but uses a cache
*
* @param program the program defining the static context
* @param op the op whose output to evaluate
* @param already a cache of already-evaluated varnodes and their values
* @return the output value
*/
protected T evaluateOp(Program program, PcodeOp op, Map<Varnode, T> already) {
OpBehavior b = OpBehaviorFactory.getOpBehavior(op.getOpcode());
if (b == null) {
return badOp(op);
}
if (b instanceof UnaryOpBehavior unOp) {
return evaluateUnaryOp(program, op, unOp, already);
}
if (b instanceof BinaryOpBehavior binOp) {
return evaluateBinaryOp(program, op, binOp, already);
}
switch (op.getOpcode()) {
case PcodeOp.LOAD:
return evaluateLoad(program, op, already);
case PcodeOp.PTRADD:
return evaluatePtrAdd(program, op, already);
case PcodeOp.PTRSUB:
return evaluatePtrSub(program, op, already);
default:
return badOp(op);
}
}
/**
* The method invoked when an unrecognized or unsupported operator is encountered
*
* @param op the op
* @return the value, but this usually throws an exception
*/
protected T badOp(PcodeOp op) {
switch (op.getOpcode()) {
case PcodeOp.UNIMPLEMENTED:
throw new LowlevelError(
"Encountered an unimplemented instruction at " +
op.getSeqnum().getTarget());
default:
throw new LowlevelError(
"Unsupported p-code op at " + op.getSeqnum().getTarget() + ": " + op);
}
}
}

View file

@ -0,0 +1,150 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.eval;
import java.util.Map;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.opbehavior.BinaryOpBehavior;
import ghidra.pcode.opbehavior.UnaryOpBehavior;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
/**
* An abstract implementation of {@link VarnodeEvaluator} that evaluates ops using a bound
* {@link PcodeArithmetic}.
*
* @param <T> the type of values resulting from evaluation
*/
public abstract class ArithmeticVarnodeEvaluator<T> extends AbstractVarnodeEvaluator<T> {
/**
* A convenience for concatenating two varnodes
*
* <p>
* There is no p-code op for catenation, but it is easily achieved as one might do in C or
* SLEIGH: {@code shift} the left piece then {@code or} it with the right piece.
*
* @param <T> the type of values
* @param arithmetic the p-code arithmetic for values of type {@link T}
* @param sizeTotal the expected output size in bytes
* @param upper the value of the left (more significant) piece
* @param lower the value of the right (less significant) piece
* @param sizeLower the size of the lower piece
* @return the result of concatenation
*/
public static <T> T catenate(PcodeArithmetic<T> arithmetic, int sizeTotal, T upper, T lower,
int sizeLower) {
T zext = arithmetic.unaryOp(PcodeOp.INT_ZEXT, sizeTotal, sizeLower, lower);
T shift = arithmetic.binaryOp(PcodeOp.INT_LEFT, sizeTotal, sizeTotal, upper, 4,
arithmetic.fromConst(sizeLower * 8, 4));
return arithmetic.binaryOp(PcodeOp.INT_OR, sizeTotal, sizeTotal, shift, sizeTotal, zext);
}
private final PcodeArithmetic<T> arithmetic;
/**
* Construct an evaluator
*
* @param arithmetic the arithmetic for computing p-code op outputs
*/
public ArithmeticVarnodeEvaluator(PcodeArithmetic<T> arithmetic) {
this.arithmetic = arithmetic;
}
@Override
protected T catenate(int sizeTotal, T upper, T lower, int sizeLower) {
return catenate(arithmetic, sizeTotal, upper, lower, sizeLower);
}
@Override
public T evaluateStorage(VariableStorage storage) {
return evaluateStorage(storage, arithmetic.fromConst(0, storage.size()));
}
@Override
protected T evaluateConstant(long value, int size) {
return arithmetic.fromConst(value, size);
}
@Override
protected T evaluateAbstract(Program program, AddressSpace space, T offset, int size,
Map<Varnode, T> already) {
long concrete = arithmetic.toLong(offset, Purpose.LOAD);
Address address = space.getAddress(concrete);
// There is no actual varnode to have a defining op, so this will be a leaf
return evaluateMemory(translateMemory(program, address), size);
}
@Override
protected T evaluateUnaryOp(Program program, PcodeOp op, UnaryOpBehavior unOp,
Map<Varnode, T> already) {
Varnode in1Var = op.getInput(0);
T in1 = evaluateVarnode(program, in1Var, already);
return arithmetic.unaryOp(op, in1);
}
@Override
protected T evaluateBinaryOp(Program program, PcodeOp op, BinaryOpBehavior binOp,
Map<Varnode, T> already) {
Varnode in1Var = op.getInput(0);
Varnode in2Var = op.getInput(1);
T in1 = evaluateVarnode(program, in1Var, already);
T in2 = evaluateVarnode(program, in2Var, already);
return arithmetic.binaryOp(op, in1, in2);
}
@Override
protected T evaluatePtrAdd(Program program, PcodeOp op, Map<Varnode, T> already) {
Varnode baseVar = op.getInput(0);
Varnode indexVar = op.getInput(1);
int size = getIntConst(op.getInput(2));
Varnode outVar = op.getOutput();
T base = evaluateVarnode(program, baseVar, already);
T index = evaluateVarnode(program, indexVar, already);
return arithmetic.ptrAdd(outVar.getSize(), baseVar.getSize(), base, indexVar.getSize(),
index, size);
}
@Override
protected T evaluatePtrSub(Program program, PcodeOp op, Map<Varnode, T> already) {
Varnode baseVar = op.getInput(0);
Varnode offsetVar = op.getInput(1);
Varnode outVar = op.getOutput();
T base = evaluateVarnode(program, baseVar, already);
T offset = evaluateVarnode(program, offsetVar, already);
return arithmetic.ptrSub(outVar.getSize(),
baseVar.getSize(), base,
offsetVar.getSize(), offset);
}
@Override
protected T evaluateLoad(Program program, PcodeOp op, Map<Varnode, T> already) {
int spaceID = getIntConst(op.getInput(0));
AddressSpace space = program.getAddressFactory().getAddressSpace(spaceID);
Varnode inOffset = op.getInput(1);
T offset = evaluateVarnode(program, inOffset, already);
Varnode outVar = op.getOutput(); // Only for measuring size
T out = evaluateAbstract(program, space, offset, outVar.getSize(), already);
return arithmetic.modAfterLoad(outVar.getSize(),
inOffset.getSize(), offset,
outVar.getSize(), out);
}
}

View file

@ -0,0 +1,66 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.eval;
import ghidra.pcode.exec.PcodeExecutor;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
/**
* An evaluator of high varnodes
*
* <p>
* This is a limited analog to {@link PcodeExecutor} but for high p-code. It is limited in that it
* can only "execute" parts of the AST that represent expressions, as a means of evaluating them. If
* it encounters, e.g., a {@link PcodeOp#MULTIEQUAL} or phi node, it will terminate throw an
* exception.
*
* @param <T> the type of values resulting from evaluation
*/
public interface VarnodeEvaluator<T> {
/**
* Evaluate a varnode
*
* @param program the program containing the varnode
* @param vn the varnode to evaluate
* @return the value of the varnode
*/
T evaluateVarnode(Program program, Varnode vn);
/**
* Evaluate variable storage
*
* <p>
* Each varnode is evaluated as in {@link #evaluateStorage(VariableStorage)} and then
* concatenated. The lower-indexed varnodes in storage are the more significant pieces, similar
* to big endian.
*
* @param storage the storage
* @return the value of the storage
*/
T evaluateStorage(VariableStorage storage);
/**
* Evaluate a high p-code op
*
* @param program the program containing the op
* @param op the p-code op
* @return the value of the op's output
*/
T evaluateOp(Program program, PcodeOp op);
}

View file

@ -16,10 +16,13 @@
package ghidra.pcode.exec;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.*;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.Msg;
@ -74,7 +77,7 @@ public abstract class AbstractBytesPcodeExecutorStatePiece<S extends BytesPcodeE
}
}
protected final AbstractSpaceMap<S> spaceMap = newSpaceMap();
protected final AbstractSpaceMap<S> spaceMap;
/**
* Construct a state for the given language
@ -85,6 +88,11 @@ public abstract class AbstractBytesPcodeExecutorStatePiece<S extends BytesPcodeE
this(language, BytesPcodeArithmetic.forLanguage(language));
}
protected AbstractBytesPcodeExecutorStatePiece(Language language,
AbstractSpaceMap<S> spaceMap) {
this(language, BytesPcodeArithmetic.forLanguage(language), spaceMap);
}
/**
* Construct a state for the given language
*
@ -94,6 +102,13 @@ public abstract class AbstractBytesPcodeExecutorStatePiece<S extends BytesPcodeE
public AbstractBytesPcodeExecutorStatePiece(Language language,
PcodeArithmetic<byte[]> arithmetic) {
super(language, arithmetic, arithmetic);
spaceMap = newSpaceMap();
}
protected AbstractBytesPcodeExecutorStatePiece(Language language,
PcodeArithmetic<byte[]> arithmetic, AbstractSpaceMap<S> spaceMap) {
super(language, arithmetic, arithmetic);
this.spaceMap = spaceMap;
}
/**
@ -138,6 +153,11 @@ public abstract class AbstractBytesPcodeExecutorStatePiece<S extends BytesPcodeE
return read;
}
@Override
protected Map<Register, byte[]> getRegisterValuesFromSpace(S s, List<Register> registers) {
return s.getRegisterValues(registers);
}
@Override
public MemBuffer getConcreteBuffer(Address address, PcodeArithmetic.Purpose purpose) {
return new StateMemBuffer(address, getForSpace(address.getAddressSpace(), false));

View file

@ -16,10 +16,13 @@
package ghidra.pcode.exec;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
/**
* An abstract executor state piece which internally uses {@code long} to address contents
@ -41,13 +44,48 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
* @param <S> the type of object for each address space
*/
public abstract static class AbstractSpaceMap<S> {
protected final Map<AddressSpace, S> spaces = new HashMap<>();
protected final Map<AddressSpace, S> spaces;
public AbstractSpaceMap() {
this.spaces = new HashMap<>();
}
protected AbstractSpaceMap(Map<AddressSpace, S> spaces) {
this.spaces = spaces;
}
public abstract S getForSpace(AddressSpace space, boolean toWrite);
public Collection<S> values() {
return spaces.values();
}
/**
* Deep copy this map, for use in a forked state (or piece)
*
* @return the copy
*/
public abstract AbstractSpaceMap<S> fork();
/**
* Deep copy the given space
*
* @param s the space
* @return the copy
*/
public abstract S fork(S s);
/**
* Produce a deep copy of the given map
*
* @param spaces the map to copy
* @return the copy
*/
public Map<AddressSpace, S> fork(Map<AddressSpace, S> spaces) {
return spaces.entrySet()
.stream()
.collect(Collectors.toMap(Entry::getKey, e -> fork(e.getValue())));
}
}
/**
@ -56,6 +94,14 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
* @param <S> the type of object for each address space
*/
public abstract static class SimpleSpaceMap<S> extends AbstractSpaceMap<S> {
public SimpleSpaceMap() {
super();
}
protected SimpleSpaceMap(Map<AddressSpace, S> spaces) {
super(spaces);
}
/**
* Construct a new space internally associated with the given address space
*
@ -68,7 +114,7 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
protected abstract S newSpace(AddressSpace space);
@Override
public S getForSpace(AddressSpace space, boolean toWrite) {
public synchronized S getForSpace(AddressSpace space, boolean toWrite) {
return spaces.computeIfAbsent(space, s -> newSpace(s));
}
}
@ -80,6 +126,14 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
* @param <S> the type of cache for each address space
*/
public abstract static class CacheingSpaceMap<B, S> extends AbstractSpaceMap<S> {
public CacheingSpaceMap() {
super();
}
protected CacheingSpaceMap(Map<AddressSpace, S> spaces) {
super(spaces);
}
/**
* Get the object backing the cache for the given address space
*
@ -102,7 +156,7 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
protected abstract S newSpace(AddressSpace space, B backing);
@Override
public S getForSpace(AddressSpace space, boolean toWrite) {
public synchronized S getForSpace(AddressSpace space, boolean toWrite) {
return spaces.computeIfAbsent(space,
s -> newSpace(s, s.isUniqueSpace() ? null : getBacking(s)));
}
@ -249,7 +303,8 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
}
@Override
public T getVar(AddressSpace space, long offset, int size, boolean quantize, Reason reason) {
public T getVar(AddressSpace space, long offset, int size, boolean quantize,
Reason reason) {
checkRange(space, offset, size);
if (space.isConstantSpace()) {
return arithmetic.fromConst(offset, size);
@ -264,4 +319,29 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
offset = quantizeOffset(space, offset);
return getFromSpace(s, offset, size, reason);
}
/**
* Can the given space for register values, as in {@link #getRegisterValues()}
*
* @param s the space to scan
* @param registers the registers known to be in the corresponding address space
* @return the map of registers to values
*/
protected abstract Map<Register, T> getRegisterValuesFromSpace(S s, List<Register> registers);
@Override
public Map<Register, T> getRegisterValues() {
Map<AddressSpace, List<Register>> regsBySpace = language.getRegisters()
.stream()
.collect(Collectors.groupingBy(Register::getAddressSpace));
Map<Register, T> result = new HashMap<>();
for (Map.Entry<AddressSpace, List<Register>> ent : regsBySpace.entrySet()) {
S s = getForSpace(ent.getKey(), false);
if (s == null) {
continue;
}
result.putAll(getRegisterValuesFromSpace(s, ent.getValue()));
}
return result;
}
}

View file

@ -1,95 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Endian;
/**
* An auxiliary arithmetic that reports the address of the control value
*
* <p>
* This is intended for use as the right side of a {@link PairedPcodeArithmetic}. Note that constant
* and unique spaces are never returned. Furthermore, any computation performed on a value,
* producing a temporary value, philosophically does not exist at any address in the state. Thus,
* every operation in this arithmetic results in {@code null}. The accompanying state piece
* {@link AddressOfPcodeExecutorStatePiece} does the real "address of" logic.
*/
public enum AddressOfPcodeArithmetic implements PcodeArithmetic<Address> {
// NB: No temp value has a real address
/** The singleton instance */
INSTANCE;
@Override
public Endian getEndian() {
return null;
}
@Override
public Address unaryOp(int opcode, int sizeout, int sizein1, Address in1) {
return null;
}
@Override
public Address binaryOp(int opcode, int sizeout, int sizein1, Address in1, int sizein2,
Address in2) {
return null;
}
@Override
public Address modBeforeStore(int sizeout, int sizeinAddress, Address inAddress,
int sizeinValue, Address inValue) {
return inValue;
}
@Override
public Address modAfterLoad(int sizeout, int sizeinAddress, Address inAddress, int sizeinValue,
Address inValue) {
return inValue;
}
@Override
public Address fromConst(byte[] value) {
return null; // TODO: Do we care about constant space?
}
@Override
public Address fromConst(BigInteger value, int size, boolean isContextreg) {
return null;
}
@Override
public Address fromConst(BigInteger value, int size) {
return null;
}
@Override
public Address fromConst(long value, int size) {
return null;
}
@Override
public byte[] toConcrete(Address value, Purpose purpose) {
throw new ConcretionError("Cannot make 'address of' concrete", purpose);
}
@Override
public long sizeOf(Address value) {
return value.getAddressSpace().getSize() / 8;
}
}

View file

@ -30,4 +30,13 @@ public class BytesPcodeExecutorState extends DefaultPcodeExecutorState<byte[]> {
super(new BytesPcodeExecutorStatePiece(language),
BytesPcodeArithmetic.forLanguage(language));
}
protected BytesPcodeExecutorState(PcodeExecutorStatePiece<byte[], byte[]> piece) {
super(piece);
}
@Override
public BytesPcodeExecutorState fork() {
return new BytesPcodeExecutorState(piece.fork());
}
}

View file

@ -15,6 +15,8 @@
*/
package ghidra.pcode.exec;
import java.util.Map;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
@ -33,13 +35,43 @@ public class BytesPcodeExecutorStatePiece
super(language);
}
protected BytesPcodeExecutorStatePiece(Language language,
AbstractSpaceMap<BytesPcodeExecutorStateSpace<Void>> spaceMap) {
super(language, spaceMap);
}
@Override
public BytesPcodeExecutorStatePiece fork() {
return new BytesPcodeExecutorStatePiece(language, spaceMap.fork());
}
class BytesSpaceMap extends SimpleSpaceMap<BytesPcodeExecutorStateSpace<Void>> {
BytesSpaceMap() {
super();
}
BytesSpaceMap(Map<AddressSpace, BytesPcodeExecutorStateSpace<Void>> spaces) {
super(spaces);
}
@Override
protected BytesPcodeExecutorStateSpace<Void> newSpace(AddressSpace space) {
return new BytesPcodeExecutorStateSpace<>(language, space, null);
}
@Override
public AbstractSpaceMap<BytesPcodeExecutorStateSpace<Void>> fork() {
return new BytesSpaceMap(fork(spaces));
}
@Override
public BytesPcodeExecutorStateSpace<Void> fork(BytesPcodeExecutorStateSpace<Void> s) {
return s.fork();
}
}
@Override
protected AbstractSpaceMap<BytesPcodeExecutorStateSpace<Void>> newSpaceMap() {
return new SimpleSpaceMap<>() {
@Override
protected BytesPcodeExecutorStateSpace<Void> newSpace(AddressSpace space) {
return new BytesPcodeExecutorStateSpace<>(language, space, null);
}
};
return new BytesSpaceMap();
}
}

View file

@ -32,7 +32,7 @@ import ghidra.util.Msg;
* @param <B> if this space is a cache, the type of object backing this space
*/
public class BytesPcodeExecutorStateSpace<B> {
protected final SemisparseByteArray bytes = new SemisparseByteArray();
protected final SemisparseByteArray bytes;
protected final Language language; // for logging diagnostics
protected final AddressSpace space;
protected final B backing;
@ -48,6 +48,19 @@ public class BytesPcodeExecutorStateSpace<B> {
this.language = language;
this.space = space;
this.backing = backing;
this.bytes = new SemisparseByteArray();
}
protected BytesPcodeExecutorStateSpace(Language language, AddressSpace space, B backing,
SemisparseByteArray bytes) {
this.language = language;
this.space = space;
this.backing = backing;
this.bytes = bytes;
}
public BytesPcodeExecutorStateSpace<B> fork() {
return new BytesPcodeExecutorStateSpace<>(language, space, backing, bytes.fork());
}
/**
@ -148,6 +161,21 @@ public class BytesPcodeExecutorStateSpace<B> {
return readBytes(offset, size, reason);
}
public Map<Register, byte[]> getRegisterValues(List<Register> registers) {
Map<Register, byte[]> result = new HashMap<>();
for (Register reg : registers) {
long min = reg.getAddress().getOffset();
long max = min + reg.getNumBytes();
if (!bytes.isInitialized(min, max)) {
continue;
}
byte[] data = new byte[reg.getNumBytes()];
bytes.getData(min, data);
result.put(reg, data);
}
return result;
}
public void clear() {
bytes.clear();
}

View file

@ -15,10 +15,13 @@
*/
package ghidra.pcode.exec;
import java.util.Map;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
/**
@ -51,6 +54,16 @@ public class DefaultPcodeExecutorState<T> implements PcodeExecutorState<T> {
return piece.getLanguage();
}
@Override
public PcodeArithmetic<T> getArithmetic() {
return arithmetic;
}
@Override
public DefaultPcodeExecutorState<T> fork() {
return new DefaultPcodeExecutorState<>(piece.fork(), arithmetic);
}
@Override
public T getVar(AddressSpace space, T offset, int size, boolean quantize, Reason reason) {
return piece.getVar(space, offset, size, quantize, reason);
@ -62,8 +75,8 @@ public class DefaultPcodeExecutorState<T> implements PcodeExecutorState<T> {
}
@Override
public PcodeArithmetic<T> getArithmetic() {
return arithmetic;
public Map<Register, T> getRegisterValues() {
return piece.getRegisterValues();
}
@Override

View file

@ -0,0 +1,123 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.pcode.PcodeOp;
/**
* An auxiliary arithmetic that reports the location the control value
*
* <p>
* This is intended for use as the right side of a {@link PairedPcodeArithmetic}. Note that constant
* and unique spaces are never returned. Furthermore, any computation performed on a value,
* producing a temporary value, philosophically does not exist at any location in the state. Thus,
* most operations in this arithmetic result in {@code null}. The accompanying state piece
* {@link LocationPcodeExecutorStatePiece} generates the actual locations.
*/
public enum LocationPcodeArithmetic implements PcodeArithmetic<ValueLocation> {
BIG_ENDIAN(Endian.BIG), LITTLE_ENDIAN(Endian.LITTLE);
public static LocationPcodeArithmetic forEndian(boolean bigEndian) {
return bigEndian ? BIG_ENDIAN : LITTLE_ENDIAN;
}
private final Endian endian;
private LocationPcodeArithmetic(Endian endian) {
this.endian = endian;
}
@Override
public Endian getEndian() {
return endian;
}
@Override
public ValueLocation unaryOp(int opcode, int sizeout, int sizein1, ValueLocation in1) {
switch (opcode) {
case PcodeOp.COPY:
case PcodeOp.INT_ZEXT:
case PcodeOp.INT_SEXT:
return in1;
default:
return null;
}
}
@Override
public ValueLocation binaryOp(int opcode, int sizeout, int sizein1, ValueLocation in1,
int sizein2, ValueLocation in2) {
switch (opcode) {
case PcodeOp.INT_LEFT:
BigInteger amount = in2 == null ? null : in2.getConst();
if (in2 == null) {
return null;
}
return in1.shiftLeft(amount.intValue());
case PcodeOp.INT_OR:
return in1 == null || in2 == null ? null : in1.intOr(in2);
default:
return null;
}
}
@Override
public ValueLocation modBeforeStore(int sizeout, int sizeinAddress, ValueLocation inAddress,
int sizeinValue, ValueLocation inValue) {
return inValue;
}
@Override
public ValueLocation modAfterLoad(int sizeout, int sizeinAddress, ValueLocation inAddress,
int sizeinValue, ValueLocation inValue) {
return inValue;
}
@Override
public ValueLocation fromConst(byte[] value) {
return ValueLocation.fromConst(Utils.bytesToLong(value, value.length, endian.isBigEndian()),
value.length);
}
@Override
public ValueLocation fromConst(BigInteger value, int size, boolean isContextreg) {
return ValueLocation.fromConst(value.longValueExact(), size);
}
@Override
public ValueLocation fromConst(BigInteger value, int size) {
return ValueLocation.fromConst(value.longValueExact(), size);
}
@Override
public ValueLocation fromConst(long value, int size) {
return ValueLocation.fromConst(value, size);
}
@Override
public byte[] toConcrete(ValueLocation value, Purpose purpose) {
throw new ConcretionError("Cannot make 'location' concrete", purpose);
}
@Override
public long sizeOf(ValueLocation value) {
return value == null ? 0 : value.size();
}
}

View file

@ -22,32 +22,45 @@ import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
/**
* An auxiliary state piece that reports the address of the control value
* An auxiliary state piece that reports the location of the control value
*
* <p>
* This is intended for use as the right side of a {@link PairedPcodeExecutorState} or
* {@link PairedPcodeExecutorStatePiece}. Except for unique spaces, sets are ignored, and gets
* simply echo back the address of the requested read. In unique spaces, the "address of" is treated
* as the value, so that values transiting unique space can correctly have their source addresses
* simply echo back the location of the requested read. In unique spaces, the "location" is treated
* as the value, so that values transiting unique space can correctly have their source locations
* reported.
*/
public class AddressOfPcodeExecutorStatePiece
implements PcodeExecutorStatePiece<byte[], Address> {
public class LocationPcodeExecutorStatePiece
implements PcodeExecutorStatePiece<byte[], ValueLocation> {
private final Language language;
private final LocationPcodeArithmetic arithmetic;
private final BytesPcodeArithmetic addressArithmetic;
private final Map<Long, Address> unique = new HashMap<>();
private final Map<Long, ValueLocation> unique;
/**
* Construct an "address of" state piece
* Construct a "location" state piece
*
* @param isBigEndian true if the control language is big endian
*/
public AddressOfPcodeExecutorStatePiece(Language language) {
public LocationPcodeExecutorStatePiece(Language language) {
this.language = language;
this.addressArithmetic = BytesPcodeArithmetic.forEndian(language.isBigEndian());
boolean isBigEndian = language.isBigEndian();
this.arithmetic = LocationPcodeArithmetic.forEndian(isBigEndian);
this.addressArithmetic = BytesPcodeArithmetic.forEndian(isBigEndian);
this.unique = new HashMap<>();
}
protected LocationPcodeExecutorStatePiece(Language language,
BytesPcodeArithmetic addressArithmetic, Map<Long, ValueLocation> unique) {
this.language = language;
this.arithmetic = LocationPcodeArithmetic.forEndian(language.isBigEndian());
this.addressArithmetic = addressArithmetic;
this.unique = unique;
}
@Override
@ -61,12 +74,19 @@ public class AddressOfPcodeExecutorStatePiece
}
@Override
public PcodeArithmetic<Address> getArithmetic() {
return AddressOfPcodeArithmetic.INSTANCE;
public PcodeArithmetic<ValueLocation> getArithmetic() {
return arithmetic;
}
@Override
public void setVar(AddressSpace space, byte[] offset, int size, boolean quantize, Address val) {
public LocationPcodeExecutorStatePiece fork() {
return new LocationPcodeExecutorStatePiece(language, addressArithmetic,
new HashMap<>(unique));
}
@Override
public void setVar(AddressSpace space, byte[] offset, int size, boolean quantize,
ValueLocation val) {
if (!space.isUniqueSpace()) {
return;
}
@ -76,18 +96,23 @@ public class AddressOfPcodeExecutorStatePiece
}
@Override
public Address getVar(AddressSpace space, byte[] offset, int size, boolean quantize,
public ValueLocation getVar(AddressSpace space, byte[] offset, int size, boolean quantize,
Reason reason) {
long lOffset = addressArithmetic.toLong(offset, Purpose.LOAD);
if (!space.isUniqueSpace()) {
return space.getAddress(lOffset);
return ValueLocation.fromVarnode(space.getAddress(lOffset), size);
}
return unique.get(lOffset);
}
@Override
public Map<Register, ValueLocation> getRegisterValues() {
return Map.of();
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
throw new ConcretionError("Cannot make 'address of' concrete buffers", purpose);
throw new ConcretionError("Cannot make 'location' concrete buffers", purpose);
}
@Override

View file

@ -15,12 +15,15 @@
*/
package ghidra.pcode.exec;
import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
/**
@ -82,6 +85,16 @@ public class PairedPcodeExecutorState<L, R> implements PcodeExecutorState<Pair<L
return arithmetic;
}
@Override
public Map<Register, Pair<L, R>> getRegisterValues() {
return piece.getRegisterValues();
}
@Override
public PairedPcodeExecutorState<L, R> fork() {
return new PairedPcodeExecutorState<>(piece.fork());
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return piece.getConcreteBuffer(address, purpose);

View file

@ -15,12 +15,15 @@
*/
package ghidra.pcode.exec;
import java.util.*;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
/**
@ -88,6 +91,26 @@ public class PairedPcodeExecutorStatePiece<A, L, R>
return arithmetic;
}
@Override
public Map<Register, Pair<L, R>> getRegisterValues() {
Map<Register, L> leftRVs = left.getRegisterValues();
Map<Register, R> rightRVs = right.getRegisterValues();
Set<Register> union = new HashSet<>();
union.addAll(leftRVs.keySet());
union.addAll(rightRVs.keySet());
Map<Register, Pair<L, R>> result = new HashMap<>();
for (Register k : union) {
result.put(k, Pair.of(leftRVs.get(k), rightRVs.get(k)));
}
return result;
}
@Override
public PairedPcodeExecutorStatePiece<A, L, R> fork() {
return new PairedPcodeExecutorStatePiece<>(left.fork(), right.fork(), addressArithmetic,
arithmetic);
}
@Override
public void setVar(AddressSpace space, A offset, int size, boolean quantize, Pair<L, R> val) {
left.setVar(space, offset, size, quantize, val.getLeft());

View file

@ -160,13 +160,70 @@ public interface PcodeArithmetic<T> {
* @param op
* @param in1
* @param in2
* @return
* @return the output value
*/
default T binaryOp(PcodeOp op, T in1, T in2) {
return binaryOp(op.getOpcode(), op.getOutput().getSize(), op.getInput(0).getSize(), in1,
op.getInput(1).getSize(), in2);
}
/**
* Apply the {@link PcodeOp#PTRADD} operator to the given inputs
*
* <p>
* The "pointer add" op takes three operands: base, index, size; and is used as a more compact
* representation of array index address computation. The {@code size} operand must be constant.
* Suppose {@code arr} is an array whose elements are {@code size} bytes each, and the address
* of its first element is {@code base}. The decompiler would likely render the
* {@link PcodeOp#PTRADD} op as {@code &arr[index]}. An equivalent SLEIGH expression is
* {@code base + index*size}.
*
* <p>
* NOTE: This op is always a result of decompiler simplification, not low p-code generation, and
* so are not ordinarily used by a {@link PcodeExecutor}.
*
* @param sizeout the size (in bytes) of the output variable
* @param sizeinBase the size (in bytes) of the variable used for the array's base address
* @param inBase the value used as the array's base address
* @param sizeinIndex the size (in bytes) of the variable used for the index
* @param inIndex the value used as the index
* @param inSize the size of each array element in bytes
* @return the output value
*/
default T ptrAdd(int sizeout, int sizeinBase, T inBase, int sizeinIndex, T inIndex,
int inSize) {
T indexSized = binaryOp(PcodeOp.INT_MULT, sizeout,
sizeinIndex, inIndex, 4, fromConst(inSize, 4));
return binaryOp(PcodeOp.INT_ADD, sizeout,
sizeinBase, inBase, sizeout, indexSized);
}
/**
* Apply the {@link PcodeOp#PTRSUB} operator to the given inputs
*
* <p>
* The "pointer subfield" op takes two operands: base, offset; and is used as a more specific
* representation of structure field address computation. Its behavior is exactly equivalent to
* {@link PcodeOp#INT_ADD}. Suppose {@code st} is a structure pointer with a field {@code f}
* located {@link offset} bytes into the structure, and {@code st} has the value {@code base}.
* The decompiler would likely render the {@link PcodeOp#PTRSUB} op as {@code &st->f}. An
* equivalent SLEIGH expression is {@code base + offset}.
*
* <p>
* NOTE: This op is always a result of decompiler simplification, not low p-code generation, and
* so are not ordinarily used by a {@link PcodeExecutor}.
*
* @param sizeout the size (in bytes) of the output variable
* @param sizeinBase the size (in bytes) of the variable used for the structure's base address
* @param inBase the value used as the structure's base address
* @param sizeinOffset the size (in bytes) of the variable used for the offset
* @param inOffset the value used as the offset
* @return the output value
*/
default T ptrSub(int sizeout, int sizeinBase, T inBase, int sizeinOffset, T inOffset) {
return binaryOp(PcodeOp.INT_ADD, sizeout, sizeinBase, inBase, sizeinOffset, inOffset);
}
/**
* Apply any modifications before a value is stored
*

View file

@ -215,12 +215,12 @@ public class PcodeExecutor<T> {
badOp(op);
return;
}
if (b instanceof UnaryOpBehavior) {
executeUnaryOp(op, (UnaryOpBehavior) b);
if (b instanceof UnaryOpBehavior unOp) {
executeUnaryOp(op, unOp);
return;
}
if (b instanceof BinaryOpBehavior) {
executeBinaryOp(op, (BinaryOpBehavior) b);
if (b instanceof BinaryOpBehavior binOp) {
executeBinaryOp(op, binOp);
return;
}
switch (op.getOpcode()) {
@ -240,7 +240,7 @@ public class PcodeExecutor<T> {
executeIndirectBranch(op, frame);
return;
case PcodeOp.CALL:
executeCall(op, frame);
executeCall(op, frame, library);
return;
case PcodeOp.CALLIND:
executeIndirectCall(op, frame);
@ -349,12 +349,12 @@ public class PcodeExecutor<T> {
Varnode inOffset = op.getInput(1);
T offset = state.getVar(inOffset, reason);
checkLoad(space, offset);
Varnode outvar = op.getOutput();
Varnode outVar = op.getOutput();
T out = state.getVar(space, offset, outvar.getSize(), true, reason);
T mod = arithmetic.modAfterLoad(outvar.getSize(), inOffset.getSize(), offset,
outvar.getSize(), out);
state.setVar(outvar, mod);
T out = state.getVar(space, offset, outVar.getSize(), true, reason);
T mod = arithmetic.modAfterLoad(outVar.getSize(), inOffset.getSize(), offset,
outVar.getSize(), out);
state.setVar(outVar, mod);
}
/**
@ -506,7 +506,7 @@ public class PcodeExecutor<T> {
* @param op the op
* @param frame the frame
*/
public void executeCall(PcodeOp op, PcodeFrame frame) {
public void executeCall(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) {
Address target = op.getInput(0).getAddress();
branchToOffset(arithmetic.fromConst(target.getOffset(), pointerSize), frame);
branchToAddress(target);

View file

@ -37,6 +37,9 @@ public interface PcodeExecutorState<T> extends PcodeExecutorStatePiece<T, T> {
return getArithmetic();
}
@Override
PcodeExecutorState<T> fork();
/**
* Use this state as the control, paired with the given auxiliary state.
*

View file

@ -15,6 +15,7 @@
*/
package ghidra.pcode.exec;
import java.util.Map;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
@ -87,6 +88,13 @@ public interface PcodeExecutorStatePiece<A, T> {
*/
PcodeArithmetic<T> getArithmetic();
/**
* Create a deep copy of this state
*
* @return the copy
*/
PcodeExecutorStatePiece<A, T> fork();
/**
* Set the value of a register variable
*
@ -219,6 +227,16 @@ public interface PcodeExecutorStatePiece<A, T> {
return getVar(address.getAddressSpace(), address.getOffset(), size, quantize, reason);
}
/**
* Get all register values known to this state
*
* <p>
* When the state acts as a cache, it should only return those cached.
*
* @return a map of registers and their values
*/
Map<Register, T> getRegisterValues();
/**
* Bind a buffer of concrete bytes at the given start address
*

View file

@ -22,8 +22,9 @@ import ghidra.app.plugin.processors.sleigh.template.OpTpl;
import ghidra.app.util.pcode.AbstractAppender;
import ghidra.app.util.pcode.AbstractPcodeFormatter;
import ghidra.pcodeCPort.slghsymbol.UserOpSymbol;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.PcodeOp;
/**
@ -88,22 +89,49 @@ public class PcodeProgram {
}
}
/**
* Generate a p-code program from the given instruction, without overrides
*
* @param instruction the instruction
* @return the p-code program
*/
public static PcodeProgram fromInstruction(Instruction instruction) {
return fromInstruction(instruction, false);
}
/**
* Generate a p-code program from the given instruction
*
* @param instruction the instruction
* @return the p-code program.
* @param includeOverrides as in {@link Instruction#getPcode(boolean)}
* @return the p-code program
*/
public static PcodeProgram fromInstruction(Instruction instruction) {
public static PcodeProgram fromInstruction(Instruction instruction, boolean includeOverrides) {
Language language = instruction.getPrototype().getLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Instruction must be parsed using Sleigh");
}
PcodeOp[] pcode = instruction.getPcode(false);
PcodeOp[] pcode = instruction.getPcode(includeOverrides);
return new PcodeProgram((SleighLanguage) language, List.of(pcode),
Map.of());
}
/**
* Generate a p-code program from a given program's inject library
*
* @param program the program
* @param name the name of the snippet
* @param type the type of the snippet
* @return the p-code program
*/
public static PcodeProgram fromInject(Program program, String name, int type) {
PcodeInjectLibrary library = program.getCompilerSpec().getPcodeInjectLibrary();
InjectContext ctx = library.buildInjectContext();
InjectPayload payload = library.getPayload(type, name);
PcodeOp[] pcode = payload.getPcode(program, ctx);
return new PcodeProgram((SleighLanguage) program.getLanguage(), List.of(pcode), Map.of());
}
protected final SleighLanguage language;
protected final List<PcodeOp> code;
protected final Map<Integer, String> useropNames = new HashMap<>();

View file

@ -0,0 +1,258 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
/**
* The location of a value
*
* <p>
* This is an analog to {@link VariableStorage}, except that this records the actual storage
* location of the evaluated variable or expression. This does not incorporate storage of
* intermediate dereferenced values. For example, suppose {@code R0 = 0xdeadbeef}, and we want to
* evaluate {@code *:4 R0}. The storage would be {@code ram:deadbeef:4}, not
* {@code R0,ram:deadbeef:4}.
*/
public class ValueLocation {
private static final AddressSpace CONST =
new GenericAddressSpace("const", 64, AddressSpace.TYPE_CONSTANT, 0);
public static String vnToString(Varnode vn, Language language) {
Register register =
language == null ? null : language.getRegister(vn.getAddress(), vn.getSize());
if (register != null) {
return String.format("%s:%d", register.getName(), vn.getSize());
}
return String.format("%s:%d", vn.getAddress(), vn.getSize());
}
private static boolean isZero(Varnode vn) {
return vn.isConstant() && vn.getOffset() == 0;
}
private static List<Varnode> removeLeading0s(List<Varnode> nodes) {
for (int i = 0; i < nodes.size(); i++) {
if (!isZero(nodes.get(i))) {
return nodes.subList(i, nodes.size());
}
}
return List.of();
}
/**
* Generate the "location" of a constant
*
* @param value the value
* @param size the size of the constant in bytes
* @return the "location"
*/
public static ValueLocation fromConst(long value, int size) {
return new ValueLocation(new Varnode(CONST.getAddress(value), size));
}
/**
* Generate a location from a varnode
*
* @param address the dynamic address of the variable
* @param size the size of the variable in bytes
* @return the location
*/
public static ValueLocation fromVarnode(Address address, int size) {
return new ValueLocation(new Varnode(address, size));
}
private final List<Varnode> nodes;
/**
* Construct a location from a list of varnodes
*
* <p>
* Any leading varnodes which are constant 0s are removed.
*
* @param nodes the varnodes
*/
public ValueLocation(Varnode... nodes) {
this.nodes = removeLeading0s(List.of(nodes));
}
/**
* Construct a location from a list of varnodes
*
* <p>
* Any leading varnodes which are constant 0s are removed.
*
* @param nodes the varnodes
*/
public ValueLocation(List<Varnode> nodes) {
this.nodes = removeLeading0s(List.copyOf(nodes));
}
/**
* Get the number of varnodes for this location
*
* @return the count
*/
public int nodeCount() {
return nodes.size();
}
/**
* Get the address of the first varnode
*
* @return the address, or null if this location has no varnodes
*/
public Address getAddress() {
return nodes.isEmpty() ? null : nodes.get(0).getAddress();
}
/**
* Render this location as a string, substituting registers where applicable
*
* @param language the optional language for register substitution
* @return the string
*/
public String toString(Language language) {
return nodes.stream().map(vn -> vnToString(vn, language)).collect(Collectors.joining(","));
}
@Override
public String toString() {
return toString(null);
}
/**
* Apply a {@link PcodeOp#INT_OR} operator
*
* <p>
* There is a very restrictive set of constraints for which this yields a non-null location. If
* either this or that is empty, the other is returned. Otherwise, the varnodes are arranged in
* pairs by taking one from each storage starting at the right, or least-significant varnode.
* Each pair must match in length, and one of the pair must be a constant zero. The non-zero
* varnode is taken. The unpaired varnodes to the left, if any, are all taken. If any pair does
* not match in length, or if neither is zero, the resulting location is null. This logic is to
* ensure location information is accrued during concatenation.
*
* @param that the other location
* @return the location
*/
public ValueLocation intOr(ValueLocation that) {
if (this.isEmpty()) {
return that;
}
if (that.isEmpty()) {
return this;
}
ListIterator<Varnode> itA = this.nodes.listIterator(this.nodeCount());
ListIterator<Varnode> itB = that.nodes.listIterator(that.nodeCount());
Varnode[] result = new Varnode[Math.max(this.nodeCount(), that.nodeCount())];
int i = result.length;
while (itA.hasNext() && itB.hasPrevious()) {
Varnode vnA = itA.previous();
Varnode vnB = itB.previous();
if (vnA.getSize() != vnB.getSize()) {
return null;
}
if (isZero(vnA)) {
result[--i] = vnB;
}
else if (isZero(vnB)) {
result[--i] = vnA;
}
}
while (itA.hasPrevious()) {
result[--i] = itA.previous();
}
while (itB.hasPrevious()) {
result[--i] = itB.previous();
}
return new ValueLocation(result);
}
/**
* If the location represents a constant, get its value
*
* @return the constant value
*/
public BigInteger getConst() {
BigInteger result = BigInteger.ZERO;
for (Varnode vn : nodes) {
if (!vn.isConstant()) {
return null;
}
result = result.shiftLeft(vn.getSize() * 8);
result = result.or(vn.getAddress().getOffsetAsBigInteger());
}
return result;
}
/**
* Apply a {@link PcodeOp#INT_LEFT} operator
*
* <p>
* This requires the shift amount to represent an integral number of bytes. Otherwise, the
* result is null. This simply inserts a constant zero to the right, having the number of bytes
* indicated by the shift amount. This logic is to ensure location information is accrued during
* concatenation.
*
* @param amount the number of bits to shift
* @return the location.
*/
public ValueLocation shiftLeft(int amount) {
if (amount % 8 != 0) {
return null;
}
List<Varnode> result = new ArrayList<>(nodes);
result.add(new Varnode(CONST.getAddress(0), amount / 8));
return new ValueLocation(result);
}
/**
* Get the total size of this location in bytes
*
* @return the size in bytes
*/
public int size() {
int result = 0;
for (Varnode vn : nodes) {
result += vn.getSize();
}
return result;
}
/**
* Check if this location includes any varnodes
*
* <p>
* Note that a location cannot consist entirely of constant zeros and be non-empty. The
* constructor will have removed them all.
*
* @return true if empty
*/
public boolean isEmpty() {
return nodes.isEmpty();
}
}

View file

@ -15,12 +15,16 @@
*/
package ghidra.pcode.emu.taint;
import java.util.List;
import java.util.Map;
import ghidra.pcode.emu.taint.plain.TaintSpace;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
import ghidra.taint.model.TaintVec;
@ -116,6 +120,11 @@ public abstract class AbstractTaintPcodeExecutorStatePiece<S extends TaintSpace>
return space.get(offset, size);
}
@Override
protected Map<Register, TaintVec> getRegisterValuesFromSpace(S space, List<Register> registers) {
return space.getRegisterValues(registers);
}
@Override
public void clear() {
for (S space : spaceMap.values()) {

View file

@ -71,6 +71,21 @@ public class TaintPcodeExecutorStatePiece extends AbstractTaintPcodeExecutorStat
protected TaintSpace newSpace(AddressSpace space) {
return new TaintSpace();
}
@Override
public AbstractSpaceMap<TaintSpace> fork() {
throw new UnsupportedOperationException();
}
@Override
public TaintSpace fork(TaintSpace s) {
throw new UnsupportedOperationException();
}
};
}
@Override
public TaintPcodeExecutorStatePiece fork() {
throw new UnsupportedOperationException();
}
}

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