mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
Merge remote-tracking branch 'origin/GP-2834_Dan_hoverVarVals--SQUASHED'
(#4732)
This commit is contained in:
commit
bf5dfa6170
105 changed files with 12279 additions and 478 deletions
|
@ -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"
|
||||
|
|
|
@ -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 → Analysis → 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 |
|
@ -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>
|
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
|
@ -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;
|
||||
|
@ -221,7 +222,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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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?
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/* ###
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* An exception to indicate failed or incomplete stack uwinding
|
||||
*/
|
||||
public class UnwindException extends RuntimeException {
|
||||
public UnwindException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UnwindException(String message, UnwindException cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue