mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 09:49:23 +02:00
GP-1230: Add Taint Analysis prototype and emulator framework support
This commit is contained in:
parent
4bfd8d1112
commit
51a1933ab3
205 changed files with 11214 additions and 3714 deletions
|
@ -33,7 +33,7 @@ dependencies {
|
|||
helpPath project(path: ':Base', configuration: 'helpPath')
|
||||
helpPath project(path: ':ProgramDiff', configuration: 'helpPath')
|
||||
|
||||
testImplementation project(path: ':Base', configuration: 'testArtifacts')
|
||||
testImplementation project(path: ':Base', configuration: 'testArtifacts')
|
||||
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
||||
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
|
||||
testImplementation project(path: ':Framework-TraceModeling', configuration: 'testArtifacts')
|
||||
|
|
|
@ -2,7 +2,9 @@ AutoReadMemorySpec
|
|||
DebuggerBot
|
||||
DebuggerMappingOpinion
|
||||
DebuggerModelFactory
|
||||
DebuggerPcodeEmulatorFactory
|
||||
DebuggerPlatformOpinion
|
||||
DebuggerProgramLaunchOpinion
|
||||
DebuggerRegisterColumnFactory
|
||||
DisassemblyInject
|
||||
LocationTrackingSpec
|
||||
|
|
|
@ -28,7 +28,7 @@ import java.util.List;
|
|||
|
||||
import ghidra.app.plugin.assembler.Assembler;
|
||||
import ghidra.app.plugin.assembler.Assemblers;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulator;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
|
@ -136,7 +136,7 @@ public class DebuggerEmuExampleScript extends GhidraScript {
|
|||
* library. This emulator will still know how to integrate with the UI, reading through to
|
||||
* open programs and writing state back into the trace.
|
||||
*/
|
||||
DebuggerTracePcodeEmulator emulator = new DebuggerTracePcodeEmulator(tool, trace, 0, null) {
|
||||
BytesDebuggerPcodeEmulator emulator = new BytesDebuggerPcodeEmulator(tool, trace, 0, null) {
|
||||
@Override
|
||||
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
|
||||
return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this);
|
||||
|
@ -169,7 +169,7 @@ public class DebuggerEmuExampleScript extends GhidraScript {
|
|||
thread.stepInstruction();
|
||||
snapshot =
|
||||
time.createSnapshot("Stepped to " + thread.getCounter());
|
||||
emulator.writeDown(trace, snapshot.getKey(), 0, false);
|
||||
emulator.writeDown(trace, snapshot.getKey(), 0);
|
||||
}
|
||||
printerr("We should not have completed 10 steps!");
|
||||
}
|
||||
|
|
|
@ -29,16 +29,17 @@ import ghidra.program.model.pcode.Varnode;
|
|||
* A userop library for the emulator
|
||||
*
|
||||
* <p>
|
||||
* If you do not have need of a custom userop library, use {@link PcodeUseropLibrary#NIL}. These
|
||||
* libraries allow you to implement userop, including those declared by the language. Without these,
|
||||
* the emulator must interrupt whenever a userop ({@code CALLOTHER}) is encountered. You can also
|
||||
* define new userops, which can be invoked from Sleigh code injected into the emulator.
|
||||
* If you do not need a custom userop library, use {@link PcodeUseropLibrary#NIL}. These libraries
|
||||
* allow you to implement userops, including those declared by the language. Without these, the
|
||||
* emulator must interrupt whenever a userop ({@code CALLOTHER}) is encountered. You can also define
|
||||
* new userops, which can be invoked from Sleigh code injected into the emulator.
|
||||
*
|
||||
* <p>
|
||||
* These libraries can have both Java-callback and p-code implementations of userops. If only using
|
||||
* p-code implementations, the library can be parameterized with type {@code <T>} and just pass that
|
||||
* over to {@link AnnotatedPcodeUseropLibrary}. Because this will demo a Java callback that assumes
|
||||
* concrete bytes, we will fix the library's type to {@code byte[]}.
|
||||
* concrete bytes, we will fix the library's type to {@code byte[]}. With careful use of the
|
||||
* {@link PcodeArithmetic}, you can keep the type an abstract {@code <T>} with Java callbacks.
|
||||
*
|
||||
* <p>
|
||||
* Methods in this class (not including those in its nested classes) are implemented as Java
|
||||
|
@ -74,8 +75,7 @@ public class DemoPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary<byte[]>
|
|||
* @return the length of the string in bytes
|
||||
*/
|
||||
@PcodeUserop
|
||||
public byte[] print_utf8(@OpState PcodeExecutorStatePiece<byte[], byte[]> state,
|
||||
byte[] start) {
|
||||
public byte[] print_utf8(@OpState PcodeExecutorState<byte[]> state, byte[] start) {
|
||||
long offset = Utils.bytesToLong(start, start.length, language.isBigEndian());
|
||||
long end = offset;
|
||||
while (state.getVar(space, end, 1, true)[0] != 0) {
|
||||
|
|
|
@ -23,6 +23,7 @@ import ghidra.pcode.emu.linux.EmuLinuxX86SyscallUseropLibrary;
|
|||
import ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary;
|
||||
import ghidra.pcode.emu.sys.EmuSyscallLibrary;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.pcode.struct.StructuredSleigh;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
|
@ -41,7 +42,8 @@ import ghidra.program.model.listing.Program;
|
|||
* call libraries typically implement that interface by annotating p-code userops with
|
||||
* {@link EmuSyscall}. This allows system calls to be implemented via Java callback or Structured
|
||||
* Sleigh. Conventionally, the Java method names of system calls should be
|
||||
* <em>platform</em>_<em>name</em>. This is to prevent name-space pollution of userops.
|
||||
* <em>platform</em>_<em>name</em>. This is to prevent name conflicts among userops when several
|
||||
* libraries are composed.
|
||||
*
|
||||
* <p>
|
||||
* Stock implementations for a limited set of Linux system calls are provided for x86 and amd64 in
|
||||
|
@ -53,7 +55,7 @@ import ghidra.program.model.listing.Program;
|
|||
*
|
||||
* <p>
|
||||
* For demonstration, this will implement one from scratch for no particular operating system, but
|
||||
* it will borrow many conventions from linux-amd64.
|
||||
* it will borrow many conventions from Linux-amd64.
|
||||
*/
|
||||
public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]> {
|
||||
private final static Charset UTF8 = Charset.forName("utf8");
|
||||
|
@ -80,11 +82,11 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
|
|||
|
||||
/**
|
||||
* Because the system call numbering is derived from the "syscall" overlay on OTHER space, a
|
||||
* program is required. The system call analyzer must be applied to it. The program and its
|
||||
* compiler spec are also used to derive (what it can of) the system call ABI. Notably, it
|
||||
* applies the calling convention of the functions placed in syscall overlay. Those parts which
|
||||
* cannot (yet) be derived from the program are instead implemented as abstract methods of this
|
||||
* class, e.g., {@link #readSyscallNumber(PcodeExecutorStatePiece)} and
|
||||
* program is required. Use the system call analyzer on your program to populate this space. The
|
||||
* program and its compiler spec are also used to derive (what it can of) the system call ABI.
|
||||
* Notably, it applies the calling convention of the functions placed in syscall overlay. Those
|
||||
* parts which cannot (yet) be derived from the program are instead implemented as abstract
|
||||
* methods of this class, e.g., {@link #readSyscallNumber(PcodeExecutorStatePiece)} and
|
||||
* {@link #handleError(PcodeExecutor, PcodeExecutionException)}.
|
||||
*
|
||||
* @param machine the emulator
|
||||
|
@ -151,7 +153,7 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
|
|||
* <p>
|
||||
* The {@link EmuSyscall} annotation allows us to specify the system call name, because the
|
||||
* userop name should be prefixed with the platform name, to avoid naming collisions among
|
||||
* userops.
|
||||
* composed libraries.
|
||||
*
|
||||
* <p>
|
||||
* For demonstration, we will export this as a system call, though that is not required for
|
||||
|
@ -173,8 +175,8 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
|
|||
* copy of the arithmetic as a field at library construction time.
|
||||
*/
|
||||
PcodeArithmetic<byte[]> arithmetic = machine.getArithmetic();
|
||||
long strLong = arithmetic.toConcrete(str).longValue();
|
||||
long endLong = arithmetic.toConcrete(end).longValue();
|
||||
long strLong = arithmetic.toLong(str, Purpose.LOAD);
|
||||
long endLong = arithmetic.toLong(end, Purpose.OTHER);
|
||||
|
||||
byte[] stringBytes =
|
||||
machine.getSharedState().getVar(space, strLong, (int) (endLong - strLong), true);
|
||||
|
@ -185,12 +187,17 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
|
|||
// Second, a Structured Sleigh example
|
||||
|
||||
/**
|
||||
* The nested class for syscall implemented using StructuredSleigh. Note that no matter the
|
||||
* implementation type, the Java method is annotated with {@link EmuSyscall}. We declare it
|
||||
* public so that the annotation processor can access the methods. Alternatively, we could
|
||||
* override {@link #getMethodLookup()}.
|
||||
* The nested class for syscalls implemented using Structured Sleigh. Note that no matter the
|
||||
* implementation type, the Java method is annotated with {@link EmuSyscall}. We declare the
|
||||
* class public so that the annotation processor can access the methods. Alternatively, we could
|
||||
* override {@link #getMethodLookup()} to provide the processor private access.
|
||||
*/
|
||||
public class DemoStructuredPart extends StructuredPart {
|
||||
/**
|
||||
* This creates a handle to the "demo_write" p-code userop for use in Structured Sleigh.
|
||||
* Otherwise, there's no way to refer to the userop. Think of it like a "forward" or
|
||||
* "external" declaration.
|
||||
*/
|
||||
UseropDecl write = userop(type("void"), "demo_write", types("char *", "char *"));
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,8 +21,6 @@
|
|||
//@menupath
|
||||
//@toolbar
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -88,7 +86,7 @@ public class StandAloneEmuExampleScript extends GhidraScript {
|
|||
*/
|
||||
Address entry = dyn.getAddress(0x00400000);
|
||||
Assembler asm = Assemblers.getAssembler(language);
|
||||
CodeBuffer buffer = new CodeBuffer(asm, entry);
|
||||
AssemblyBuffer buffer = new AssemblyBuffer(asm, entry);
|
||||
buffer.assemble("MOV RCX, 0xdeadbeef");
|
||||
Address injectHere = buffer.getNext();
|
||||
buffer.assemble("MOV RAX, 1");
|
||||
|
@ -150,30 +148,4 @@ public class StandAloneEmuExampleScript extends GhidraScript {
|
|||
.evaluate(thread.getExecutor()),
|
||||
8, language.isBigEndian()));
|
||||
}
|
||||
|
||||
public static class CodeBuffer {
|
||||
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
private final Assembler asm;
|
||||
private final Address entry;
|
||||
|
||||
public CodeBuffer(Assembler asm, Address entry) {
|
||||
this.asm = asm;
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public Address getNext() {
|
||||
return entry.add(baos.size());
|
||||
}
|
||||
|
||||
public byte[] assemble(String line)
|
||||
throws AssemblySyntaxException, AssemblySemanticException, IOException {
|
||||
byte[] bytes = asm.assembleLine(getNext(), line);
|
||||
baos.write(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return baos.toByteArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,5 +40,11 @@
|
|||
current address and whose registers are initialized to the register context at the current
|
||||
address. Optionally, other registers can be initialized via the UI or a script. The new thread
|
||||
is activated so that stepping actions will affect it by default.</P>
|
||||
|
||||
<H3><A name="configure_emulator"></A> Configure Emulator</H3>
|
||||
|
||||
<P>This action is always available. It lists emulators available for configuration. Selecting
|
||||
one will set it as the current emulator. The next time emulation is activated, it will use the
|
||||
selected emulator.</P>
|
||||
</BODY>
|
||||
</HTML>
|
||||
|
|
|
@ -28,18 +28,19 @@ import ghidra.util.table.GhidraTableFilterPanel;
|
|||
|
||||
public abstract class AbstractDebuggerMapProposalDialog<R> extends DialogComponentProvider {
|
||||
|
||||
protected final EnumeratedColumnTableModel<R> tableModel = createTableModel();
|
||||
protected final EnumeratedColumnTableModel<R> tableModel;
|
||||
protected GTable table;
|
||||
protected GhidraTableFilterPanel<R> filterPanel;
|
||||
|
||||
private Collection<R> adjusted;
|
||||
|
||||
protected AbstractDebuggerMapProposalDialog(String title) {
|
||||
protected AbstractDebuggerMapProposalDialog(PluginTool tool, String title) {
|
||||
super(title, true, true, true, false);
|
||||
tableModel = createTableModel(tool);
|
||||
populateComponents();
|
||||
}
|
||||
|
||||
protected abstract EnumeratedColumnTableModel<R> createTableModel();
|
||||
protected abstract EnumeratedColumnTableModel<R> createTableModel(PluginTool tool);
|
||||
|
||||
protected void populateComponents() {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
|
|
|
@ -143,16 +143,17 @@ public class DebuggerBlockChooserDialog extends DialogComponentProvider {
|
|||
}
|
||||
}
|
||||
|
||||
final EnumeratedColumnTableModel<MemoryBlockRow> tableModel =
|
||||
new DefaultEnumeratedColumnTableModel<>("Blocks", MemoryBlockTableColumns.class);
|
||||
final EnumeratedColumnTableModel<MemoryBlockRow> tableModel;
|
||||
|
||||
GTable table;
|
||||
GhidraTableFilterPanel<MemoryBlockRow> filterPanel;
|
||||
|
||||
private Entry<Program, MemoryBlock> chosen;
|
||||
|
||||
public DebuggerBlockChooserDialog() {
|
||||
public DebuggerBlockChooserDialog(PluginTool tool) {
|
||||
super("Memory Blocks", true, true, true, false);
|
||||
tableModel =
|
||||
new DefaultEnumeratedColumnTableModel<>(tool, "Blocks", MemoryBlockTableColumns.class);
|
||||
populateComponents();
|
||||
}
|
||||
|
||||
|
|
|
@ -572,6 +572,21 @@ public interface DebuggerResources {
|
|||
}
|
||||
}
|
||||
|
||||
interface ConfigureEmulatorAction {
|
||||
String NAME = "Configure Emulator";
|
||||
String DESCRIPTION = "Choose and configure the current emulator";
|
||||
String GROUP = GROUP_MAINTENANCE;
|
||||
String HELP_ANCHOR = "configure_emulator";
|
||||
|
||||
static ToggleActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ToggleActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractQuickLaunchAction extends DockingAction {
|
||||
public static final String NAME = "Quick Launch";
|
||||
public static final Icon ICON = ICON_DEBUGGER; // TODO: A different icon?
|
||||
|
|
|
@ -93,7 +93,7 @@ public abstract class DebuggerGoToTrait {
|
|||
}
|
||||
|
||||
public CompletableFuture<Boolean> goToSleigh(AddressSpace space, PcodeExpression expression) {
|
||||
AsyncPcodeExecutor<byte[]> executor = TracePcodeUtils.executorForCoordinates(current);
|
||||
AsyncPcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(current);
|
||||
CompletableFuture<byte[]> result = expression.evaluate(executor);
|
||||
return result.thenApply(offset -> {
|
||||
Address address = space.getAddress(
|
||||
|
|
|
@ -134,7 +134,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
|
|||
LogicalBreakpointTableColumns, LogicalBreakpoint, LogicalBreakpointRow, LogicalBreakpoint> {
|
||||
|
||||
public LogicalBreakpointTableModel(DebuggerBreakpointsProvider provider) {
|
||||
super("Breakpoints", LogicalBreakpointTableColumns.class, lb -> lb,
|
||||
super(provider.getTool(), "Breakpoints", LogicalBreakpointTableColumns.class, lb -> lb,
|
||||
lb -> new LogicalBreakpointRow(provider, lb));
|
||||
}
|
||||
|
||||
|
@ -212,8 +212,8 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
|
|||
BreakpointLocationTableColumns, ObjectKey, BreakpointLocationRow, TraceBreakpoint> {
|
||||
|
||||
public BreakpointLocationTableModel(DebuggerBreakpointsProvider provider) {
|
||||
super("Locations", BreakpointLocationTableColumns.class, TraceBreakpoint::getObjectKey,
|
||||
loc -> new BreakpointLocationRow(provider, loc));
|
||||
super(provider.getTool(), "Locations", BreakpointLocationTableColumns.class,
|
||||
TraceBreakpoint::getObjectKey, loc -> new BreakpointLocationRow(provider, loc));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -45,8 +45,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.SelectNoneAction;
|
|||
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
|
||||
import ghidra.framework.options.AutoOptions;
|
||||
import ghidra.framework.options.annotation.*;
|
||||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
|
@ -205,8 +204,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
|||
protected static class LogTableModel extends DebouncedRowWrappedEnumeratedColumnTableModel< //
|
||||
LogTableColumns, ActionContext, LogRow, LogRow> {
|
||||
|
||||
public LogTableModel() {
|
||||
super("Log", LogTableColumns.class, r -> r.getActionContext(), r -> r);
|
||||
public LogTableModel(PluginTool tool) {
|
||||
super(tool, "Log", LogTableColumns.class, r -> r.getActionContext(), r -> r);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -286,7 +285,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
|||
protected final Map<String, Map<String, DockingActionIf>> actionsByOwnerThenName =
|
||||
new LinkedHashMap<>();
|
||||
|
||||
protected final LogTableModel logTableModel = new LogTableModel();
|
||||
protected final LogTableModel logTableModel;
|
||||
protected GhidraTable logTable;
|
||||
private GhidraTableFilterPanel<LogRow> logFilterPanel;
|
||||
|
||||
|
@ -301,6 +300,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
|||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_CONSOLE, plugin.getName());
|
||||
this.plugin = plugin;
|
||||
|
||||
logTableModel = new LogTableModel(tool);
|
||||
|
||||
tool.addPopupActionProvider(this);
|
||||
|
||||
setIcon(DebuggerResources.ICON_PROVIDER_CONSOLE);
|
||||
|
|
|
@ -55,7 +55,7 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin {
|
|||
return ctx.hasSelection() ? ctx.getSelection() : null;
|
||||
}
|
||||
|
||||
protected DebuggerCopyIntoProgramDialog copyDialog = new DebuggerCopyIntoProgramDialog();
|
||||
protected DebuggerCopyIntoProgramDialog copyDialog;
|
||||
|
||||
protected DockingAction actionExportView;
|
||||
protected DockingAction actionCopyIntoCurrentProgram;
|
||||
|
@ -70,6 +70,7 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin {
|
|||
|
||||
public DebuggerCopyActionsPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
copyDialog = new DebuggerCopyIntoProgramDialog(tool);
|
||||
|
||||
createActions();
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
|||
import ghidra.app.plugin.core.debug.gui.copying.DebuggerCopyPlan.Copier;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
@ -200,8 +201,8 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider {
|
|||
|
||||
protected static class RangeTableModel
|
||||
extends DefaultEnumeratedColumnTableModel<RangeTableColumns, RangeEntry> {
|
||||
public RangeTableModel() {
|
||||
super("Ranges", RangeTableColumns.class);
|
||||
public RangeTableModel(PluginTool tool) {
|
||||
super(tool, "Ranges", RangeTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -302,15 +303,16 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider {
|
|||
protected JCheckBox cbUseOverlays;
|
||||
protected DebuggerCopyPlan plan = new DebuggerCopyPlan();
|
||||
|
||||
protected final RangeTableModel tableModel = new RangeTableModel();
|
||||
protected final RangeTableModel tableModel;
|
||||
protected GTable table;
|
||||
protected GhidraTableFilterPanel<RangeEntry> filterPanel;
|
||||
|
||||
protected JButton resetButton;
|
||||
|
||||
public DebuggerCopyIntoProgramDialog() {
|
||||
public DebuggerCopyIntoProgramDialog(PluginTool tool) {
|
||||
super("Copy Into Program", true, true, true, true);
|
||||
|
||||
tableModel = new RangeTableModel(tool);
|
||||
populateComponents();
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractDebuggerMapProposalDialog;
|
|||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapRegionsAction;
|
||||
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
|
@ -101,8 +102,8 @@ public class DebuggerRegionMapProposalDialog
|
|||
protected static class RegionMapPropsalTableModel extends
|
||||
DefaultEnumeratedColumnTableModel<RegionMapTableColumns, RegionMapEntry> {
|
||||
|
||||
public RegionMapPropsalTableModel() {
|
||||
super("Region Map", RegionMapTableColumns.class);
|
||||
public RegionMapPropsalTableModel(PluginTool tool) {
|
||||
super(tool, "Region Map", RegionMapTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -114,13 +115,13 @@ public class DebuggerRegionMapProposalDialog
|
|||
private final DebuggerRegionsProvider provider;
|
||||
|
||||
public DebuggerRegionMapProposalDialog(DebuggerRegionsProvider provider) {
|
||||
super(MapRegionsAction.NAME);
|
||||
super(provider.getTool(), MapRegionsAction.NAME);
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RegionMapPropsalTableModel createTableModel() {
|
||||
return new RegionMapPropsalTableModel();
|
||||
protected RegionMapPropsalTableModel createTableModel(PluginTool tool) {
|
||||
return new RegionMapPropsalTableModel(tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -44,8 +44,7 @@ import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTab
|
|||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
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.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
@ -124,8 +123,8 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
|||
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
|
||||
RegionTableColumns, ObjectKey, RegionRow, TraceMemoryRegion> {
|
||||
|
||||
public RegionTableModel() {
|
||||
super("Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey,
|
||||
public RegionTableModel(PluginTool tool) {
|
||||
super(tool, "Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey,
|
||||
RegionRow::new);
|
||||
}
|
||||
}
|
||||
|
@ -233,7 +232,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
|||
|
||||
private final RegionsListener regionsListener = new RegionsListener();
|
||||
|
||||
protected final RegionTableModel regionTableModel = new RegionTableModel();
|
||||
protected final RegionTableModel regionTableModel;
|
||||
protected GhidraTable regionTable;
|
||||
private GhidraTableFilterPanel<RegionRow> regionFilterPanel;
|
||||
|
||||
|
@ -260,6 +259,8 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
|||
DebuggerRegionActionContext.class);
|
||||
this.plugin = plugin;
|
||||
|
||||
regionTableModel = new RegionTableModel(tool);
|
||||
|
||||
setIcon(DebuggerResources.ICON_PROVIDER_REGIONS);
|
||||
setHelpLocation(DebuggerResources.HELP_PROVIDER_REGIONS);
|
||||
setWindowMenuGroup(DebuggerPluginPackage.NAME);
|
||||
|
@ -268,7 +269,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
|||
|
||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
|
||||
blockChooserDialog = new DebuggerBlockChooserDialog();
|
||||
blockChooserDialog = new DebuggerBlockChooserDialog(tool);
|
||||
regionProposalDialog = new DebuggerRegionMapProposalDialog(this);
|
||||
|
||||
setDefaultWindowPosition(WindowPosition.BOTTOM);
|
||||
|
|
|
@ -1,243 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.modules;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.table.ColumnSortState.SortDirection;
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
import ghidra.app.services.DebuggerStaticMappingService;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.modules.TraceSection;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
|
||||
public class DebuggerBlockChooserDialog extends DialogComponentProvider {
|
||||
static class MemoryBlockRow {
|
||||
private final Program program;
|
||||
private final MemoryBlock block;
|
||||
private double score;
|
||||
|
||||
public MemoryBlockRow(Program program, MemoryBlock block) {
|
||||
this.program = program;
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
public Program getProgram() {
|
||||
return program;
|
||||
}
|
||||
|
||||
public MemoryBlock getBlock() {
|
||||
return block;
|
||||
}
|
||||
|
||||
public String getProgramName() {
|
||||
DomainFile df = program.getDomainFile();
|
||||
if (df != null) {
|
||||
return df.getName();
|
||||
}
|
||||
return program.getName();
|
||||
}
|
||||
|
||||
public String getBlockName() {
|
||||
return block.getName();
|
||||
}
|
||||
|
||||
public Address getMinAddress() {
|
||||
return block.getStart();
|
||||
}
|
||||
|
||||
public Address getMaxAddress() {
|
||||
return block.getEnd();
|
||||
}
|
||||
|
||||
public long getLength() {
|
||||
return block.getSize();
|
||||
}
|
||||
|
||||
public double getScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
public double score(TraceSection section, DebuggerStaticMappingService service) {
|
||||
if (section == null) {
|
||||
return score = 0;
|
||||
}
|
||||
return score = service.proposeSectionMap(section, program, block).computeScore();
|
||||
}
|
||||
|
||||
public ProgramLocation getProgramLocation() {
|
||||
return new ProgramLocation(program, block.getStart());
|
||||
}
|
||||
}
|
||||
|
||||
enum MemoryBlockTableColumns
|
||||
implements EnumeratedTableColumn<MemoryBlockTableColumns, MemoryBlockRow> {
|
||||
SCORE("Score", Double.class, MemoryBlockRow::getScore, SortDirection.DESCENDING),
|
||||
PROGRAM("Program", String.class, MemoryBlockRow::getProgramName, SortDirection.ASCENDING),
|
||||
BLOCK("Block", String.class, MemoryBlockRow::getBlockName, SortDirection.ASCENDING),
|
||||
START("Start Address", Address.class, MemoryBlockRow::getMinAddress, SortDirection.ASCENDING),
|
||||
END("End Address", Address.class, MemoryBlockRow::getMaxAddress, SortDirection.ASCENDING),
|
||||
LENGTH("Length", Long.class, MemoryBlockRow::getLength, SortDirection.ASCENDING);
|
||||
|
||||
<T> MemoryBlockTableColumns(String header, Class<T> cls, Function<MemoryBlockRow, T> getter,
|
||||
SortDirection dir) {
|
||||
this.header = header;
|
||||
this.cls = cls;
|
||||
this.getter = getter;
|
||||
this.dir = dir;
|
||||
}
|
||||
|
||||
private final String header;
|
||||
private final Function<MemoryBlockRow, ?> getter;
|
||||
private final Class<?> cls;
|
||||
private final SortDirection dir;
|
||||
|
||||
@Override
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueClass() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueOf(MemoryBlockRow row) {
|
||||
return getter.apply(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortDirection defaultSortDirection() {
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
||||
final EnumeratedColumnTableModel<MemoryBlockRow> tableModel =
|
||||
new DefaultEnumeratedColumnTableModel<>("Blocks", MemoryBlockTableColumns.class);
|
||||
|
||||
GTable table;
|
||||
GhidraTableFilterPanel<MemoryBlockRow> filterPanel;
|
||||
|
||||
private Entry<Program, MemoryBlock> chosen;
|
||||
|
||||
protected DebuggerBlockChooserDialog() {
|
||||
super("Memory Blocks", true, true, true, false);
|
||||
populateComponents();
|
||||
}
|
||||
|
||||
protected void populateComponents() {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
|
||||
table = new GTable(tableModel);
|
||||
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
panel.add(new JScrollPane(table));
|
||||
|
||||
filterPanel = new GhidraTableFilterPanel<>(table, tableModel);
|
||||
panel.add(filterPanel, BorderLayout.SOUTH);
|
||||
|
||||
addWorkPanel(panel);
|
||||
|
||||
addOKButton();
|
||||
addCancelButton();
|
||||
|
||||
table.getSelectionModel().addListSelectionListener(evt -> {
|
||||
okButton.setEnabled(filterPanel.getSelectedItems().size() == 1);
|
||||
// Prevent empty selection
|
||||
});
|
||||
|
||||
// TODO: Adjust column widths?
|
||||
TableColumnModel columnModel = table.getColumnModel();
|
||||
|
||||
TableColumn startCol = columnModel.getColumn(MemoryBlockTableColumns.START.ordinal());
|
||||
startCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
|
||||
TableColumn endCol = columnModel.getColumn(MemoryBlockTableColumns.END.ordinal());
|
||||
endCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
|
||||
TableColumn lenCol = columnModel.getColumn(MemoryBlockTableColumns.LENGTH.ordinal());
|
||||
lenCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX);
|
||||
}
|
||||
|
||||
public Map.Entry<Program, MemoryBlock> chooseBlock(PluginTool tool, TraceSection section,
|
||||
Collection<Program> programs) {
|
||||
setBlocksFromPrograms(programs);
|
||||
computeScores(section, tool.getService(DebuggerStaticMappingService.class));
|
||||
selectHighestScoringBlock();
|
||||
tool.showDialog(this);
|
||||
return getChosen();
|
||||
}
|
||||
|
||||
protected void computeScores(TraceSection section, DebuggerStaticMappingService service) {
|
||||
for (MemoryBlockRow rec : tableModel.getModelData()) {
|
||||
rec.score(section, service);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setBlocksFromPrograms(Collection<Program> programs) {
|
||||
this.tableModel.clear();
|
||||
List<MemoryBlockRow> rows = new ArrayList<>();
|
||||
for (Program program : programs) {
|
||||
for (MemoryBlock block : program.getMemory().getBlocks()) {
|
||||
rows.add(new MemoryBlockRow(program, block));
|
||||
}
|
||||
}
|
||||
this.tableModel.addAll(rows);
|
||||
}
|
||||
|
||||
protected void selectHighestScoringBlock() {
|
||||
MemoryBlockRow best = null;
|
||||
for (MemoryBlockRow rec : tableModel.getModelData()) {
|
||||
if (best == null || rec.getScore() > best.getScore()) {
|
||||
best = rec;
|
||||
}
|
||||
}
|
||||
if (best != null) {
|
||||
filterPanel.setSelectedItem(best);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void okCallback() {
|
||||
MemoryBlockRow sel = filterPanel.getSelectedItem();
|
||||
this.chosen = sel == null ? null : Map.entry(sel.program, sel.block);
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelCallback() {
|
||||
this.chosen = null;
|
||||
close();
|
||||
}
|
||||
|
||||
public Entry<Program, MemoryBlock> getChosen() {
|
||||
return chosen;
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
|||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapModulesAction;
|
||||
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Swing;
|
||||
|
@ -100,8 +101,8 @@ public class DebuggerModuleMapProposalDialog
|
|||
protected static class ModuleMapPropsalTableModel extends
|
||||
DefaultEnumeratedColumnTableModel<ModuleMapTableColumns, ModuleMapEntry> {
|
||||
|
||||
public ModuleMapPropsalTableModel() {
|
||||
super("Module Map", ModuleMapTableColumns.class);
|
||||
public ModuleMapPropsalTableModel(PluginTool tool) {
|
||||
super(tool, "Module Map", ModuleMapTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -113,13 +114,13 @@ public class DebuggerModuleMapProposalDialog
|
|||
private final DebuggerModulesProvider provider;
|
||||
|
||||
protected DebuggerModuleMapProposalDialog(DebuggerModulesProvider provider) {
|
||||
super(MapModulesAction.NAME);
|
||||
super(provider.getTool(), MapModulesAction.NAME);
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModuleMapPropsalTableModel createTableModel() {
|
||||
return new ModuleMapPropsalTableModel();
|
||||
protected ModuleMapPropsalTableModel createTableModel(PluginTool tool) {
|
||||
return new ModuleMapPropsalTableModel(tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -53,8 +53,7 @@ import ghidra.async.TypeSpec;
|
|||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.model.*;
|
||||
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.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
@ -207,8 +206,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
|||
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
|
||||
ModuleTableColumns, ObjectKey, ModuleRow, TraceModule> {
|
||||
|
||||
public ModuleTableModel() {
|
||||
super("Modules", ModuleTableColumns.class, TraceModule::getObjectKey, ModuleRow::new);
|
||||
public ModuleTableModel(PluginTool tool) {
|
||||
super(tool, "Modules", ModuleTableColumns.class, TraceModule::getObjectKey,
|
||||
ModuleRow::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -221,8 +221,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
|||
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
|
||||
SectionTableColumns, ObjectKey, SectionRow, TraceSection> {
|
||||
|
||||
public SectionTableModel() {
|
||||
super("Sections", SectionTableColumns.class, TraceSection::getObjectKey,
|
||||
public SectionTableModel(PluginTool tool) {
|
||||
super(tool, "Sections", SectionTableColumns.class, TraceSection::getObjectKey,
|
||||
SectionRow::new);
|
||||
}
|
||||
|
||||
|
@ -555,11 +555,11 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
|||
private final RecordersChangedListener recordersChangedListener =
|
||||
new RecordersChangedListener();
|
||||
|
||||
protected final ModuleTableModel moduleTableModel = new ModuleTableModel();
|
||||
protected final ModuleTableModel moduleTableModel;
|
||||
protected GhidraTable moduleTable;
|
||||
private GhidraTableFilterPanel<ModuleRow> moduleFilterPanel;
|
||||
|
||||
protected final SectionTableModel sectionTableModel = new SectionTableModel();
|
||||
protected final SectionTableModel sectionTableModel;
|
||||
protected GhidraTable sectionTable;
|
||||
protected GhidraTableFilterPanel<SectionRow> sectionFilterPanel;
|
||||
private final SectionsBySelectedModulesTableFilter filterSectionsBySelectedModules =
|
||||
|
@ -599,6 +599,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
|||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MODULES, plugin.getName(), null);
|
||||
this.plugin = plugin;
|
||||
|
||||
moduleTableModel = new ModuleTableModel(tool);
|
||||
sectionTableModel = new SectionTableModel(tool);
|
||||
|
||||
setIcon(DebuggerResources.ICON_PROVIDER_MODULES);
|
||||
setHelpLocation(DebuggerResources.HELP_PROVIDER_MODULES);
|
||||
setWindowMenuGroup(DebuggerPluginPackage.NAME);
|
||||
|
@ -607,7 +610,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
|||
|
||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
|
||||
blockChooserDialog = new DebuggerBlockChooserDialog();
|
||||
blockChooserDialog = new DebuggerBlockChooserDialog(tool);
|
||||
moduleProposalDialog = new DebuggerModuleMapProposalDialog(this);
|
||||
sectionProposalDialog = new DebuggerSectionMapProposalDialog(this);
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractDebuggerMapProposalDialog;
|
|||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapSectionsAction;
|
||||
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
|
@ -102,8 +103,8 @@ public class DebuggerSectionMapProposalDialog
|
|||
protected static class SectionMapPropsalTableModel extends
|
||||
DefaultEnumeratedColumnTableModel<SectionMapTableColumns, SectionMapEntry> {
|
||||
|
||||
public SectionMapPropsalTableModel() {
|
||||
super("Section Map", SectionMapTableColumns.class);
|
||||
public SectionMapPropsalTableModel(PluginTool tool) {
|
||||
super(tool, "Section Map", SectionMapTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -115,13 +116,13 @@ public class DebuggerSectionMapProposalDialog
|
|||
private final DebuggerModulesProvider provider;
|
||||
|
||||
public DebuggerSectionMapProposalDialog(DebuggerModulesProvider provider) {
|
||||
super(MapSectionsAction.NAME);
|
||||
super(provider.getTool(), MapSectionsAction.NAME);
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SectionMapPropsalTableModel createTableModel() {
|
||||
return new SectionMapPropsalTableModel();
|
||||
protected SectionMapPropsalTableModel createTableModel(PluginTool tool) {
|
||||
return new SectionMapPropsalTableModel(tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -42,8 +42,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
|||
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
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.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
@ -103,9 +102,9 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
|
|||
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
|
||||
StaticMappingTableColumns, ObjectKey, StaticMappingRow, TraceStaticMapping> {
|
||||
|
||||
public MappingTableModel() {
|
||||
super("Mappings", StaticMappingTableColumns.class, TraceStaticMapping::getObjectKey,
|
||||
StaticMappingRow::new);
|
||||
public MappingTableModel(PluginTool tool) {
|
||||
super(tool, "Mappings", StaticMappingTableColumns.class,
|
||||
TraceStaticMapping::getObjectKey, StaticMappingRow::new);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,7 +148,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
|
|||
|
||||
private ListenerForStaticMappingDisplay listener = new ListenerForStaticMappingDisplay();
|
||||
|
||||
protected final MappingTableModel mappingTableModel = new MappingTableModel();
|
||||
protected final MappingTableModel mappingTableModel;
|
||||
|
||||
private JPanel mainPanel = new JPanel(new BorderLayout());
|
||||
protected GTable mappingTable;
|
||||
|
@ -165,6 +164,8 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
|
|||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MAPPINGS, plugin.getName(), null);
|
||||
this.plugin = plugin;
|
||||
|
||||
mappingTableModel = new MappingTableModel(tool);
|
||||
|
||||
this.addMappingDialog = new DebuggerAddMappingDialog();
|
||||
this.autoWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
|
||||
|
|
|
@ -647,7 +647,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
|||
TargetObject targetObject = container.getTargetObject();
|
||||
String name = targetObject.getName();
|
||||
DefaultEnumeratedColumnTableModel<?, ObjectAttributeRow> model =
|
||||
new DefaultEnumeratedColumnTableModel<>(name, ObjectAttributeColumn.class);
|
||||
new DefaultEnumeratedColumnTableModel<>(tool, name, ObjectAttributeColumn.class);
|
||||
Map<String, Object> map = container.getAttributeMap();
|
||||
List<ObjectAttributeRow> list = new ArrayList<>();
|
||||
for (Object val : map.values()) {
|
||||
|
|
|
@ -64,9 +64,7 @@ public class DebuggerAttachDialog extends DialogComponentProvider {
|
|||
protected RefreshAction actionRefresh;
|
||||
protected JButton attachButton;
|
||||
|
||||
private final RowObjectTableModel<TargetAttachable> processes =
|
||||
new DefaultEnumeratedColumnTableModel<>("Attachables",
|
||||
AttachableProcessesTableColumns.class);
|
||||
private final RowObjectTableModel<TargetAttachable> processes;
|
||||
protected TargetAttacher attacher;
|
||||
private GTable processTable;
|
||||
|
||||
|
@ -74,6 +72,8 @@ public class DebuggerAttachDialog extends DialogComponentProvider {
|
|||
super(AbstractAttachAction.NAME, true, true, true, false);
|
||||
this.provider = provider;
|
||||
this.plugin = provider.getPlugin();
|
||||
processes = new DefaultEnumeratedColumnTableModel<>(plugin.getTool(), "Attachables",
|
||||
AttachableProcessesTableColumns.class);
|
||||
|
||||
populateComponents();
|
||||
createActions();
|
||||
|
|
|
@ -52,67 +52,6 @@ public class ObjectEnumeratedColumnTableModel<C extends ObjectsEnumeratedTableCo
|
|||
}
|
||||
}
|
||||
|
||||
public class TableRowIterator implements RowIterator<R> {
|
||||
protected final ListIterator<R> it = modelData.listIterator();
|
||||
protected int index;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public R next() {
|
||||
index = it.nextIndex();
|
||||
return it.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious() {
|
||||
return it.hasPrevious();
|
||||
}
|
||||
|
||||
@Override
|
||||
public R previous() {
|
||||
index = it.previousIndex();
|
||||
return it.previous();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextIndex() {
|
||||
return it.nextIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int previousIndex() {
|
||||
return it.previousIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
it.remove();
|
||||
fireTableRowsDeleted(index, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(R e) {
|
||||
it.set(e);
|
||||
fireTableRowsUpdated(index, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyUpdated() {
|
||||
fireTableRowsUpdated(index, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(R e) {
|
||||
it.add(e);
|
||||
int nextIndex = it.nextIndex();
|
||||
fireTableRowsInserted(nextIndex, nextIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private final List<R> modelData = new ArrayList<>();
|
||||
private final String name;
|
||||
private C[] cols;
|
||||
|
|
|
@ -38,7 +38,7 @@ 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.gui.pcode.UniqueRow.RefType;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerPcodeMachine;
|
||||
import ghidra.app.plugin.processors.sleigh.template.OpTpl;
|
||||
import ghidra.app.services.DebuggerEmulationService;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
|
@ -49,12 +49,10 @@ import ghidra.base.widgets.table.DataTypeTableCellEditor;
|
|||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.options.AutoOptions;
|
||||
import ghidra.framework.options.annotation.*;
|
||||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.PcodeExecutorState;
|
||||
import ghidra.pcode.exec.PcodeFrame;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.lang.Language;
|
||||
|
@ -143,8 +141,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
|||
|
||||
protected static class PcodeTableModel
|
||||
extends DefaultEnumeratedColumnTableModel<PcodeTableColumns, PcodeRow> {
|
||||
public PcodeTableModel() {
|
||||
super("p-code", PcodeTableColumns.class);
|
||||
public PcodeTableModel(PluginTool tool) {
|
||||
super(tool, "p-code", PcodeTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -208,8 +206,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
|||
|
||||
protected static class UniqueTableModel
|
||||
extends DefaultEnumeratedColumnTableModel<UniqueTableColumns, UniqueRow> {
|
||||
public UniqueTableModel() {
|
||||
super("Unique", UniqueTableColumns.class);
|
||||
public UniqueTableModel(PluginTool tool) {
|
||||
super(tool, "Unique", UniqueTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -575,12 +573,12 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
|||
|
||||
JSplitPane mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||
|
||||
final UniqueTableModel uniqueTableModel;
|
||||
GhidraTable uniqueTable;
|
||||
UniqueTableModel uniqueTableModel = new UniqueTableModel();
|
||||
GhidraTableFilterPanel<UniqueRow> uniqueFilterPanel;
|
||||
|
||||
final PcodeTableModel pcodeTableModel;
|
||||
GhidraTable pcodeTable;
|
||||
PcodeTableModel pcodeTableModel = new PcodeTableModel();
|
||||
JLabel instructionLabel;
|
||||
// No filter panel on p-code
|
||||
PcodeCellRenderer codeColRenderer;
|
||||
|
@ -592,6 +590,9 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
|||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_PCODE, plugin.getName(), null);
|
||||
this.plugin = plugin;
|
||||
|
||||
uniqueTableModel = new UniqueTableModel(tool);
|
||||
pcodeTableModel = new PcodeTableModel(tool);
|
||||
|
||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
this.autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
|
||||
|
||||
|
@ -877,9 +878,10 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
|||
pcodeTableModel.add(row);
|
||||
}
|
||||
|
||||
protected void populateFromFrame(PcodeFrame frame, PcodeExecutorState<byte[]> state) {
|
||||
protected <T> void populateFromFrame(PcodeFrame frame, PcodeExecutorState<T> state,
|
||||
PcodeArithmetic<T> arithmetic) {
|
||||
populatePcode(frame);
|
||||
populateUnique(frame, state);
|
||||
populateUnique(frame, state, arithmetic);
|
||||
}
|
||||
|
||||
protected int computeCodeColWidth(List<PcodeRow> rows) {
|
||||
|
@ -916,7 +918,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
|||
pcodeTable.scrollToSelectedRow();
|
||||
}
|
||||
|
||||
protected void populateUnique(PcodeFrame frame, PcodeExecutorState<byte[]> state) {
|
||||
protected <T> void populateUnique(PcodeFrame frame, PcodeExecutorState<T> state,
|
||||
PcodeArithmetic<T> arithmetic) {
|
||||
Language language = current.getTrace().getBaseLanguage();
|
||||
// NOTE: They may overlap. I don't think I care.
|
||||
Set<Varnode> uniques = new TreeSet<>(UNIQUE_COMPARATOR);
|
||||
|
@ -936,7 +939,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
|||
// TODO: Permit modification of unique variables
|
||||
List<UniqueRow> toAdd =
|
||||
uniques.stream()
|
||||
.map(u -> new UniqueRow(this, language, state, u))
|
||||
.map(u -> new UniqueRow(this, language, state, arithmetic, u))
|
||||
.collect(Collectors.toList());
|
||||
uniqueTableModel.addAll(toAdd);
|
||||
}
|
||||
|
@ -971,7 +974,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
|||
populateSingleton(EnumPcodeRow.DECODE);
|
||||
return;
|
||||
}
|
||||
DebuggerTracePcodeEmulator emu = emulationService.getCachedEmulator(trace, time);
|
||||
DebuggerPcodeMachine<?> emu = emulationService.getCachedEmulator(trace, time);
|
||||
if (emu != null) {
|
||||
clear();
|
||||
doLoadPcodeFrameFromEmulator(emu);
|
||||
|
@ -986,8 +989,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
|||
}, SwingExecutorService.LATER);
|
||||
}
|
||||
|
||||
protected void doLoadPcodeFrameFromEmulator(DebuggerTracePcodeEmulator emu) {
|
||||
PcodeThread<byte[]> thread = emu.getThread(current.getThread().getPath(), false);
|
||||
protected <T> void doLoadPcodeFrameFromEmulator(DebuggerPcodeMachine<T> emu) {
|
||||
PcodeThread<T> thread = emu.getThread(current.getThread().getPath(), false);
|
||||
if (thread == null) {
|
||||
/**
|
||||
* Happens when focus is on a thread not stepped in the schedule. Stepping it would
|
||||
|
@ -1012,7 +1015,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
|||
populateSingleton(EnumPcodeRow.DECODE);
|
||||
return;
|
||||
}
|
||||
populateFromFrame(frame, thread.getState());
|
||||
populateFromFrame(frame, thread.getState(), thread.getArithmetic());
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
|
|
|
@ -19,8 +19,9 @@ import java.math.BigInteger;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
import ghidra.docking.settings.SettingsImpl;
|
||||
import ghidra.pcode.exec.PcodeArithmetic;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.pcode.exec.PcodeExecutorState;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.lang.Language;
|
||||
|
@ -39,36 +40,54 @@ public class UniqueRow {
|
|||
if (isWrite) {
|
||||
return READ_WRITE;
|
||||
}
|
||||
else {
|
||||
return READ;
|
||||
}
|
||||
return READ;
|
||||
}
|
||||
else {
|
||||
if (isWrite) {
|
||||
return WRITE;
|
||||
}
|
||||
else {
|
||||
return NONE;
|
||||
}
|
||||
if (isWrite) {
|
||||
return WRITE;
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Putting these related methods, all using a common type, into a nested class allows us to
|
||||
* introduce {@code <T>}, essentially a "universal type."
|
||||
*
|
||||
* @param <T> the type of state from which concrete parts are extracted.
|
||||
*/
|
||||
public static class ConcretizedState<T> {
|
||||
private final PcodeExecutorState<T> state;
|
||||
private final PcodeArithmetic<T> arithmetic;
|
||||
|
||||
public ConcretizedState(PcodeExecutorState<T> state, PcodeArithmetic<T> arithmetic) {
|
||||
this.state = state;
|
||||
this.arithmetic = arithmetic;
|
||||
}
|
||||
|
||||
public byte[] getBytes(Varnode vn) {
|
||||
return arithmetic.toConcrete(state.getVar(vn), Purpose.INSPECT);
|
||||
}
|
||||
|
||||
public BigInteger getValue(Varnode vn) {
|
||||
return arithmetic.toBigInteger(state.getVar(vn), Purpose.INSPECT);
|
||||
}
|
||||
}
|
||||
|
||||
protected final DebuggerPcodeStepperProvider provider;
|
||||
protected final Language language;
|
||||
protected final PcodeExecutorState<byte[]> state;
|
||||
protected final ConcretizedState<?> state;
|
||||
protected final Varnode vn;
|
||||
|
||||
protected DataType dataType;
|
||||
|
||||
public UniqueRow(DebuggerPcodeStepperProvider provider, Language language,
|
||||
PcodeExecutorState<byte[]> state, Varnode vn) {
|
||||
public <T> UniqueRow(DebuggerPcodeStepperProvider provider, Language language,
|
||||
PcodeExecutorState<T> state, PcodeArithmetic<T> arithmetic, Varnode vn) {
|
||||
if (!vn.isUnique()) {
|
||||
throw new AssertionError("Only uniques allowed in unique table");
|
||||
}
|
||||
this.provider = provider;
|
||||
this.language = language;
|
||||
this.state = state;
|
||||
this.state = new ConcretizedState<>(state, arithmetic);
|
||||
this.vn = vn;
|
||||
}
|
||||
|
||||
|
@ -105,9 +124,26 @@ public class UniqueRow {
|
|||
return String.format("$U%x:%d", vn.getOffset(), vn.getSize());
|
||||
}
|
||||
|
||||
// TODO: Pluggable columns to display abstract pieces
|
||||
|
||||
/**
|
||||
* Renders the raw bytes as space-separated hexadecimal-digit pairs, if concrete
|
||||
*
|
||||
* <p>
|
||||
* If the state's concrete piece cannot be extracted by the machine's arithmetic, this simply
|
||||
* returns {@code "(not concrete)"}.
|
||||
*
|
||||
* @return the byte string
|
||||
*/
|
||||
public String getBytes() {
|
||||
// TODO: Could keep value cached?
|
||||
byte[] bytes = state.getVar(vn);
|
||||
byte[] bytes;
|
||||
try {
|
||||
bytes = state.getBytes(vn);
|
||||
}
|
||||
catch (UnsupportedOperationException e) {
|
||||
return "(not concrete)";
|
||||
}
|
||||
if (bytes == null) {
|
||||
return "??";
|
||||
}
|
||||
|
@ -117,9 +153,18 @@ public class UniqueRow {
|
|||
return NumericUtilities.convertBytesToString(bytes, " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the concrete part of the variable as an unsigned big integer
|
||||
*
|
||||
* @return the value, or null if the value cannot be made concrete
|
||||
*/
|
||||
public BigInteger getValue() {
|
||||
byte[] bytes = state.getVar(vn);
|
||||
return Utils.bytesToBigInteger(bytes, bytes.length, language.isBigEndian(), false);
|
||||
try {
|
||||
return state.getValue(vn);
|
||||
}
|
||||
catch (UnsupportedOperationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public DataType getDataType() {
|
||||
|
@ -135,7 +180,7 @@ public class UniqueRow {
|
|||
if (dataType == null) {
|
||||
return "";
|
||||
}
|
||||
byte[] bytes = state.getVar(vn);
|
||||
byte[] bytes = state.getBytes(vn);
|
||||
if (bytes == null) {
|
||||
return "??";
|
||||
}
|
||||
|
|
|
@ -203,8 +203,7 @@ public class DebuggerPlatformPlugin extends Plugin {
|
|||
|
||||
private final ChangeListener classChangeListener = evt -> this.classesChanged();
|
||||
|
||||
protected final DebuggerSelectPlatformOfferDialog offerDialog =
|
||||
new DebuggerSelectPlatformOfferDialog();
|
||||
protected final DebuggerSelectPlatformOfferDialog offerDialog;
|
||||
|
||||
final Map<Trace, PlatformActionSet> actionsChoosePlatform = new WeakHashMap<>();
|
||||
DockingAction actionMore;
|
||||
|
@ -212,6 +211,7 @@ public class DebuggerPlatformPlugin extends Plugin {
|
|||
public DebuggerPlatformPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
|
||||
offerDialog = new DebuggerSelectPlatformOfferDialog(tool);
|
||||
|
||||
ClassSearcher.addChangeListener(classChangeListener);
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import docking.widgets.table.ColumnSortState.SortDirection;
|
|||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.util.DefaultLanguageService;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
|
@ -134,8 +135,8 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
|
|||
public static class OfferTableModel
|
||||
extends DefaultEnumeratedColumnTableModel<OfferTableColumns, DebuggerPlatformOffer> {
|
||||
|
||||
public OfferTableModel() {
|
||||
super("Offers", OfferTableColumns.class);
|
||||
public OfferTableModel(PluginTool tool) {
|
||||
super(tool, "Offers", OfferTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -146,14 +147,13 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
|
|||
}
|
||||
|
||||
public static class OfferPanel extends JPanel {
|
||||
private final OfferTableModel offerTableModel = new OfferTableModel();
|
||||
private final GhidraTable offerTable = new GhidraTable(offerTableModel);
|
||||
private final GhidraTableFilterPanel<DebuggerPlatformOffer> offerTableFilterPanel =
|
||||
new GhidraTableFilterPanel<>(offerTable, offerTableModel);
|
||||
private final OfferTableModel offerTableModel;
|
||||
private final GhidraTable offerTable;
|
||||
private final GhidraTableFilterPanel<DebuggerPlatformOffer> offerTableFilterPanel;
|
||||
private final JLabel descLabel = new JLabel();
|
||||
private final JCheckBox overrideCheckBox = new JCheckBox("Show Only Recommended Offers");
|
||||
|
||||
private final JScrollPane scrollPane = new JScrollPane(offerTable) {
|
||||
private final JScrollPane scrollPane = new JScrollPane() {
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
Dimension pref = super.getPreferredSize();
|
||||
|
@ -178,7 +178,12 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
|
|||
private LanguageID preferredLangID;
|
||||
private CompilerSpecID preferredCsID;
|
||||
|
||||
{
|
||||
protected OfferPanel(PluginTool tool) {
|
||||
offerTableModel = new OfferTableModel(tool);
|
||||
offerTable = new GhidraTable(offerTableModel);
|
||||
offerTableFilterPanel = new GhidraTableFilterPanel<>(offerTable, offerTableModel);
|
||||
scrollPane.setViewportView(offerTable);
|
||||
|
||||
JPanel descPanel = new JPanel(new BorderLayout());
|
||||
descPanel.setBorder(BorderFactory.createTitledBorder("Description"));
|
||||
descPanel.add(descLabel, BorderLayout.CENTER);
|
||||
|
@ -263,13 +268,14 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private final OfferPanel offerPanel = new OfferPanel();
|
||||
private final OfferPanel offerPanel;
|
||||
|
||||
private boolean isCancelled = false;
|
||||
|
||||
protected DebuggerSelectPlatformOfferDialog() {
|
||||
protected DebuggerSelectPlatformOfferDialog(PluginTool tool) {
|
||||
super(DebuggerResources.NAME_CHOOSE_PLATFORM, true, false, true, false);
|
||||
|
||||
offerPanel = new OfferPanel(tool);
|
||||
populateComponents();
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import docking.widgets.table.DefaultEnumeratedColumnTableModel;
|
|||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
import docking.widgets.table.GTable;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
|
@ -105,8 +106,8 @@ public class DebuggerAvailableRegistersDialog extends DialogComponentProvider {
|
|||
|
||||
protected static class AvailableRegistersTableModel extends
|
||||
DefaultEnumeratedColumnTableModel<AvailableRegisterTableColumns, AvailableRegisterRow> {
|
||||
public AvailableRegistersTableModel() {
|
||||
super("Available Registers", AvailableRegisterTableColumns.class);
|
||||
public AvailableRegistersTableModel(PluginTool tool) {
|
||||
super(tool, "Available Registers", AvailableRegisterTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -119,8 +120,7 @@ public class DebuggerAvailableRegistersDialog extends DialogComponentProvider {
|
|||
|
||||
private Language language;
|
||||
|
||||
/* testing */ final AvailableRegistersTableModel availableTableModel =
|
||||
new AvailableRegistersTableModel();
|
||||
/* testing */ final AvailableRegistersTableModel availableTableModel;
|
||||
private final Map<Register, AvailableRegisterRow> regMap = new HashMap<>();
|
||||
|
||||
private GTable availableTable;
|
||||
|
@ -135,6 +135,7 @@ public class DebuggerAvailableRegistersDialog extends DialogComponentProvider {
|
|||
super(DebuggerResources.SelectRegistersAction.NAME, true, true, true, false);
|
||||
this.provider = provider;
|
||||
|
||||
availableTableModel = new AvailableRegistersTableModel(provider.getTool());
|
||||
populateComponents();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,4 +13,23 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec;
|
||||
package ghidra.app.plugin.core.debug.gui.register;
|
||||
|
||||
import docking.widgets.table.DynamicTableColumn;
|
||||
import ghidra.util.classfinder.ExtensionPoint;
|
||||
|
||||
/**
|
||||
* A factory for adding a custom column to the Registers table
|
||||
*
|
||||
* <p>
|
||||
* All discovered factories' columns are automatically added as hidden columns to the Registers
|
||||
* table.
|
||||
*/
|
||||
public interface DebuggerRegisterColumnFactory extends ExtensionPoint {
|
||||
/**
|
||||
* Create the column
|
||||
*
|
||||
* @return the column
|
||||
*/
|
||||
DynamicTableColumn<RegisterRow, ?, ?> create();
|
||||
}
|
|
@ -77,6 +77,7 @@ import ghidra.trace.model.program.TraceProgramView;
|
|||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.*;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.data.DataTypeParser.AllowedDataTypes;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
@ -201,14 +202,24 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
|
||||
protected static class RegistersTableModel
|
||||
extends DefaultEnumeratedColumnTableModel<RegisterTableColumns, RegisterRow> {
|
||||
public RegistersTableModel() {
|
||||
super("Registers", RegisterTableColumns.class);
|
||||
public RegistersTableModel(PluginTool tool) {
|
||||
super(tool, "Registers", RegisterTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RegisterTableColumns> defaultSortOrder() {
|
||||
return List.of(RegisterTableColumns.FAV, RegisterTableColumns.NUMBER);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<RegisterRow> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<RegisterRow> descriptor = super.createTableColumnDescriptor();
|
||||
for (DebuggerRegisterColumnFactory factory : ClassSearcher
|
||||
.getInstances(DebuggerRegisterColumnFactory.class)) {
|
||||
descriptor.addHiddenColumn(factory.create());
|
||||
}
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
|
||||
|
@ -472,8 +483,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
|
||||
private JPanel mainPanel = new JPanel(new BorderLayout());
|
||||
|
||||
final RegistersTableModel regsTableModel;
|
||||
GhidraTable regsTable;
|
||||
RegistersTableModel regsTableModel = new RegistersTableModel();
|
||||
GhidraTableFilterPanel<RegisterRow> regsFilterPanel;
|
||||
Map<Register, RegisterRow> regMap = new HashMap<>();
|
||||
|
||||
|
@ -495,6 +506,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
boolean isClone) {
|
||||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_REGISTERS, plugin.getName());
|
||||
this.plugin = plugin;
|
||||
|
||||
regsTableModel = new RegistersTableModel(tool);
|
||||
|
||||
this.selectionByCSpec = selectionByCSpec;
|
||||
this.favoritesByCSpec = favoritesByCSpec;
|
||||
this.isClone = isClone;
|
||||
|
@ -1387,4 +1401,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES));
|
||||
}
|
||||
}
|
||||
|
||||
public DebuggerCoordinates getCurrent() {
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,30 +18,49 @@ package ghidra.app.plugin.core.debug.gui.register;
|
|||
import java.math.BigInteger;
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.services.DebuggerStateEditingService;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.listing.Data;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* A row displayed in the registers table of the Debugger
|
||||
*/
|
||||
public class RegisterRow {
|
||||
private final DebuggerRegistersProvider provider;
|
||||
private boolean favorite;
|
||||
private final int number;
|
||||
private final Register register;
|
||||
|
||||
public RegisterRow(DebuggerRegistersProvider provider, int number, Register register) {
|
||||
protected RegisterRow(DebuggerRegistersProvider provider, int number, Register register) {
|
||||
this.provider = provider;
|
||||
this.number = number;
|
||||
this.register = Objects.requireNonNull(register);
|
||||
this.favorite = provider.isFavorite(register);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this register is one of the user's favorites
|
||||
*
|
||||
* <p>
|
||||
* Note: Favorites are memorized on a per-compiler-spec (ABI, almost) basis.
|
||||
*
|
||||
* @param favorite true if favorite
|
||||
*/
|
||||
public void setFavorite(boolean favorite) {
|
||||
this.favorite = favorite;
|
||||
provider.setFavorite(register, favorite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this register is one of the user's favorites
|
||||
*
|
||||
* @return true if favorite
|
||||
*/
|
||||
public boolean isFavorite() {
|
||||
return favorite;
|
||||
}
|
||||
|
@ -55,18 +74,42 @@ public class RegisterRow {
|
|||
return number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the register
|
||||
*
|
||||
* @return the register
|
||||
*/
|
||||
public Register getRegister() {
|
||||
return register;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the register's name
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return register.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the register can be edited
|
||||
*
|
||||
* @return true if editable
|
||||
*/
|
||||
public boolean isValueEditable() {
|
||||
return provider.canWriteRegister(register);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to set the register's value
|
||||
*
|
||||
* <p>
|
||||
* The edit will be directed according to the tool's current edit mode. See
|
||||
* {@link DebuggerStateEditingService#getCurrentMode(Trace)}
|
||||
*
|
||||
* @param value the value
|
||||
*/
|
||||
public void setValue(BigInteger value) {
|
||||
try {
|
||||
provider.writeRegisterValue(register, value);
|
||||
|
@ -78,8 +121,13 @@ public class RegisterRow {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the value of the register
|
||||
*
|
||||
* <p>
|
||||
* TODO: Perhaps some caching for all these getters which rely on the DB, since they could be
|
||||
* invoked on every repaint.
|
||||
*
|
||||
* @return the value
|
||||
*/
|
||||
public BigInteger getValue() {
|
||||
return provider.getRegisterValue(register);
|
||||
|
@ -89,31 +137,78 @@ public class RegisterRow {
|
|||
return provider.getRegisterData(register);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a data type to the register
|
||||
*
|
||||
* <p>
|
||||
* This is memorized in the trace for the current and future snaps
|
||||
*
|
||||
* @param dataType the data type
|
||||
*/
|
||||
public void setDataType(DataType dataType) {
|
||||
provider.writeRegisterDataType(register, dataType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data type of the register
|
||||
*
|
||||
* @return the data type
|
||||
*/
|
||||
public DataType getDataType() {
|
||||
return provider.getRegisterDataType(register);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the register as represented by its data type
|
||||
*
|
||||
* @param representation the value to set
|
||||
*/
|
||||
public void setRepresentation(String representation) {
|
||||
provider.writeRegisterValueRepresentation(register, representation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the register's value can be set via its data type's representation
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isRepresentationEditable() {
|
||||
return provider.canWriteRegisterRepresentation(register);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the register as represented by its data type
|
||||
*
|
||||
* @return the value
|
||||
*/
|
||||
public String getRepresentation() {
|
||||
return provider.getRegisterValueRepresentation(register);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the register's value is (completely) known
|
||||
*
|
||||
* @return true if known
|
||||
*/
|
||||
public boolean isKnown() {
|
||||
return provider.isRegisterKnown(register);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the register's value changed since last navigation or command
|
||||
*
|
||||
* @return true if changed
|
||||
*/
|
||||
public boolean isChanged() {
|
||||
return provider.isRegisterChanged(register);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table's current coordinates (usually also the tool's)
|
||||
*
|
||||
* @return the coordinates
|
||||
*/
|
||||
public DebuggerCoordinates getCurrent() {
|
||||
return provider.getCurrent();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,8 +36,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
|||
import ghidra.app.services.*;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.target.TargetStackFrame;
|
||||
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.lang.Register;
|
||||
|
@ -116,8 +115,8 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
|||
protected static class StackTableModel
|
||||
extends DefaultEnumeratedColumnTableModel<StackTableColumns, StackFrameRow> {
|
||||
|
||||
public StackTableModel() {
|
||||
super("Stack", StackTableColumns.class);
|
||||
public StackTableModel(PluginTool tool) {
|
||||
super(tool, "Stack", StackTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -243,7 +242,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
|||
|
||||
private final SuppressableCallback<Void> cbFrameSelected = new SuppressableCallback<>();
|
||||
|
||||
protected final StackTableModel stackTableModel = new StackTableModel();
|
||||
protected final StackTableModel stackTableModel;
|
||||
protected GhidraTable stackTable;
|
||||
protected GhidraTableFilterPanel<StackFrameRow> stackFilterPanel;
|
||||
|
||||
|
@ -253,7 +252,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
|||
|
||||
public DebuggerStackProvider(DebuggerStackPlugin plugin) {
|
||||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_STACK, plugin.getName());
|
||||
//this.plugin = plugin;
|
||||
stackTableModel = new StackTableModel(tool);
|
||||
|
||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
|
||||
|
|
|
@ -92,8 +92,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
|||
ThreadTableColumns, ObjectKey, ThreadRow, TraceThread> {
|
||||
|
||||
public ThreadTableModel(DebuggerThreadsProvider provider) {
|
||||
super("Threads", ThreadTableColumns.class, TraceThread::getObjectKey,
|
||||
t -> new ThreadRow(provider.modelService, t));
|
||||
super(provider.getTool(), "Threads", ThreadTableColumns.class,
|
||||
TraceThread::getObjectKey, t -> new ThreadRow(provider.modelService, t));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import com.google.common.collect.Collections2;
|
|||
import docking.widgets.table.*;
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||
import ghidra.trace.model.TraceDomainObjectListener;
|
||||
|
@ -130,8 +131,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
|||
}
|
||||
}
|
||||
|
||||
protected final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel =
|
||||
new DefaultEnumeratedColumnTableModel<>("Snapshots", SnapshotTableColumns.class);
|
||||
protected final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel;
|
||||
protected final GTable snapshotTable;
|
||||
protected final GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
|
||||
protected boolean hideScratch = true;
|
||||
|
@ -141,8 +141,10 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
|||
|
||||
protected final SnapshotListener listener = new SnapshotListener();
|
||||
|
||||
public DebuggerSnapshotTablePanel() {
|
||||
public DebuggerSnapshotTablePanel(PluginTool tool) {
|
||||
super(new BorderLayout());
|
||||
snapshotTableModel =
|
||||
new DefaultEnumeratedColumnTableModel<>(tool, "Snapshots", SnapshotTableColumns.class);
|
||||
snapshotTable = new GTable(snapshotTableModel);
|
||||
snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
add(new JScrollPane(snapshotTable));
|
||||
|
|
|
@ -60,7 +60,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
|||
@SuppressWarnings("unused")
|
||||
private final Wiring autoServiceWiring;
|
||||
|
||||
/*testing*/ final DebuggerSnapshotTablePanel mainPanel = new DebuggerSnapshotTablePanel();
|
||||
/*testing*/ final DebuggerSnapshotTablePanel mainPanel;
|
||||
|
||||
private DebuggerSnapActionContext myActionContext;
|
||||
|
||||
|
@ -80,6 +80,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
|||
setHelpLocation(HELP_PROVIDER_TIME);
|
||||
setWindowMenuGroup(DebuggerPluginPackage.NAME);
|
||||
|
||||
mainPanel = new DebuggerSnapshotTablePanel(tool);
|
||||
buildMainPanel();
|
||||
|
||||
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getSnap());
|
||||
|
|
|
@ -86,7 +86,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider {
|
|||
opStep.addActionListener(evt -> doStep(s -> s.steppedPcodeForward(null, 1)));
|
||||
|
||||
{
|
||||
snapshotPanel = new DebuggerSnapshotTablePanel();
|
||||
snapshotPanel = new DebuggerSnapshotTablePanel(tool);
|
||||
workPanel.add(snapshotPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
|
|
|
@ -197,8 +197,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
|||
|
||||
protected static class WatchTableModel
|
||||
extends DefaultEnumeratedColumnTableModel<WatchTableColumns, WatchRow> {
|
||||
public WatchTableModel() {
|
||||
super("Watches", WatchTableColumns.class);
|
||||
public WatchTableModel(PluginTool tool) {
|
||||
super(tool, "Watches", WatchTableColumns.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -346,7 +346,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
|||
|
||||
private JPanel mainPanel = new JPanel(new BorderLayout());
|
||||
|
||||
protected final WatchTableModel watchTableModel = new WatchTableModel();
|
||||
protected final WatchTableModel watchTableModel;
|
||||
protected GhidraTable watchTable;
|
||||
protected GhidraTableFilterPanel<WatchRow> watchFilterPanel;
|
||||
|
||||
|
@ -366,6 +366,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
|||
public DebuggerWatchesProvider(DebuggerWatchesPlugin plugin) {
|
||||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_WATCHES, plugin.getName());
|
||||
this.plugin = plugin;
|
||||
watchTableModel = new WatchTableModel(tool);
|
||||
|
||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ import ghidra.docking.settings.Settings;
|
|||
import ghidra.docking.settings.SettingsImpl;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState;
|
||||
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece;
|
||||
import ghidra.pcode.exec.trace.TraceSleighUtils;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.*;
|
||||
|
@ -170,21 +170,20 @@ public class WatchRow {
|
|||
return dataType.getValue(buffer, SettingsImpl.NO_SETTINGS, value.length);
|
||||
}
|
||||
|
||||
public static class ReadDepsTraceBytesPcodeExecutorState
|
||||
extends TraceBytesPcodeExecutorState {
|
||||
public static class ReadDepsTraceBytesPcodeExecutorStatePiece
|
||||
extends DirectBytesTracePcodeExecutorStatePiece {
|
||||
private AddressSet reads = new AddressSet();
|
||||
|
||||
public ReadDepsTraceBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread,
|
||||
public ReadDepsTraceBytesPcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
super(trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getVar(AddressSpace space, long offset, int size,
|
||||
boolean truncateAddressableUnit) {
|
||||
byte[] data = super.getVar(space, offset, size, truncateAddressableUnit);
|
||||
public byte[] getVar(AddressSpace space, long offset, int size, boolean quantize) {
|
||||
byte[] data = super.getVar(space, offset, size, quantize);
|
||||
if (space.isMemorySpace()) {
|
||||
offset = truncateOffset(space, offset);
|
||||
offset = quantizeOffset(space, offset);
|
||||
}
|
||||
if (space.isMemorySpace() || space.isRegisterSpace()) {
|
||||
try {
|
||||
|
@ -213,42 +212,42 @@ public class WatchRow {
|
|||
|
||||
public static class ReadDepsPcodeExecutor
|
||||
extends PcodeExecutor<Pair<byte[], Address>> {
|
||||
private ReadDepsTraceBytesPcodeExecutorState depsState;
|
||||
private ReadDepsTraceBytesPcodeExecutorStatePiece depsPiece;
|
||||
|
||||
public ReadDepsPcodeExecutor(ReadDepsTraceBytesPcodeExecutorState depsState,
|
||||
public ReadDepsPcodeExecutor(ReadDepsTraceBytesPcodeExecutorStatePiece depsState,
|
||||
SleighLanguage language, PairedPcodeArithmetic<byte[], Address> arithmetic,
|
||||
PcodeExecutorState<Pair<byte[], Address>> state) {
|
||||
super(language, arithmetic, state);
|
||||
this.depsState = depsState;
|
||||
this.depsPiece = depsState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PcodeFrame execute(PcodeProgram program,
|
||||
PcodeUseropLibrary<Pair<byte[], Address>> library) {
|
||||
depsState.reset();
|
||||
depsPiece.reset();
|
||||
return super.execute(program, library);
|
||||
}
|
||||
|
||||
public AddressSet getReads() {
|
||||
return depsState.getReads();
|
||||
return depsPiece.getReads();
|
||||
}
|
||||
}
|
||||
|
||||
protected static ReadDepsPcodeExecutor buildAddressDepsExecutor(
|
||||
DebuggerCoordinates coordinates) {
|
||||
Trace trace = coordinates.getTrace();
|
||||
ReadDepsTraceBytesPcodeExecutorState state =
|
||||
new ReadDepsTraceBytesPcodeExecutorState(trace, coordinates.getViewSnap(),
|
||||
ReadDepsTraceBytesPcodeExecutorStatePiece piece =
|
||||
new ReadDepsTraceBytesPcodeExecutorStatePiece(trace, coordinates.getViewSnap(),
|
||||
coordinates.getThread(), coordinates.getFrame());
|
||||
Language language = trace.getBaseLanguage();
|
||||
if (!(language instanceof SleighLanguage)) {
|
||||
throw new IllegalArgumentException("Watch expressions require a SLEIGH language");
|
||||
}
|
||||
PcodeExecutorState<Pair<byte[], Address>> paired =
|
||||
state.paired(new AddressOfPcodeExecutorState(language.isBigEndian()));
|
||||
PcodeExecutorState<Pair<byte[], Address>> paired = new DefaultPcodeExecutorState<>(piece)
|
||||
.paired(new AddressOfPcodeExecutorStatePiece(language.isBigEndian()));
|
||||
PairedPcodeArithmetic<byte[], Address> arithmetic = new PairedPcodeArithmetic<>(
|
||||
BytesPcodeArithmetic.forLanguage(language), AddressOfPcodeArithmetic.INSTANCE);
|
||||
return new ReadDepsPcodeExecutor(state, (SleighLanguage) language, arithmetic, paired);
|
||||
return new ReadDepsPcodeExecutor(piece, (SleighLanguage) language, arithmetic, paired);
|
||||
}
|
||||
|
||||
public void setCoordinates(DebuggerCoordinates coordinates) {
|
||||
|
@ -271,7 +270,7 @@ public class WatchRow {
|
|||
recompile();
|
||||
}
|
||||
if (coordinates.isAliveAndReadsPresent()) {
|
||||
asyncExecutor = TracePcodeUtils.executorForCoordinates(coordinates);
|
||||
asyncExecutor = DebuggerPcodeUtils.executorForCoordinates(coordinates);
|
||||
}
|
||||
executorWithState = TraceSleighUtils.buildByteWithStateExecutor(trace,
|
||||
coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame());
|
||||
|
|
|
@ -20,7 +20,7 @@ import java.util.concurrent.*;
|
|||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.pcode.exec.AccessPcodeExecutionException;
|
||||
import ghidra.pcode.exec.trace.TraceCachedWriteBytesPcodeExecutorState;
|
||||
import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece;
|
||||
import ghidra.pcode.exec.trace.TraceSleighUtils;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
|
@ -31,8 +31,17 @@ import ghidra.trace.model.memory.TraceMemoryState;
|
|||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
public abstract class AbstractReadsTargetPcodeExecutorState
|
||||
extends TraceCachedWriteBytesPcodeExecutorState {
|
||||
/**
|
||||
* An executor state piece that knows to read live state if applicable
|
||||
*
|
||||
* <p>
|
||||
* This takes a handle to the trace's recorder, if applicable, and will check if the source snap is
|
||||
* the recorder's snap. If so, it will direct the recorder to capture the desired state, if they're
|
||||
* not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state will wait up to 1
|
||||
* second (see {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}).
|
||||
*/
|
||||
public abstract class AbstractReadsTargetPcodeExecutorStatePiece
|
||||
extends BytesTracePcodeExecutorStatePiece {
|
||||
|
||||
abstract class AbstractReadsTargetCachedSpace extends CachedSpace {
|
||||
public AbstractReadsTargetCachedSpace(Language language, AddressSpace space,
|
||||
|
@ -86,30 +95,47 @@ public abstract class AbstractReadsTargetPcodeExecutorState
|
|||
protected final TraceRecorder recorder;
|
||||
protected final PluginTool tool;
|
||||
|
||||
public AbstractReadsTargetPcodeExecutorState(PluginTool tool, Trace trace, long snap,
|
||||
public AbstractReadsTargetPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap,
|
||||
TraceThread thread, int frame, TraceRecorder recorder) {
|
||||
super(trace, snap, thread, frame);
|
||||
this.tool = tool;
|
||||
this.recorder = recorder;
|
||||
}
|
||||
|
||||
protected abstract AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
|
||||
TraceMemorySpace tms);
|
||||
/**
|
||||
* Get the tool that manages this state's emulator.
|
||||
*
|
||||
* <p>
|
||||
* This is necessary to obtain the static mapping service, in case memory should be filled from
|
||||
* static images.
|
||||
*
|
||||
* @return the tool
|
||||
*/
|
||||
public PluginTool getTool() {
|
||||
return tool;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CachedSpace getForSpace(AddressSpace space, boolean toWrite) {
|
||||
return spaces.computeIfAbsent(space, s -> {
|
||||
TraceMemorySpace tms;
|
||||
if (s.isUniqueSpace()) {
|
||||
tms = null;
|
||||
/**
|
||||
* Get the recorder associated with the trace
|
||||
*
|
||||
* @return this is used to check for and perform live reads
|
||||
*/
|
||||
public TraceRecorder getRecorder() {
|
||||
return recorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* A partially implemented space map which retrieves "backing" objects from the trace's memory
|
||||
* and register spaces.
|
||||
*/
|
||||
protected abstract class TargetBackedSpaceMap
|
||||
extends CacheingSpaceMap<TraceMemorySpace, CachedSpace> {
|
||||
@Override
|
||||
protected TraceMemorySpace getBacking(AddressSpace space) {
|
||||
try (UndoableTransaction tid =
|
||||
UndoableTransaction.start(trace, "Create space")) {
|
||||
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, true);
|
||||
}
|
||||
else {
|
||||
try (UndoableTransaction tid =
|
||||
UndoableTransaction.start(trace, "Create space")) {
|
||||
tms = TraceSleighUtils.getSpaceForExecution(s, trace, thread, frame, true);
|
||||
}
|
||||
}
|
||||
return createCachedSpace(s, tms);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,67 +17,70 @@ package ghidra.app.plugin.core.debug.service.emulation;
|
|||
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.pcode.emu.BytesPcodeThread;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.PcodeExecutorState;
|
||||
import ghidra.pcode.exec.trace.TracePcodeEmulator;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.pcode.emu.*;
|
||||
import ghidra.pcode.exec.trace.BytesTracePcodeEmulator;
|
||||
import ghidra.pcode.exec.trace.TracePcodeExecutorState;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A trace emulator that knows how to read target memory when necessary
|
||||
*
|
||||
* <p>
|
||||
* This is the default emulator used by the Debugger UI to perform interpolation and extrapolation.
|
||||
* For standalone scripting, consider using {@link BytesTracePcodeEmulator} or {@link PcodeEmulator}
|
||||
* instead. The former readily reads and records its state to traces, while the latter is the
|
||||
* simplest use case. See scripts ending in {@code EmuExampleScript} for example uses.
|
||||
*
|
||||
* <p>
|
||||
* This emulator must always be run in its own thread, or at least a thread that can never lock the
|
||||
* UI. It blocks on target reads so that execution can proceed synchronously. Probably the most
|
||||
* suitable option is to use a background task.
|
||||
*/
|
||||
public class DebuggerTracePcodeEmulator extends TracePcodeEmulator {
|
||||
public class BytesDebuggerPcodeEmulator extends BytesTracePcodeEmulator
|
||||
implements DebuggerPcodeMachine<byte[]> {
|
||||
protected final PluginTool tool;
|
||||
protected final TraceRecorder recorder;
|
||||
|
||||
public DebuggerTracePcodeEmulator(PluginTool tool, Trace trace, long snap,
|
||||
/**
|
||||
* Create the emulator
|
||||
*
|
||||
* @param tool the tool creating the emulator
|
||||
* @param trace the trace from which the emulator loads state
|
||||
* @param snap the snap from which the emulator loads state
|
||||
* @param recorder if applicable, the recorder for the trace's live target
|
||||
*/
|
||||
public BytesDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap,
|
||||
TraceRecorder recorder) {
|
||||
super(trace, snap);
|
||||
this.tool = tool;
|
||||
this.recorder = recorder;
|
||||
}
|
||||
|
||||
protected boolean isRegisterKnown(String threadName, Register register) {
|
||||
TraceThread thread = trace.getThreadManager().getLiveThreadByPath(snap, threadName);
|
||||
TraceMemoryRegisterSpace space =
|
||||
trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||
if (space == null) {
|
||||
return false;
|
||||
}
|
||||
return space.getState(snap, register) == TraceMemoryState.KNOWN;
|
||||
@Override
|
||||
public PluginTool getTool() {
|
||||
return tool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceRecorder getRecorder() {
|
||||
return recorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BytesPcodeThread createThread(String name) {
|
||||
BytesPcodeThread thread = super.createThread(name);
|
||||
Register contextreg = language.getContextBaseRegister();
|
||||
if (contextreg != Register.NO_CONTEXT && !isRegisterKnown(name, contextreg)) {
|
||||
RegisterValue context = trace.getRegisterContextManager()
|
||||
.getValueWithDefault(language, contextreg, snap, thread.getCounter());
|
||||
if (context != null) { // TODO: Why does this happen?
|
||||
thread.overrideContext(context);
|
||||
}
|
||||
}
|
||||
initializeThreadContext(thread);
|
||||
return thread;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> createSharedState() {
|
||||
public TracePcodeExecutorState<byte[]> createSharedState() {
|
||||
return new ReadsTargetMemoryPcodeExecutorState(tool, trace, snap, null, 0, recorder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> emuThread) {
|
||||
public TracePcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> emuThread) {
|
||||
TraceThread traceThread =
|
||||
trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName());
|
||||
return new ReadsTargetRegistersPcodeExecutorState(tool, trace, snap, traceThread, 0,
|
|
@ -0,0 +1,39 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.emulation;
|
||||
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
/**
|
||||
* The Debugger's default emulator factory
|
||||
*/
|
||||
public class BytesDebuggerPcodeEmulatorFactory implements DebuggerPcodeEmulatorFactory {
|
||||
// TODO: Config options:
|
||||
// 1) userop library
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Default Concrete P-code Emulator";
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebuggerPcodeMachine<?> create(PluginTool tool, Trace trace, long snap,
|
||||
TraceRecorder recorder) {
|
||||
return new BytesDebuggerPcodeEmulator(tool, trace, snap, recorder);
|
||||
}
|
||||
}
|
|
@ -17,14 +17,19 @@ package ghidra.app.plugin.core.debug.service.emulation;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.ToggleDockingAction;
|
||||
import ghidra.app.context.ProgramLocationActionContext;
|
||||
import ghidra.app.events.ProgramActivatedPluginEvent;
|
||||
import ghidra.app.events.ProgramClosedPluginEvent;
|
||||
|
@ -32,8 +37,7 @@ import ghidra.app.plugin.PluginCategoryNames;
|
|||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.EmulateAddThreadAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.EmulateProgramAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.async.AsyncLazyMap;
|
||||
import ghidra.framework.plugintool.*;
|
||||
|
@ -49,6 +53,7 @@ import ghidra.trace.model.time.TraceSnapshot;
|
|||
import ghidra.trace.model.time.schedule.CompareResult;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
||||
|
@ -74,20 +79,21 @@ import ghidra.util.task.TaskMonitor;
|
|||
})
|
||||
public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEmulationService {
|
||||
protected static final int MAX_CACHE_SIZE = 5;
|
||||
protected static long nextSnap = Long.MIN_VALUE; // HACK
|
||||
|
||||
protected static class CacheKey implements Comparable<CacheKey> {
|
||||
protected final Trace trace;
|
||||
protected final TraceSchedule time;
|
||||
private final int hashCode;
|
||||
|
||||
public CacheKey(Trace trace, TraceSchedule time) {
|
||||
this.trace = trace;
|
||||
this.time = time;
|
||||
this.trace = Objects.requireNonNull(trace);
|
||||
this.time = Objects.requireNonNull(time);
|
||||
this.hashCode = Objects.hash(trace, time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(trace, time);
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -130,9 +136,9 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
}
|
||||
|
||||
protected static class CachedEmulator {
|
||||
final DebuggerTracePcodeEmulator emulator;
|
||||
final DebuggerPcodeMachine<?> emulator;
|
||||
|
||||
public CachedEmulator(DebuggerTracePcodeEmulator emulator) {
|
||||
public CachedEmulator(DebuggerPcodeMachine<?> emulator) {
|
||||
this.emulator = emulator;
|
||||
}
|
||||
}
|
||||
|
@ -162,6 +168,9 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
}
|
||||
}
|
||||
|
||||
protected DebuggerPcodeEmulatorFactory emulatorFactory =
|
||||
new BytesDebuggerPcodeEmulatorFactory();
|
||||
|
||||
protected final Set<CacheKey> eldest = new LinkedHashSet<>();
|
||||
protected final NavigableMap<CacheKey, CachedEmulator> cache = new TreeMap<>();
|
||||
protected final AsyncLazyMap<CacheKey, Long> requests =
|
||||
|
@ -180,6 +189,10 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
|
||||
DockingAction actionEmulateProgram;
|
||||
DockingAction actionEmulateAddThread;
|
||||
Map<Class<? extends DebuggerPcodeEmulatorFactory>, ToggleDockingAction> //
|
||||
actionsChooseEmulatorFactory = new HashMap<>();
|
||||
|
||||
final ChangeListener classChangeListener = this::classesChanged;
|
||||
|
||||
public DebuggerEmulationServicePlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
|
@ -205,6 +218,46 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
.popupWhen(this::emulateAddThreadEnabled)
|
||||
.onAction(this::emulateAddThreadActivated)
|
||||
.buildAndInstall(tool);
|
||||
ClassSearcher.addChangeListener(classChangeListener);
|
||||
updateConfigureEmulatorStates();
|
||||
}
|
||||
|
||||
private void classesChanged(ChangeEvent e) {
|
||||
updateConfigureEmulatorStates();
|
||||
}
|
||||
|
||||
private ToggleDockingAction createActionChooseEmulator(DebuggerPcodeEmulatorFactory factory) {
|
||||
ToggleDockingAction action = ConfigureEmulatorAction.builder(this)
|
||||
.menuPath(DebuggerPluginPackage.NAME, "Configure Emulator", factory.getTitle())
|
||||
.onAction(ctx -> configureEmulatorActivated(factory))
|
||||
.buildAndInstall(tool);
|
||||
String[] path = action.getMenuBarData().getMenuPath();
|
||||
tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), "zz");
|
||||
return action;
|
||||
}
|
||||
|
||||
private void updateConfigureEmulatorStates() {
|
||||
Map<Class<? extends DebuggerPcodeEmulatorFactory>, DebuggerPcodeEmulatorFactory> byClass =
|
||||
getEmulatorFactories().stream()
|
||||
.collect(Collectors.toMap(DebuggerPcodeEmulatorFactory::getClass,
|
||||
Objects::requireNonNull));
|
||||
Iterator<Entry<Class<? extends DebuggerPcodeEmulatorFactory>, ToggleDockingAction>> it =
|
||||
actionsChooseEmulatorFactory.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Entry<Class<? extends DebuggerPcodeEmulatorFactory>, ToggleDockingAction> ent =
|
||||
it.next();
|
||||
if (!byClass.keySet().contains(ent.getKey())) {
|
||||
tool.removeAction(ent.getValue());
|
||||
}
|
||||
}
|
||||
for (Entry<Class<? extends DebuggerPcodeEmulatorFactory>, DebuggerPcodeEmulatorFactory> ent : byClass
|
||||
.entrySet()) {
|
||||
if (!actionsChooseEmulatorFactory.containsKey(ent.getKey())) {
|
||||
ToggleDockingAction action = createActionChooseEmulator(ent.getValue());
|
||||
action.setSelected(ent.getKey() == emulatorFactory.getClass());
|
||||
actionsChooseEmulatorFactory.put(ent.getKey(), action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean emulateProgramEnabled(ProgramLocationActionContext ctx) {
|
||||
|
@ -228,7 +281,6 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
Trace trace = null;
|
||||
try {
|
||||
trace = ProgramEmulationUtils.launchEmulationTrace(program, ctx.getAddress(), this);
|
||||
|
||||
traceManager.openTrace(trace);
|
||||
traceManager.activateTrace(trace);
|
||||
}
|
||||
|
@ -275,7 +327,6 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
}
|
||||
|
||||
private void emulateAddThreadActivated(ProgramLocationActionContext ctx) {
|
||||
|
||||
Program programOrView = ctx.getProgram();
|
||||
if (programOrView instanceof TraceProgramView) {
|
||||
TraceProgramView view = (TraceProgramView) programOrView;
|
||||
|
@ -322,6 +373,35 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
}
|
||||
}
|
||||
|
||||
private void configureEmulatorActivated(DebuggerPcodeEmulatorFactory factory) {
|
||||
// TODO: Pull up config page. Tool Options? Program/Trace Options?
|
||||
setEmulatorFactory(factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<DebuggerPcodeEmulatorFactory> getEmulatorFactories() {
|
||||
return ClassSearcher.getInstances(DebuggerPcodeEmulatorFactory.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setEmulatorFactory(DebuggerPcodeEmulatorFactory factory) {
|
||||
emulatorFactory = Objects.requireNonNull(factory);
|
||||
for (ToggleDockingAction toggle : actionsChooseEmulatorFactory.values()) {
|
||||
toggle.setSelected(false);
|
||||
}
|
||||
ToggleDockingAction chosen = actionsChooseEmulatorFactory.get(factory.getClass());
|
||||
if (chosen == null) {
|
||||
// Must be special or otherwise not discovered. Could happen.
|
||||
Msg.warn(this, "An undiscovered emulator factory was set via the API: " + factory);
|
||||
}
|
||||
chosen.setSelected(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized DebuggerPcodeEmulatorFactory getEmulatorFactory() {
|
||||
return emulatorFactory;
|
||||
}
|
||||
|
||||
protected Map.Entry<CacheKey, CachedEmulator> findNearestPrefix(CacheKey key) {
|
||||
synchronized (cache) {
|
||||
Map.Entry<CacheKey, CachedEmulator> candidate = cache.floorEntry(key);
|
||||
|
@ -378,7 +458,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
Trace trace = key.trace;
|
||||
TraceSchedule time = key.time;
|
||||
CachedEmulator ce;
|
||||
DebuggerTracePcodeEmulator emu;
|
||||
DebuggerPcodeMachine<?> emu;
|
||||
Map.Entry<CacheKey, CachedEmulator> ancestor = findNearestPrefix(key);
|
||||
if (ancestor != null) {
|
||||
CacheKey prevKey = ancestor.getKey();
|
||||
|
@ -396,7 +476,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
time.finish(trace, prevKey.time, emu, monitor);
|
||||
}
|
||||
else {
|
||||
emu = new DebuggerTracePcodeEmulator(tool, trace, time.getSnap(),
|
||||
emu = emulatorFactory.create(tool, trace, time.getSnap(),
|
||||
modelService == null ? null : modelService.getRecorder(trace));
|
||||
ce = new CachedEmulator(emu);
|
||||
monitor.initialize(time.totalTickCount());
|
||||
|
@ -405,7 +485,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
TraceSnapshot destSnap;
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate")) {
|
||||
destSnap = findScratch(trace, time);
|
||||
emu.writeDown(trace, destSnap.getKey(), time.getSnap(), false);
|
||||
emu.writeDown(trace, destSnap.getKey(), time.getSnap());
|
||||
}
|
||||
|
||||
synchronized (cache) {
|
||||
|
@ -436,7 +516,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
|
|||
}
|
||||
|
||||
@Override
|
||||
public DebuggerTracePcodeEmulator getCachedEmulator(Trace trace, TraceSchedule time) {
|
||||
public DebuggerPcodeMachine<?> getCachedEmulator(Trace trace, TraceSchedule time) {
|
||||
CachedEmulator ce = cache.get(new CacheKey(trace, time));
|
||||
return ce == null ? null : ce.emulator;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.emulation;
|
||||
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.classfinder.ExtensionPoint;
|
||||
|
||||
/**
|
||||
* A factory for configuring and creating a Debugger-integrated emulator
|
||||
*
|
||||
* <p>
|
||||
* See {@link BytesDebuggerPcodeEmulatorFactory} for the default implementation. See the Taint
|
||||
* Analyzer for the archetype of alternative implementations.
|
||||
*/
|
||||
public interface DebuggerPcodeEmulatorFactory extends ExtensionPoint {
|
||||
// TODO: Config options, use ModelFactory as a model
|
||||
|
||||
/**
|
||||
* Get the title, to appear in menus and dialogs
|
||||
*
|
||||
* @return the title
|
||||
*/
|
||||
String getTitle();
|
||||
|
||||
/**
|
||||
* Create the emulator
|
||||
*
|
||||
* @param tool the tool creating the emulator
|
||||
* @param trace the user's current trace from which the emulator should load state
|
||||
* @param snap the user's current snap from which the emulator should load state
|
||||
* @param recorder if applicable, the recorder for the trace's live target
|
||||
* @return the emulator
|
||||
*/
|
||||
DebuggerPcodeMachine<?> create(PluginTool tool, Trace trace, long snap,
|
||||
TraceRecorder recorder);
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.emulation;
|
||||
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.pcode.emu.PcodeMachine;
|
||||
import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory;
|
||||
import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator;
|
||||
import ghidra.pcode.exec.trace.TracePcodeMachine;
|
||||
|
||||
/**
|
||||
* A Debugger-integrated emulator (or p-code machine)
|
||||
*
|
||||
* <p>
|
||||
* This is a "mix in" interface. It is part of the SPI, but not the API. That is, emulator
|
||||
* developers should use this interface, but emulator clients should not. Clients should use
|
||||
* {@link PcodeMachine} instead. A common implementation is an emulator with concrete plus some
|
||||
* auxiliary state. To realize such a machine, please see {@link AuxDebuggerPcodeEmulator} and
|
||||
* {@link AuxDebuggerEmulatorPartsFactory}.
|
||||
*
|
||||
* @param <T> the type of values in the machine's memory and registers
|
||||
*/
|
||||
public interface DebuggerPcodeMachine<T> extends TracePcodeMachine<T> {
|
||||
/**
|
||||
* Get the tool where this emulator is integrated
|
||||
*
|
||||
* @return the tool
|
||||
*/
|
||||
PluginTool getTool();
|
||||
|
||||
/**
|
||||
* Get the trace's recorder for its live target, if applicable
|
||||
*
|
||||
* @return the recorder, or null
|
||||
*/
|
||||
TraceRecorder getRecorder();
|
||||
}
|
|
@ -15,123 +15,29 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.service.emulation;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import ghidra.app.services.DebuggerStaticMappingService;
|
||||
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.MathUtilities;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class ReadsTargetMemoryPcodeExecutorState
|
||||
extends AbstractReadsTargetPcodeExecutorState {
|
||||
|
||||
protected class ReadsTargetMemoryCachedSpace extends AbstractReadsTargetCachedSpace {
|
||||
|
||||
public ReadsTargetMemoryCachedSpace(Language language, AddressSpace space,
|
||||
TraceMemorySpace backing, long snap) {
|
||||
super(language, space, backing, snap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillUninitialized(AddressSet uninitialized) {
|
||||
AddressSet unknown;
|
||||
unknown = computeUnknown(uninitialized);
|
||||
if (unknown.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (fillUnknownWithRecorder(unknown)) {
|
||||
unknown = computeUnknown(uninitialized);
|
||||
if (unknown.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (fillUnknownWithStaticImages(unknown)) {
|
||||
unknown = computeUnknown(uninitialized);
|
||||
if (unknown.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean fillUnknownWithRecorder(AddressSet unknown) {
|
||||
if (!isLive()) {
|
||||
return false;
|
||||
}
|
||||
waitTimeout(recorder.readMemoryBlocks(unknown, TaskMonitor.DUMMY, false));
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean fillUnknownWithStaticImages(AddressSet unknown) {
|
||||
boolean result = false;
|
||||
// TODO: Expand to block? DON'T OVERWRITE KNOWN!
|
||||
DebuggerStaticMappingService mappingService =
|
||||
tool.getService(DebuggerStaticMappingService.class);
|
||||
byte[] data = new byte[4096];
|
||||
for (Entry<Program, Collection<MappedAddressRange>> ent : mappingService
|
||||
.getOpenMappedViews(trace, unknown, snap)
|
||||
.entrySet()) {
|
||||
Program program = ent.getKey();
|
||||
Memory memory = program.getMemory();
|
||||
AddressSetView initialized = memory.getLoadedAndInitializedAddressSet();
|
||||
|
||||
Collection<MappedAddressRange> mappedSet = ent.getValue();
|
||||
for (MappedAddressRange mappedRng : mappedSet) {
|
||||
AddressRange srng = mappedRng.getSourceAddressRange();
|
||||
long shift = mappedRng.getShift();
|
||||
for (AddressRange subsrng : initialized.intersectRange(srng.getMinAddress(),
|
||||
srng.getMaxAddress())) {
|
||||
Msg.debug(this,
|
||||
"Filling in unknown trace memory in emulator using mapped image: " +
|
||||
program + ": " + subsrng);
|
||||
long lower = subsrng.getMinAddress().getOffset();
|
||||
long fullLen = subsrng.getLength();
|
||||
while (fullLen > 0) {
|
||||
int len = MathUtilities.unsignedMin(data.length, fullLen);
|
||||
try {
|
||||
int read =
|
||||
memory.getBytes(space.getAddress(lower), data, 0, len);
|
||||
if (read < len) {
|
||||
Msg.warn(this,
|
||||
" Partial read of " + subsrng + ". Got " + read +
|
||||
" bytes");
|
||||
}
|
||||
// write(lower - shift, data, 0 ,read);
|
||||
bytes.putData(lower - shift, data, 0, read);
|
||||
}
|
||||
catch (MemoryAccessException | AddressOutOfBoundsException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
lower += len;
|
||||
fullLen -= len;
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A state composing a single {@link ReadsTargetMemoryPcodeExecutorStatePiece}
|
||||
*/
|
||||
public class ReadsTargetMemoryPcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
|
||||
/**
|
||||
* Create the state
|
||||
*
|
||||
* @param tool the tool of the emulator
|
||||
* @param trace the trace of the emulator
|
||||
* @param snap the snap of the emulator
|
||||
* @param thread probably null, since this the shared part
|
||||
* @param frame probably 0, because frame only matters for non-null thread
|
||||
* @param recorder the recorder of the emulator
|
||||
*/
|
||||
public ReadsTargetMemoryPcodeExecutorState(PluginTool tool, Trace trace, long snap,
|
||||
TraceThread thread, int frame, TraceRecorder recorder) {
|
||||
super(tool, trace, snap, thread, frame, recorder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
|
||||
TraceMemorySpace tms) {
|
||||
return new ReadsTargetMemoryCachedSpace(language, s, tms, snap);
|
||||
super(new ReadsTargetMemoryPcodeExecutorStatePiece(tool, trace, snap, thread, frame,
|
||||
recorder));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.emulation;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.services.DebuggerStaticMappingService;
|
||||
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.MathUtilities;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* An executor state piece that knows to read live memory if applicable
|
||||
*
|
||||
* <p>
|
||||
* This takes a handle to the trace's recorder, if applicable, and will check if the source snap is
|
||||
* the recorder's snap. If so, it will direct the recorder to capture the block(s) containing the
|
||||
* read, if they're not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state
|
||||
* will wait up to 1 second (see
|
||||
* {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}).
|
||||
*
|
||||
* <p>
|
||||
* This state will also attempt to fill unknown bytes with values from mapped static images. The
|
||||
* order to retrieve state is:
|
||||
* <ol>
|
||||
* <li>The cache, i.e., this state object</li>
|
||||
* <li>The trace</li>
|
||||
* <li>The live target, if applicable</li>
|
||||
* <li>Mapped static images, if available</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>
|
||||
* If all those defer, the state is read as if filled with 0s.
|
||||
*/
|
||||
public class ReadsTargetMemoryPcodeExecutorStatePiece
|
||||
extends AbstractReadsTargetPcodeExecutorStatePiece {
|
||||
|
||||
/**
|
||||
* A space, corresponding to a memory space, of this state
|
||||
*
|
||||
* <p>
|
||||
* All of the actual read logic is contained here. We override the space map factory so that it
|
||||
* creates these spaces.
|
||||
*/
|
||||
protected class ReadsTargetMemoryCachedSpace extends AbstractReadsTargetCachedSpace {
|
||||
|
||||
public ReadsTargetMemoryCachedSpace(Language language, AddressSpace space,
|
||||
TraceMemorySpace backing, long snap) {
|
||||
super(language, space, backing, snap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillUninitialized(AddressSet uninitialized) {
|
||||
AddressSet unknown;
|
||||
unknown = computeUnknown(uninitialized);
|
||||
if (unknown.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (fillUnknownWithRecorder(unknown)) {
|
||||
unknown = computeUnknown(uninitialized);
|
||||
if (unknown.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (fillUnknownWithStaticImages(unknown)) {
|
||||
unknown = computeUnknown(uninitialized);
|
||||
if (unknown.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean fillUnknownWithRecorder(AddressSet unknown) {
|
||||
if (!isLive()) {
|
||||
return false;
|
||||
}
|
||||
waitTimeout(recorder.readMemoryBlocks(unknown, TaskMonitor.DUMMY, false));
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean fillUnknownWithStaticImages(AddressSet unknown) {
|
||||
boolean result = false;
|
||||
// TODO: Expand to block? DON'T OVERWRITE KNOWN!
|
||||
DebuggerStaticMappingService mappingService =
|
||||
tool.getService(DebuggerStaticMappingService.class);
|
||||
byte[] data = new byte[4096];
|
||||
for (Entry<Program, Collection<MappedAddressRange>> ent : mappingService
|
||||
.getOpenMappedViews(trace, unknown, snap)
|
||||
.entrySet()) {
|
||||
Program program = ent.getKey();
|
||||
Memory memory = program.getMemory();
|
||||
AddressSetView initialized = memory.getLoadedAndInitializedAddressSet();
|
||||
|
||||
Collection<MappedAddressRange> mappedSet = ent.getValue();
|
||||
for (MappedAddressRange mappedRng : mappedSet) {
|
||||
AddressRange drng = mappedRng.getDestinationAddressRange();
|
||||
long shift = mappedRng.getShift();
|
||||
for (AddressRange subdrng : initialized.intersectRange(drng.getMinAddress(),
|
||||
drng.getMaxAddress())) {
|
||||
Msg.debug(this,
|
||||
"Filling in unknown trace memory in emulator using mapped image: " +
|
||||
program + ": " + subdrng);
|
||||
long lower = subdrng.getMinAddress().getOffset();
|
||||
long fullLen = subdrng.getLength();
|
||||
while (fullLen > 0) {
|
||||
int len = MathUtilities.unsignedMin(data.length, fullLen);
|
||||
try {
|
||||
int read =
|
||||
memory.getBytes(space.getAddress(lower), data, 0, len);
|
||||
if (read < len) {
|
||||
Msg.warn(this,
|
||||
" Partial read of " + subdrng + ". Got " + read +
|
||||
" bytes");
|
||||
}
|
||||
// write(lower - shift, data, 0 ,read);
|
||||
bytes.putData(lower - shift, data, 0, read);
|
||||
}
|
||||
catch (MemoryAccessException | AddressOutOfBoundsException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
lower += len;
|
||||
fullLen -= len;
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public ReadsTargetMemoryPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap,
|
||||
TraceThread thread, int frame, TraceRecorder recorder) {
|
||||
super(tool, trace, snap, thread, frame, recorder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
|
||||
return new TargetBackedSpaceMap() {
|
||||
@Override
|
||||
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
|
||||
return new ReadsTargetMemoryCachedSpace(language, space, backing, snap);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -15,63 +15,29 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.service.emulation;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class ReadsTargetRegistersPcodeExecutorState
|
||||
extends AbstractReadsTargetPcodeExecutorState {
|
||||
|
||||
protected class ReadsTargetRegistersCachedSpace extends AbstractReadsTargetCachedSpace {
|
||||
|
||||
public ReadsTargetRegistersCachedSpace(Language language, AddressSpace space,
|
||||
TraceMemorySpace source, long snap) {
|
||||
super(language, space, source, snap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillUninitialized(AddressSet uninitialized) {
|
||||
if (!isLive()) {
|
||||
return;
|
||||
}
|
||||
AddressSet unknown = computeUnknown(uninitialized);
|
||||
Set<Register> toRead = new HashSet<>();
|
||||
for (AddressRange rng : unknown) {
|
||||
Register register =
|
||||
language.getRegister(rng.getMinAddress(), (int) rng.getLength());
|
||||
if (register == null) {
|
||||
Msg.error(this, "Could not figure register for " + rng);
|
||||
}
|
||||
else if (!recorder.getRegisterMapper(thread)
|
||||
.getRegistersOnTarget()
|
||||
.contains(register)) {
|
||||
Msg.warn(this, "Register not recognized by target: " + register);
|
||||
}
|
||||
else {
|
||||
toRead.add(register);
|
||||
}
|
||||
}
|
||||
waitTimeout(recorder.captureThreadRegisters(thread, 0, toRead));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A state composing a single {@link ReadsTargetRegistersPcodeExecutorStatePiece}
|
||||
*/
|
||||
public class ReadsTargetRegistersPcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
|
||||
/**
|
||||
* Create the state
|
||||
*
|
||||
* @param tool the tool of the emulator
|
||||
* @param trace the trace of the emulator
|
||||
* @param snap the snap of the emulator
|
||||
* @param thread the thread to which the state is assigned
|
||||
* @param frame the frame to which the state is assigned, probably 0
|
||||
* @param recorder the recorder of the emulator
|
||||
*/
|
||||
public ReadsTargetRegistersPcodeExecutorState(PluginTool tool, Trace trace, long snap,
|
||||
TraceThread thread, int frame, TraceRecorder recorder) {
|
||||
super(tool, trace, snap, thread, frame, recorder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
|
||||
TraceMemorySpace tms) {
|
||||
return new ReadsTargetRegistersCachedSpace(language, s, tms, snap);
|
||||
super(new ReadsTargetRegistersPcodeExecutorStatePiece(tool, trace, snap, thread, frame,
|
||||
recorder));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.service.emulation;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* An executor state piece that knows to read live memory if applicable
|
||||
*
|
||||
* <p>
|
||||
* This takes a handle to the trace's recorder, if applicable, and will check if the source snap is
|
||||
* the recorder's snap. If so, it will direct the recorder to capture the register to be read, if
|
||||
* it's not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state will wait up to
|
||||
* 1 second (see {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}).
|
||||
*
|
||||
* <ol>
|
||||
* <li>The cache, i.e., this state object</li>
|
||||
* <li>The trace</li>
|
||||
* <li>The live target, if applicable</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>
|
||||
* If all those defer, the state is read as if filled with 0s.
|
||||
*/
|
||||
public class ReadsTargetRegistersPcodeExecutorStatePiece
|
||||
extends AbstractReadsTargetPcodeExecutorStatePiece {
|
||||
|
||||
/**
|
||||
* A space, corresponding to a register space (really a thread) of this state
|
||||
*
|
||||
* <p>
|
||||
* All of the actual read logic is contained here. We override the space map factory so that it
|
||||
* creates these spaces.
|
||||
*/
|
||||
protected class ReadsTargetRegistersCachedSpace extends AbstractReadsTargetCachedSpace {
|
||||
|
||||
public ReadsTargetRegistersCachedSpace(Language language, AddressSpace space,
|
||||
TraceMemorySpace source, long snap) {
|
||||
super(language, space, source, snap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillUninitialized(AddressSet uninitialized) {
|
||||
if (!isLive()) {
|
||||
return;
|
||||
}
|
||||
AddressSet unknown = computeUnknown(uninitialized);
|
||||
Set<Register> toRead = new HashSet<>();
|
||||
for (AddressRange rng : unknown) {
|
||||
Register register =
|
||||
language.getRegister(rng.getMinAddress(), (int) rng.getLength());
|
||||
if (register == null) {
|
||||
Msg.error(this, "Could not figure register for " + rng);
|
||||
}
|
||||
else if (!recorder.getRegisterMapper(thread)
|
||||
.getRegistersOnTarget()
|
||||
.contains(register)) {
|
||||
Msg.warn(this, "Register not recognized by target: " + register);
|
||||
}
|
||||
else {
|
||||
toRead.add(register);
|
||||
}
|
||||
}
|
||||
waitTimeout(recorder.captureThreadRegisters(thread, 0, toRead));
|
||||
}
|
||||
}
|
||||
|
||||
public ReadsTargetRegistersPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap,
|
||||
TraceThread thread, int frame, TraceRecorder recorder) {
|
||||
super(tool, trace, snap, thread, frame, recorder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
|
||||
return new TargetBackedSpaceMap() {
|
||||
@Override
|
||||
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
|
||||
return new ReadsTargetRegistersCachedSpace(language, space, backing, snap);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -208,8 +208,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
protected final ChangeListener classChangeListener = new ChangeListenerForFactoryInstances();
|
||||
protected final ListenerOnRecorders listenerOnRecorders = new ListenerOnRecorders();
|
||||
|
||||
protected final DebuggerSelectMappingOfferDialog offerDialog =
|
||||
new DebuggerSelectMappingOfferDialog();
|
||||
protected final DebuggerSelectMappingOfferDialog offerDialog;
|
||||
protected final DebuggerConnectDialog connectDialog = new DebuggerConnectDialog();
|
||||
|
||||
DockingAction actionDisconnectAll;
|
||||
|
@ -218,7 +217,7 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
|
||||
public DebuggerModelServicePlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
|
||||
offerDialog = new DebuggerSelectMappingOfferDialog(tool);
|
||||
ClassSearcher.addChangeListener(classChangeListener);
|
||||
refreshFactoryInstances();
|
||||
connectDialog.setModelService(this);
|
||||
|
|
|
@ -29,6 +29,7 @@ import docking.widgets.table.ColumnSortState.SortDirection;
|
|||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOffer;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.util.DefaultLanguageService;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
|
@ -126,8 +127,8 @@ public class DebuggerSelectMappingOfferDialog extends DialogComponentProvider {
|
|||
public static class OfferTableModel
|
||||
extends DefaultEnumeratedColumnTableModel<OfferTableColumns, DebuggerMappingOffer> {
|
||||
|
||||
public OfferTableModel() {
|
||||
super("Offers", OfferTableColumns.class);
|
||||
public OfferTableModel(PluginTool tool) {
|
||||
super(tool, "Offers", OfferTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -138,14 +139,13 @@ public class DebuggerSelectMappingOfferDialog extends DialogComponentProvider {
|
|||
}
|
||||
|
||||
public static class OfferPanel extends JPanel {
|
||||
private final OfferTableModel offerTableModel = new OfferTableModel();
|
||||
private final GhidraTable offerTable = new GhidraTable(offerTableModel);
|
||||
private final GhidraTableFilterPanel<DebuggerMappingOffer> offerTableFilterPanel =
|
||||
new GhidraTableFilterPanel<>(offerTable, offerTableModel);
|
||||
private final OfferTableModel offerTableModel;
|
||||
private final GhidraTable offerTable;
|
||||
private final GhidraTableFilterPanel<DebuggerMappingOffer> offerTableFilterPanel;
|
||||
private final JLabel descLabel = new JLabel();
|
||||
private final JCheckBox overrideCheckBox = new JCheckBox("Show Only Recommended Offers");
|
||||
|
||||
private final JScrollPane scrollPane = new JScrollPane(offerTable) {
|
||||
private final JScrollPane scrollPane = new JScrollPane() {
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
Dimension pref = super.getPreferredSize();
|
||||
|
@ -170,7 +170,12 @@ public class DebuggerSelectMappingOfferDialog extends DialogComponentProvider {
|
|||
private LanguageID preferredLangID;
|
||||
private CompilerSpecID preferredCsID;
|
||||
|
||||
{
|
||||
protected OfferPanel(PluginTool tool) {
|
||||
offerTableModel = new OfferTableModel(tool);
|
||||
offerTable = new GhidraTable(offerTableModel);
|
||||
offerTableFilterPanel = new GhidraTableFilterPanel<>(offerTable, offerTableModel);
|
||||
scrollPane.setViewportView(offerTable);
|
||||
|
||||
JPanel descPanel = new JPanel(new BorderLayout());
|
||||
descPanel.setBorder(BorderFactory.createTitledBorder("Description"));
|
||||
descPanel.add(descLabel, BorderLayout.CENTER);
|
||||
|
@ -255,13 +260,14 @@ public class DebuggerSelectMappingOfferDialog extends DialogComponentProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private final OfferPanel offerPanel = new OfferPanel();
|
||||
private final OfferPanel offerPanel;
|
||||
|
||||
private boolean isCancelled = false;
|
||||
|
||||
protected DebuggerSelectMappingOfferDialog() {
|
||||
protected DebuggerSelectMappingOfferDialog(PluginTool tool) {
|
||||
super(DebuggerResources.AbstractRecordAction.NAME, true, false, true, false);
|
||||
|
||||
offerPanel = new OfferPanel(tool);
|
||||
populateComponents();
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableCo
|
|||
import docking.widgets.table.RowWrappedEnumeratedColumnTableModel;
|
||||
import ghidra.async.AsyncDebouncer;
|
||||
import ghidra.async.AsyncTimer;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
public class DebouncedRowWrappedEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTableColumn<C, R>, K, R, T>
|
||||
|
@ -28,9 +29,10 @@ public class DebouncedRowWrappedEnumeratedColumnTableModel<C extends Enum<C> & E
|
|||
|
||||
AsyncDebouncer<Void> debouncer = new AsyncDebouncer<Void>(AsyncTimer.DEFAULT_TIMER, 100);
|
||||
|
||||
public DebouncedRowWrappedEnumeratedColumnTableModel(String name, Class<C> colType,
|
||||
public DebouncedRowWrappedEnumeratedColumnTableModel(PluginTool tool, String name,
|
||||
Class<C> colType,
|
||||
Function<T, K> keyFunc, Function<T, R> wrapper) {
|
||||
super(name, colType, keyFunc, wrapper);
|
||||
super(tool, name, colType, keyFunc, wrapper);
|
||||
|
||||
debouncer.addListener(this::settled);
|
||||
}
|
||||
|
|
|
@ -15,19 +15,59 @@
|
|||
*/
|
||||
package ghidra.app.services;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.*;
|
||||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A service for accessing managed emulators.
|
||||
*
|
||||
* <p>
|
||||
* Managed emulators are employed by the UI and trace manager to perform emulation requested by the
|
||||
* user. Scripts may interact with these managed emulators, or they may instantiate their own
|
||||
* unmanaged emulators, without using this service.
|
||||
*/
|
||||
@ServiceInfo(defaultProvider = DebuggerEmulationServicePlugin.class)
|
||||
public interface DebuggerEmulationService {
|
||||
|
||||
/**
|
||||
* Get the available emulator factories
|
||||
*
|
||||
* @return the collection of factories
|
||||
*/
|
||||
Collection<DebuggerPcodeEmulatorFactory> getEmulatorFactories();
|
||||
|
||||
/**
|
||||
* Set the current emulator factory
|
||||
*
|
||||
* <p>
|
||||
* TODO: Should this be set on a per-program, per-trace basis? Need to decide what is saved to
|
||||
* the tool and what is saved to the program/trace. My inclination is to save current factory to
|
||||
* the tool, but the config options for each factory to the program/trace.
|
||||
*
|
||||
* <p>
|
||||
* TODO: Should there be some opinion service for choosing default configs? Seem overly
|
||||
* complicated for what it offers. For now, we won't save anything, we'll default to the
|
||||
* (built-in) {@link BytesDebuggerPcodeEmulatorFactory}, and we won't have configuration
|
||||
* options.
|
||||
*
|
||||
* @param factory the chosen factory
|
||||
*/
|
||||
void setEmulatorFactory(DebuggerPcodeEmulatorFactory factory);
|
||||
|
||||
/**
|
||||
* Get the current emulator factory
|
||||
*
|
||||
* @return the factory
|
||||
*/
|
||||
DebuggerPcodeEmulatorFactory getEmulatorFactory();
|
||||
|
||||
/**
|
||||
* Perform emulation to realize the machine state of the given time coordinates
|
||||
*
|
||||
|
@ -81,5 +121,5 @@ public interface DebuggerEmulationService {
|
|||
* @param time the time coordinates, including initial snap, steps, and p-code steps
|
||||
* @return the copied p-code frame
|
||||
*/
|
||||
DebuggerTracePcodeEmulator getCachedEmulator(Trace trace, TraceSchedule time);
|
||||
DebuggerPcodeMachine<?> getCachedEmulator(Trace trace, TraceSchedule time);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
|
||||
|
@ -36,12 +37,19 @@ import ghidra.program.model.pcode.Varnode;
|
|||
* until the computation has been performed -- assuming the requested variable actually depends on
|
||||
* that computation.
|
||||
*
|
||||
* <p>
|
||||
* TODO: Deprecate this? It's clever, but it'd probably be easier to just use a synchronous executor
|
||||
* on a separate thread. The necessity of {@link #stepAsync(PcodeFrame, PcodeUseropLibrary)}, etc.,
|
||||
* indicates a failure of the interface to encapsulate this use case. We can adjust the interface,
|
||||
* which would probably not end well, or we can continue to allow the CompletableFuture-specific
|
||||
* steppers to leak out, or we can just torch this and use another thread.
|
||||
*
|
||||
* @param <T> the type of values in the state
|
||||
*/
|
||||
public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
|
||||
public AsyncPcodeExecutor(SleighLanguage language,
|
||||
PcodeArithmetic<CompletableFuture<T>> arithmetic,
|
||||
PcodeExecutorStatePiece<CompletableFuture<T>, CompletableFuture<T>> state) {
|
||||
PcodeExecutorState<CompletableFuture<T>> state) {
|
||||
super(language, arithmetic, state);
|
||||
}
|
||||
|
||||
|
@ -73,7 +81,7 @@ public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
|
|||
Varnode condVar = op.getInput(1);
|
||||
CompletableFuture<T> cond = state.getVar(condVar);
|
||||
return cond.thenAccept(c -> {
|
||||
if (arithmetic.isTrue(cond)) {
|
||||
if (arithmetic.isTrue(cond, Purpose.CONDITION)) {
|
||||
executeBranch(op, frame);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -15,21 +15,23 @@
|
|||
*/
|
||||
package ghidra.pcode.exec;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.pcode.opbehavior.BinaryOpBehavior;
|
||||
import ghidra.pcode.opbehavior.UnaryOpBehavior;
|
||||
import ghidra.program.model.lang.Endian;
|
||||
import ghidra.program.model.lang.Language;
|
||||
|
||||
/**
|
||||
* An arithmetic which can operate on futures of a wrapped type
|
||||
*
|
||||
* @see AsyncPcodeExecutor for comment regarding potential deprecation or immediate removal
|
||||
* @param <T> the type of values wrapped
|
||||
*/
|
||||
public class AsyncWrappedPcodeArithmetic<T> implements PcodeArithmetic<CompletableFuture<T>> {
|
||||
public static final AsyncWrappedPcodeArithmetic<byte[]> BYTES_BE =
|
||||
new AsyncWrappedPcodeArithmetic<>(BytesPcodeArithmetic.BIG_ENDIAN);
|
||||
public static final AsyncWrappedPcodeArithmetic<byte[]> BYTES_LE =
|
||||
new AsyncWrappedPcodeArithmetic<>(BytesPcodeArithmetic.LITTLE_ENDIAN);
|
||||
@Deprecated(forRemoval = true) // TODO: Not getting used
|
||||
public static final AsyncWrappedPcodeArithmetic<BigInteger> BIGINT =
|
||||
new AsyncWrappedPcodeArithmetic<>(BigIntegerPcodeArithmetic.INSTANCE);
|
||||
|
||||
public static AsyncWrappedPcodeArithmetic<byte[]> forEndian(boolean isBigEndian) {
|
||||
return isBigEndian ? BYTES_BE : BYTES_LE;
|
||||
|
@ -46,46 +48,71 @@ public class AsyncWrappedPcodeArithmetic<T> implements PcodeArithmetic<Completab
|
|||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<T> unaryOp(UnaryOpBehavior op, int sizeout, int sizein1,
|
||||
CompletableFuture<T> in1) {
|
||||
return in1.thenApply(t1 -> arithmetic.unaryOp(op, sizeout, sizein1, t1));
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AsyncWrappedPcodeArithmetic<?> that = (AsyncWrappedPcodeArithmetic<?>) obj;
|
||||
return Objects.equals(this.arithmetic, that.arithmetic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<T> binaryOp(BinaryOpBehavior op, int sizeout, int sizein1,
|
||||
public Endian getEndian() {
|
||||
return arithmetic.getEndian();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<T> unaryOp(int opcode, int sizeout, int sizein1,
|
||||
CompletableFuture<T> in1) {
|
||||
return in1.thenApply(t1 -> arithmetic.unaryOp(opcode, sizeout, sizein1, t1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<T> binaryOp(int opcode, int sizeout, int sizein1,
|
||||
CompletableFuture<T> in1, int sizein2, CompletableFuture<T> in2) {
|
||||
return in1.thenCombine(in2,
|
||||
(t1, t2) -> arithmetic.binaryOp(op, sizeout, sizein1, t1, sizein2, t2));
|
||||
(t1, t2) -> arithmetic.binaryOp(opcode, sizeout, sizein1, t1, sizein2, t2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<T> fromConst(long value, int size) {
|
||||
return CompletableFuture.completedFuture(arithmetic.fromConst(value, size));
|
||||
public CompletableFuture<T> modBeforeStore(int sizeout, int sizeinAddress,
|
||||
CompletableFuture<T> inAddress, int sizeinValue, CompletableFuture<T> inValue) {
|
||||
return inValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<T> fromConst(BigInteger value, int size, boolean isContextreg) {
|
||||
return CompletableFuture.completedFuture(arithmetic.fromConst(value, size, isContextreg));
|
||||
public CompletableFuture<T> modAfterLoad(int sizeout, int sizeinAddress,
|
||||
CompletableFuture<T> inAddress, int sizeinValue, CompletableFuture<T> inValue) {
|
||||
return inValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrue(CompletableFuture<T> cond) {
|
||||
if (!cond.isDone()) {
|
||||
throw new AssertionError("You need a better 8-ball");
|
||||
public CompletableFuture<T> fromConst(byte[] value) {
|
||||
return CompletableFuture.completedFuture(arithmetic.fromConst(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toConcrete(CompletableFuture<T> value, Purpose purpose) {
|
||||
if (!value.isDone()) {
|
||||
throw new ConcretionError("You need a better 8-ball", purpose);
|
||||
}
|
||||
return arithmetic.isTrue(cond.getNow(null));
|
||||
return arithmetic.toConcrete(value.getNow(null), purpose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger toConcrete(CompletableFuture<T> cond, boolean isContextreg) {
|
||||
if (!cond.isDone()) {
|
||||
throw new AssertionError("You need a better 8-ball");
|
||||
public long sizeOf(CompletableFuture<T> value) {
|
||||
if (!value.isDone()) {
|
||||
// TODO: Make a class which has future and expected size?
|
||||
throw new RuntimeException("You need a better 8-ball");
|
||||
}
|
||||
return arithmetic.toConcrete(cond.getNow(null), isContextreg);
|
||||
return arithmetic.sizeOf(value.getNow(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<T> sizeOf(CompletableFuture<T> value) {
|
||||
return value.thenApply(v -> arithmetic.sizeOf(v));
|
||||
public CompletableFuture<T> sizeOfAbstract(CompletableFuture<T> value) {
|
||||
return value.thenApply(v -> arithmetic.sizeOfAbstract(v));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,14 @@ package ghidra.pcode.exec;
|
|||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class AsyncWrappedPcodeExecutorState<T> extends AsyncWrappedPcodeExecutorStatePiece<T, T>
|
||||
implements PcodeExecutorState<CompletableFuture<T>> {
|
||||
public AsyncWrappedPcodeExecutorState(PcodeExecutorStatePiece<T, T> state) {
|
||||
super(state);
|
||||
/**
|
||||
* The state for a {@link AsyncWrappedPcodeExecutorStatePiece}
|
||||
*
|
||||
* @param <T> the type of wrapped values
|
||||
*/
|
||||
public class AsyncWrappedPcodeExecutorState<T>
|
||||
extends DefaultPcodeExecutorState<CompletableFuture<T>> {
|
||||
public AsyncWrappedPcodeExecutorState(PcodeExecutorStatePiece<T, T> piece) {
|
||||
super(new AsyncWrappedPcodeExecutorStatePiece<>(piece));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,17 +19,38 @@ import java.util.concurrent.CompletableFuture;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
|
||||
/**
|
||||
* An executor state piece which can operate on futures of a wrapped type
|
||||
*
|
||||
* @see AsyncPcodeExecutor for comment regarding potential deprecation or immediate removal
|
||||
* @param <T> the type of values wrapped
|
||||
*/
|
||||
public class AsyncWrappedPcodeExecutorStatePiece<A, T>
|
||||
implements PcodeExecutorStatePiece<CompletableFuture<A>, CompletableFuture<T>> {
|
||||
protected final PcodeExecutorStatePiece<A, T> state;
|
||||
protected final AsyncWrappedPcodeArithmetic<A> addressArithmetic;
|
||||
protected final AsyncWrappedPcodeArithmetic<T> arithmetic;
|
||||
private CompletableFuture<?> lastWrite = AsyncUtils.NIL;
|
||||
|
||||
public AsyncWrappedPcodeExecutorStatePiece(PcodeExecutorStatePiece<A, T> state) {
|
||||
this.state = state;
|
||||
this.addressArithmetic = new AsyncWrappedPcodeArithmetic<>(state.getAddressArithmetic());
|
||||
this.arithmetic = new AsyncWrappedPcodeArithmetic<>(state.getArithmetic());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncWrappedPcodeArithmetic<A> getAddressArithmetic() {
|
||||
return addressArithmetic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncWrappedPcodeArithmetic<T> getArithmetic() {
|
||||
return arithmetic;
|
||||
}
|
||||
|
||||
protected boolean isWriteDone() {
|
||||
|
@ -45,41 +66,36 @@ public class AsyncWrappedPcodeExecutorStatePiece<A, T>
|
|||
}
|
||||
|
||||
protected CompletableFuture<?> doSetVar(AddressSpace space, CompletableFuture<A> offset,
|
||||
int size, boolean truncateAddressableUnit, CompletableFuture<T> val) {
|
||||
int size, boolean quantize, CompletableFuture<T> val) {
|
||||
return offset.thenCompose(off -> val.thenAccept(v -> {
|
||||
state.setVar(space, off, size, truncateAddressableUnit, v);
|
||||
state.setVar(space, off, size, quantize, v);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVar(AddressSpace space, CompletableFuture<A> offset, int size,
|
||||
boolean truncateAddressableUnit, CompletableFuture<T> val) {
|
||||
nextWrite(() -> doSetVar(space, offset, size, truncateAddressableUnit, val));
|
||||
boolean quantize, CompletableFuture<T> val) {
|
||||
nextWrite(() -> doSetVar(space, offset, size, quantize, val));
|
||||
}
|
||||
|
||||
protected CompletableFuture<T> doGetVar(AddressSpace space, CompletableFuture<A> offset,
|
||||
int size, boolean truncateAddressableUnit) {
|
||||
int size, boolean quantize) {
|
||||
return offset.thenApply(off -> {
|
||||
return state.getVar(space, off, size, truncateAddressableUnit);
|
||||
return state.getVar(space, off, size, quantize);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<T> getVar(AddressSpace space, CompletableFuture<A> offset, int size,
|
||||
boolean truncateAddressableUnit) {
|
||||
return nextRead(() -> doGetVar(space, offset, size, truncateAddressableUnit));
|
||||
boolean quantize) {
|
||||
return nextRead(() -> doGetVar(space, offset, size, quantize));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<A> longToOffset(AddressSpace space, long l) {
|
||||
return CompletableFuture.completedFuture(state.longToOffset(space, l));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemBuffer getConcreteBuffer(Address address) {
|
||||
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
|
||||
if (!isWriteDone()) {
|
||||
throw new AssertionError("An async write is still pending");
|
||||
}
|
||||
return state.getConcreteBuffer(address);
|
||||
return state.getConcreteBuffer(address, purpose);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,22 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState;
|
||||
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
public enum TracePcodeUtils {
|
||||
public enum DebuggerPcodeUtils {
|
||||
;
|
||||
/**
|
||||
* Get an executor which can be used to evaluate Sleigh expressions at the given coordinates,
|
||||
* asynchronously.
|
||||
*
|
||||
* <p>
|
||||
* TODO: Change this to be synchronous and have clients evaluate expressions in another thread?
|
||||
*
|
||||
* @param coordinates the coordinates
|
||||
* @return the executor
|
||||
*/
|
||||
public static AsyncPcodeExecutor<byte[]> executorForCoordinates(
|
||||
DebuggerCoordinates coordinates) {
|
||||
Trace trace = coordinates.getTrace();
|
||||
|
@ -39,7 +49,7 @@ public enum TracePcodeUtils {
|
|||
PcodeExecutorState<CompletableFuture<byte[]>> state;
|
||||
if (coordinates.getRecorder() == null) {
|
||||
state = new AsyncWrappedPcodeExecutorState<>(
|
||||
new TraceBytesPcodeExecutorState(trace, coordinates.getViewSnap(),
|
||||
new DirectBytesTracePcodeExecutorStatePiece(trace, coordinates.getViewSnap(),
|
||||
coordinates.getThread(), coordinates.getFrame()));
|
||||
}
|
||||
else {
|
|
@ -15,128 +15,26 @@
|
|||
*/
|
||||
package ghidra.pcode.exec;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState;
|
||||
import ghidra.pcode.exec.trace.TraceMemoryStatePcodeExecutorStatePiece;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A state composing a single {@link TraceRecorderAsyncPcodeExecutorStatePiece}
|
||||
*/
|
||||
public class TraceRecorderAsyncPcodeExecutorState
|
||||
extends AsyncWrappedPcodeExecutorState<byte[]> {
|
||||
private final TraceRecorder recorder;
|
||||
private final TraceBytesPcodeExecutorState traceState;
|
||||
private final TraceMemoryStatePcodeExecutorStatePiece traceMemState;
|
||||
|
||||
extends DefaultPcodeExecutorState<CompletableFuture<byte[]>> {
|
||||
/**
|
||||
* Create the state
|
||||
*
|
||||
* @param recorder the recorder for the trace's live target
|
||||
* @param snap the user's current snap
|
||||
* @param thread the user's current thread
|
||||
* @param frame the user's current frame
|
||||
*/
|
||||
public TraceRecorderAsyncPcodeExecutorState(TraceRecorder recorder, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
super(new TraceBytesPcodeExecutorState(recorder.getTrace(), snap, thread, frame));
|
||||
this.recorder = recorder;
|
||||
this.traceState = (TraceBytesPcodeExecutorState) state;
|
||||
this.traceMemState =
|
||||
new TraceMemoryStatePcodeExecutorStatePiece(recorder.getTrace(), snap, thread, frame);
|
||||
}
|
||||
|
||||
protected CompletableFuture<?> doSetTargetVar(AddressSpace space, long offset, int size,
|
||||
boolean truncateAddressableUnit, byte[] val) {
|
||||
return recorder.writeVariable(traceState.getThread(), 0, space.getAddress(offset), val);
|
||||
}
|
||||
|
||||
protected byte[] knitFromResults(NavigableMap<Address, byte[]> map, Address addr, int size) {
|
||||
Address floor = map.floorKey(addr);
|
||||
NavigableMap<Address, byte[]> tail;
|
||||
if (floor == null) {
|
||||
tail = map;
|
||||
}
|
||||
else {
|
||||
tail = map.tailMap(floor, true);
|
||||
}
|
||||
byte[] result = new byte[size];
|
||||
for (Map.Entry<Address, byte[]> ent : tail.entrySet()) {
|
||||
long off = ent.getKey().subtract(addr);
|
||||
if (off >= size || off < 0) {
|
||||
break;
|
||||
}
|
||||
int subSize = Math.min(size - (int) off, ent.getValue().length);
|
||||
System.arraycopy(ent.getValue(), 0, result, (int) off, subSize);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected CompletableFuture<byte[]> doGetTargetVar(AddressSpace space, long offset,
|
||||
int size, boolean truncateAddressableUnit) {
|
||||
if (space.isMemorySpace()) {
|
||||
Address addr = space.getAddress(truncateOffset(space, offset));
|
||||
AddressSet set = new AddressSet(addr, space.getAddress(offset + size - 1));
|
||||
CompletableFuture<NavigableMap<Address, byte[]>> future =
|
||||
recorder.readMemoryBlocks(set, TaskMonitor.DUMMY, true);
|
||||
return future.thenApply(map -> {
|
||||
return knitFromResults(map, addr, size);
|
||||
});
|
||||
}
|
||||
assert space.isRegisterSpace();
|
||||
|
||||
Language lang = recorder.getTrace().getBaseLanguage();
|
||||
Register register = lang.getRegister(space, offset, size);
|
||||
if (register == null) {
|
||||
// TODO: Is this too restrictive?
|
||||
throw new IllegalArgumentException(
|
||||
"read from register space must be from one register");
|
||||
}
|
||||
Register baseRegister = register.getBaseRegister();
|
||||
|
||||
CompletableFuture<Map<Register, RegisterValue>> future =
|
||||
recorder.captureThreadRegisters(traceState.getThread(), traceState.getFrame(),
|
||||
Set.of(baseRegister));
|
||||
return future.thenApply(map -> {
|
||||
RegisterValue baseVal = map.get(baseRegister);
|
||||
if (baseVal == null) {
|
||||
return state.getVar(space, offset, size, truncateAddressableUnit);
|
||||
}
|
||||
BigInteger val = baseVal.getRegisterValue(register).getUnsignedValue();
|
||||
return Utils.bigIntegerToBytes(val, size,
|
||||
recorder.getTrace().getBaseLanguage().isBigEndian());
|
||||
});
|
||||
}
|
||||
|
||||
protected boolean isTargetSpace(AddressSpace space) {
|
||||
return traceState.getSnap() == recorder.getSnap() && !space.isConstantSpace() &&
|
||||
!space.isUniqueSpace();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<?> doSetVar(AddressSpace space,
|
||||
CompletableFuture<byte[]> offset, int size, boolean truncateAddressableUnit,
|
||||
CompletableFuture<byte[]> val) {
|
||||
if (!isTargetSpace(space)) {
|
||||
return super.doSetVar(space, offset, size, truncateAddressableUnit, val);
|
||||
}
|
||||
return offset.thenCompose(off -> val.thenCompose(v -> {
|
||||
return doSetTargetVar(space, traceState.offsetToLong(off), size,
|
||||
truncateAddressableUnit, v);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<byte[]> doGetVar(AddressSpace space,
|
||||
CompletableFuture<byte[]> offset, int size, boolean truncateAddressableUnit) {
|
||||
if (!isTargetSpace(space)) {
|
||||
return super.doGetVar(space, offset, size, truncateAddressableUnit);
|
||||
}
|
||||
return offset.thenCompose(off -> {
|
||||
TraceMemoryState ms = traceMemState.getVar(space, off, size, truncateAddressableUnit);
|
||||
if (ms == TraceMemoryState.KNOWN) {
|
||||
return super.doGetVar(space, offset, size, truncateAddressableUnit);
|
||||
}
|
||||
return doGetTargetVar(space, traceState.offsetToLong(off), size,
|
||||
truncateAddressableUnit);
|
||||
});
|
||||
super(new TraceRecorderAsyncPcodeExecutorStatePiece(recorder, snap, thread, frame));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece;
|
||||
import ghidra.pcode.exec.trace.TraceMemoryStatePcodeExecutorStatePiece;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* An executor state which can asynchronously read and write a live target, if applicable
|
||||
*
|
||||
* <p>
|
||||
* This is used for executing Sleigh code to manipulate trace history or a live target.
|
||||
*
|
||||
* <p>
|
||||
* TODO: It might be easier to re-factor this to operate synchronously, executing Sleigh programs in
|
||||
* a separate thread.
|
||||
*/
|
||||
public class TraceRecorderAsyncPcodeExecutorStatePiece
|
||||
extends AsyncWrappedPcodeExecutorStatePiece<byte[], byte[]> {
|
||||
private final TraceRecorder recorder;
|
||||
private final DirectBytesTracePcodeExecutorStatePiece traceState;
|
||||
private final TraceMemoryStatePcodeExecutorStatePiece traceMemState;
|
||||
|
||||
public TraceRecorderAsyncPcodeExecutorStatePiece(TraceRecorder recorder, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
super(
|
||||
new DirectBytesTracePcodeExecutorStatePiece(recorder.getTrace(), snap, thread, frame));
|
||||
this.recorder = recorder;
|
||||
this.traceState = (DirectBytesTracePcodeExecutorStatePiece) state;
|
||||
this.traceMemState =
|
||||
new TraceMemoryStatePcodeExecutorStatePiece(recorder.getTrace(), snap, thread, frame);
|
||||
}
|
||||
|
||||
protected CompletableFuture<?> doSetTargetVar(AddressSpace space, long offset, int size,
|
||||
boolean quantize, byte[] val) {
|
||||
return recorder.writeVariable(traceState.getThread(), 0, space.getAddress(offset), val);
|
||||
}
|
||||
|
||||
protected byte[] knitFromResults(NavigableMap<Address, byte[]> map, Address addr, int size) {
|
||||
Address floor = map.floorKey(addr);
|
||||
NavigableMap<Address, byte[]> tail;
|
||||
if (floor == null) {
|
||||
tail = map;
|
||||
}
|
||||
else {
|
||||
tail = map.tailMap(floor, true);
|
||||
}
|
||||
byte[] result = new byte[size];
|
||||
for (Map.Entry<Address, byte[]> ent : tail.entrySet()) {
|
||||
long off = ent.getKey().subtract(addr);
|
||||
if (off >= size || off < 0) {
|
||||
break;
|
||||
}
|
||||
int subSize = Math.min(size - (int) off, ent.getValue().length);
|
||||
System.arraycopy(ent.getValue(), 0, result, (int) off, subSize);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected CompletableFuture<byte[]> doGetTargetVar(AddressSpace space, long offset,
|
||||
int size, boolean quantize) {
|
||||
if (space.isMemorySpace()) {
|
||||
Address addr = space.getAddress(quantizeOffset(space, offset));
|
||||
AddressSet set = new AddressSet(addr, space.getAddress(offset + size - 1));
|
||||
CompletableFuture<NavigableMap<Address, byte[]>> future =
|
||||
recorder.readMemoryBlocks(set, TaskMonitor.DUMMY, true);
|
||||
return future.thenApply(map -> {
|
||||
return knitFromResults(map, addr, size);
|
||||
});
|
||||
}
|
||||
assert space.isRegisterSpace();
|
||||
|
||||
Language lang = recorder.getTrace().getBaseLanguage();
|
||||
Register register = lang.getRegister(space, offset, size);
|
||||
if (register == null) {
|
||||
// TODO: Is this too restrictive?
|
||||
throw new IllegalArgumentException(
|
||||
"read from register space must be from one register");
|
||||
}
|
||||
Register baseRegister = register.getBaseRegister();
|
||||
|
||||
CompletableFuture<Map<Register, RegisterValue>> future =
|
||||
recorder.captureThreadRegisters(traceState.getThread(), traceState.getFrame(),
|
||||
Set.of(baseRegister));
|
||||
return future.thenApply(map -> {
|
||||
RegisterValue baseVal = map.get(baseRegister);
|
||||
if (baseVal == null) {
|
||||
return state.getVar(space, offset, size, quantize);
|
||||
}
|
||||
BigInteger val = baseVal.getRegisterValue(register).getUnsignedValue();
|
||||
return Utils.bigIntegerToBytes(val, size,
|
||||
recorder.getTrace().getBaseLanguage().isBigEndian());
|
||||
});
|
||||
}
|
||||
|
||||
protected boolean isTargetSpace(AddressSpace space) {
|
||||
return traceState.getSnap() == recorder.getSnap() && !space.isConstantSpace() &&
|
||||
!space.isUniqueSpace();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<?> doSetVar(AddressSpace space,
|
||||
CompletableFuture<byte[]> offset, int size, boolean quantize,
|
||||
CompletableFuture<byte[]> val) {
|
||||
if (!isTargetSpace(space)) {
|
||||
return super.doSetVar(space, offset, size, quantize, val);
|
||||
}
|
||||
return offset.thenCompose(off -> val.thenCompose(v -> {
|
||||
long lOff = traceState.getAddressArithmetic().toLong(off, Purpose.STORE);
|
||||
return doSetTargetVar(space, lOff, size, quantize, v);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<byte[]> doGetVar(AddressSpace space,
|
||||
CompletableFuture<byte[]> offset, int size, boolean quantize) {
|
||||
if (!isTargetSpace(space)) {
|
||||
return super.doGetVar(space, offset, size, quantize);
|
||||
}
|
||||
return offset.thenCompose(off -> {
|
||||
TraceMemoryState ms = traceMemState.getVar(space, off, size, quantize);
|
||||
if (ms == TraceMemoryState.KNOWN) {
|
||||
return super.doGetVar(space, offset, size, quantize);
|
||||
}
|
||||
long lOff = traceState.getAddressArithmetic().toLong(off, Purpose.LOAD);
|
||||
return doGetTargetVar(space, lOff, size, quantize);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.debug.auxiliary;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.emulation.*;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory;
|
||||
import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator;
|
||||
import ghidra.pcode.exec.trace.PairedTracePcodeExecutorStatePiece;
|
||||
import ghidra.pcode.exec.trace.TracePcodeExecutorState;
|
||||
import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory;
|
||||
import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator;
|
||||
|
||||
/**
|
||||
* The most capable auxiliary emulator parts factory
|
||||
*
|
||||
* <p>
|
||||
* This can manufacture parts for an emulator that is fully integrated with the Debugger UI, as well
|
||||
* as all the parts for the less integrated forms of the same emulator. The pattern of use is
|
||||
* generally to implement {@link DebuggerPcodeEmulatorFactory}, allowing the UI to discover and
|
||||
* instantiate the emulator, though they could also be created directly by scripts or plugins.
|
||||
*
|
||||
* <p>
|
||||
* For an example of a fully-integrated solution using this interface, see the Taint Analyzer. Its
|
||||
* project serves as an archetype for similar dynamic analysis employing p-code emulation.
|
||||
*
|
||||
* <p>
|
||||
* We recommend implementors start with the methods declared in {@link AuxEmulatorPartsFactory} with
|
||||
* the aim of creating a derivative of {@link AuxPcodeEmulator}. Note that one Debugger-integrated
|
||||
* emulator parts factory can be used with all three of {@link AuxPcodeEmulator},
|
||||
* {@link AuxTracePcodeEmulator}, {@link AuxTraceEmulatorPartsFactory}. Once the stand-alone
|
||||
* emulator has been tested, proceed to the methods in {@link AuxTraceEmulatorPartsFactory} with the
|
||||
* aim of creating a derivative of {@link AuxTracePcodeEmulator}. Most of the work here is in
|
||||
* factoring the state objects and pieces to reduce code duplication among the stand-alone and
|
||||
* trace-integrated states. Once the trace-integrated emulator is tested, then proceed to the
|
||||
* methods declared here in {@link AuxDebuggerEmulatorPartsFactory} with the aim of creating a
|
||||
* derivative of {@link AuxDebuggerPcodeEmulator}. Again, most of the work is in factoring the
|
||||
* states to avoid code duplication. Once the Debugger-integrated emulator is tested, the final bit
|
||||
* is to implement a {@link DebuggerPcodeEmulatorFactory} so that users can configure and create the
|
||||
* emulator. Other UI pieces, e.g., actions, fields, and table columns, may be needed to facilitate
|
||||
* user access to the emulator's auxiliary state. Furthermore, a userop library for accessing the
|
||||
* auxiliary state is recommended, since Sleigh code can be executed by the user.
|
||||
*
|
||||
* @param <U> the type of auxiliary values
|
||||
*/
|
||||
public interface AuxDebuggerEmulatorPartsFactory<U> extends AuxTraceEmulatorPartsFactory<U> {
|
||||
/**
|
||||
* Create the shared (memory) state of a new Debugger-integrated emulator
|
||||
*
|
||||
* <p>
|
||||
* This state is usually composed of pieces using {@link PairedTracePcodeExecutorStatePiece},
|
||||
* but it does not have to be. It must incorporate the concrete piece provided. The state must
|
||||
* be capable of lazily loading state from a trace, from a live target, and from mapped static
|
||||
* programs. It must also be able to write its cache into the trace at another snapshot. The
|
||||
* given concrete piece is already capable of doing that for concrete values. The auxiliary
|
||||
* piece can, at its discretion, delegate to the concrete piece in order to derive its values.
|
||||
* It should be able to independently load its state from the trace and mapped static program,
|
||||
* since this is one way a user expects to initialize the auxiliary values.
|
||||
*
|
||||
* @param emulator the emulator
|
||||
* @param concrete the concrete piece
|
||||
* @return the composed state
|
||||
*/
|
||||
TracePcodeExecutorState<Pair<byte[], U>> createDebuggerSharedState(
|
||||
AuxDebuggerPcodeEmulator<U> emulator,
|
||||
ReadsTargetMemoryPcodeExecutorStatePiece concrete);
|
||||
|
||||
/**
|
||||
* Create the local (register) state of a new Debugger-integrated thread
|
||||
*
|
||||
* <p>
|
||||
* Like
|
||||
* {@link #createDebuggerSharedState(AuxDebuggerPcodeEmulator, ReadsTargetMemoryPcodeExecutorStatePiece)}
|
||||
* this state must also be capable of lazily loading state from a trace and from a live target.
|
||||
* Static programs can't be mapped into register space, so they do not apply here.
|
||||
*
|
||||
* @param emulator the emulator
|
||||
* @param thread the new thread
|
||||
* @param concrete the concrete piece
|
||||
* @return the composed state
|
||||
*/
|
||||
TracePcodeExecutorState<Pair<byte[], U>> createDebuggerLocalState(
|
||||
AuxDebuggerPcodeEmulator<U> emulator, PcodeThread<Pair<byte[], U>> thread,
|
||||
ReadsTargetRegistersPcodeExecutorStatePiece concrete);
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.debug.auxiliary;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.emulation.*;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory;
|
||||
import ghidra.pcode.exec.trace.TracePcodeExecutorState;
|
||||
import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory;
|
||||
import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
/**
|
||||
* An Debugger-integrated emulator whose parts are manufactured by a
|
||||
* {@link AuxDebuggerEmulatorPartsFactory}
|
||||
*
|
||||
* <p>
|
||||
* See the parts factory interface and its super interfaces:
|
||||
* <ul>
|
||||
* <li>{@link AuxDebuggerEmulatorPartsFactory}</li>
|
||||
* <li>{@link AuxTraceEmulatorPartsFactory}</li>
|
||||
* <li>{@link AuxEmulatorPartsFactory}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <U> the type of auxiliary values
|
||||
*/
|
||||
public abstract class AuxDebuggerPcodeEmulator<U> extends AuxTracePcodeEmulator<U>
|
||||
implements DebuggerPcodeMachine<Pair<byte[], U>> {
|
||||
protected final PluginTool tool;
|
||||
protected final TraceRecorder recorder;
|
||||
|
||||
/**
|
||||
* Create a new emulator
|
||||
*
|
||||
* @param tool the user's tool where the emulator is integrated
|
||||
* @param trace the user's current trace from which the emulator loads state
|
||||
* @param snap the user's current snapshot from which the emulator loads state
|
||||
* @param recorder if applicable, the trace's recorder for its live target
|
||||
*/
|
||||
public AuxDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap,
|
||||
TraceRecorder recorder) {
|
||||
super(trace, snap);
|
||||
this.tool = tool;
|
||||
this.recorder = recorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected abstract AuxDebuggerEmulatorPartsFactory<U> getPartsFactory();
|
||||
|
||||
@Override
|
||||
public PluginTool getTool() {
|
||||
return tool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceRecorder getRecorder() {
|
||||
return recorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorState<Pair<byte[], U>> createSharedState() {
|
||||
return getPartsFactory().createDebuggerSharedState(this,
|
||||
new ReadsTargetMemoryPcodeExecutorStatePiece(tool, trace, snap, null, 0, recorder));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorState<Pair<byte[], U>> createLocalState(
|
||||
PcodeThread<Pair<byte[], U>> thread) {
|
||||
return getPartsFactory().createDebuggerLocalState(this, thread,
|
||||
new ReadsTargetRegistersPcodeExecutorStatePiece(tool, trace, snap,
|
||||
getTraceThread(thread), 0,
|
||||
recorder));
|
||||
}
|
||||
}
|
|
@ -567,7 +567,12 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||
@After
|
||||
public void tearDown() {
|
||||
waitForTasks();
|
||||
runSwing(() -> traceManager.setSaveTracesByDefault(false));
|
||||
runSwing(() -> {
|
||||
if (traceManager == null) {
|
||||
return;
|
||||
}
|
||||
traceManager.setSaveTracesByDefault(false);
|
||||
});
|
||||
|
||||
if (tb != null) {
|
||||
if (traceManager != null && traceManager.getOpenTraces().contains(tb.trace)) {
|
||||
|
|
|
@ -29,7 +29,7 @@ import ghidra.app.plugin.assembler.Assemblers;
|
|||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.pcode.DebuggerPcodeStepperProvider.PcodeRowHtmlFormatter;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerPcodeMachine;
|
||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.app.services.DebuggerEmulationService;
|
||||
|
@ -142,10 +142,10 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg
|
|||
traceManager.activateTime(schedule2);
|
||||
waitForPass(() -> assertEquals(schedule2, pcodeProvider.current.getTime()));
|
||||
|
||||
DebuggerTracePcodeEmulator emu =
|
||||
DebuggerPcodeMachine<?> emu =
|
||||
waitForValue(() -> emuService.getCachedEmulator(tb.trace, schedule2));
|
||||
assertNotNull(emu);
|
||||
PcodeThread<byte[]> et = emu.getThread(thread.getPath(), false);
|
||||
PcodeThread<?> et = emu.getThread(thread.getPath(), false);
|
||||
waitForPass(() -> assertNull(et.getFrame()));
|
||||
|
||||
/**
|
||||
|
@ -171,7 +171,7 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg
|
|||
PcodeProgram prog = SleighProgramCompiler.compileProgram(language, "test", sleigh,
|
||||
PcodeUseropLibrary.nil());
|
||||
PcodeExecutor<byte[]> executor =
|
||||
new PcodeExecutor<>(language, PcodeArithmetic.BYTES_BE, null);
|
||||
new PcodeExecutor<>(language, BytesPcodeArithmetic.BIG_ENDIAN, null);
|
||||
PcodeFrame frame = executor.begin(prog);
|
||||
PcodeRowHtmlFormatter formatter = pcodeProvider.new PcodeRowHtmlFormatter(language, frame);
|
||||
return formatter.getRows();
|
||||
|
|
|
@ -33,7 +33,7 @@ import ghidra.app.services.DebuggerStateEditingService.StateEditor;
|
|||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.dbg.target.TargetRegisterBank;
|
||||
import ghidra.pcode.exec.AsyncPcodeExecutor;
|
||||
import ghidra.pcode.exec.TracePcodeUtils;
|
||||
import ghidra.pcode.exec.DebuggerPcodeUtils;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
|
@ -143,8 +143,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
|
|||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
// NB. TraceManager should automatically activate the first thread
|
||||
TraceThread thread = tb.getOrAddThread("Threads[0]", 0);
|
||||
AsyncPcodeExecutor<byte[]> executor =
|
||||
TracePcodeUtils.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread));
|
||||
AsyncPcodeExecutor<byte[]> executor = DebuggerPcodeUtils
|
||||
.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread));
|
||||
|
||||
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
||||
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
|
||||
|
@ -181,8 +181,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
|
|||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
// NB. TraceManager should automatically activate the first thread
|
||||
thread = tb.getOrAddThread("Threads[0]", 0);
|
||||
AsyncPcodeExecutor<byte[]> executor =
|
||||
TracePcodeUtils.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread));
|
||||
AsyncPcodeExecutor<byte[]> executor = DebuggerPcodeUtils
|
||||
.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread));
|
||||
|
||||
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
||||
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
|
||||
|
|
|
@ -29,6 +29,7 @@ import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
|||
import ghidra.app.services.ActionSource;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.dbg.model.TestTargetRegisterBankInThread;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
|
|
@ -24,8 +24,17 @@ import ghidra.trace.model.Trace;
|
|||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState
|
||||
extends TraceCachedWriteBytesPcodeExecutorState {
|
||||
/**
|
||||
* A state piece which can check for uninitialized reads
|
||||
*
|
||||
* <p>
|
||||
* Depending on the use case, it may be desirable to ensure all reads through the course of
|
||||
* emulation are from initialized parts of memory. For traces, there's an additional consideration
|
||||
* as to whether the values are present, but state. Again, depending on the use case, that may be
|
||||
* acceptable. See the extensions of this class for "stock" implementations.
|
||||
*/
|
||||
public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece
|
||||
extends BytesTracePcodeExecutorStatePiece {
|
||||
|
||||
protected class CheckedCachedSpace extends CachedSpace {
|
||||
public CheckedCachedSpace(Language language, AddressSpace space, TraceMemorySpace source,
|
||||
|
@ -45,16 +54,31 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState
|
|||
}
|
||||
}
|
||||
|
||||
public AbstractCheckedTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap,
|
||||
public AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
super(trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
|
||||
return new CheckedCachedSpace(language, space, backing, snap);
|
||||
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
|
||||
return new TraceBackedSpaceMap() {
|
||||
@Override
|
||||
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
|
||||
return new CheckedCachedSpace(language, space, backing, snap);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide what to do, give that a portion of a read is uninitialized
|
||||
*
|
||||
* @param backing the object backing the address space that was read
|
||||
* @param start the starting address of the requested read
|
||||
* @param size the size of the requested read
|
||||
* @param uninitialized the portion of the read that is uninitialized
|
||||
* @return the adjusted size of the read
|
||||
* @throws Exception to interrupt the emulator
|
||||
*/
|
||||
protected abstract int checkUninitialized(TraceMemorySpace backing, Address start, int size,
|
||||
AddressSet uninitialized);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.pcode.emu.PcodeEmulator;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* An emulator that can read initial state from a trace and record its state back into it
|
||||
*/
|
||||
public class BytesTracePcodeEmulator extends PcodeEmulator implements TracePcodeMachine<byte[]> {
|
||||
protected final Trace trace;
|
||||
protected final long snap;
|
||||
|
||||
/**
|
||||
* Create a trace-bound emulator
|
||||
*
|
||||
* @param trace the trace
|
||||
* @param snap the snap from which it lazily reads its state
|
||||
*/
|
||||
public BytesTracePcodeEmulator(Trace trace, long snap) {
|
||||
super(trace.getBaseLanguage());
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSnap() {
|
||||
return snap;
|
||||
}
|
||||
|
||||
protected TracePcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
return new BytesTracePcodeExecutorState(trace, snap, thread, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorState<byte[]> createSharedState() {
|
||||
return newState(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> thread) {
|
||||
return newState(getTraceThread(thread));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A state composing a single {@link BytesTracePcodeExecutorStatePiece}
|
||||
*/
|
||||
class BytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
|
||||
/**
|
||||
* Create the state
|
||||
*
|
||||
* @param trace the trace from which bytes are loaded
|
||||
* @param snap the snap from which bytes are loaded
|
||||
* @param thread if applicable, the thread identifying the register space
|
||||
* @param frame if applicable, the frame identifying the register space
|
||||
*/
|
||||
public BytesTracePcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
super(new BytesTracePcodeExecutorStatePiece(trace, snap, thread, frame));
|
||||
}
|
||||
}
|
|
@ -20,9 +20,9 @@ import java.nio.ByteBuffer;
|
|||
import com.google.common.collect.*;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import ghidra.pcode.exec.AbstractBytesPcodeExecutorState;
|
||||
import ghidra.pcode.exec.AbstractBytesPcodeExecutorStatePiece;
|
||||
import ghidra.pcode.exec.BytesPcodeExecutorStateSpace;
|
||||
import ghidra.pcode.exec.trace.TraceCachedWriteBytesPcodeExecutorState.CachedSpace;
|
||||
import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece.CachedSpace;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.Language;
|
||||
|
@ -32,22 +32,23 @@ import ghidra.trace.model.thread.TraceThread;
|
|||
import ghidra.util.MathUtilities;
|
||||
|
||||
/**
|
||||
* A state which reads bytes from a trace, but caches writes internally.
|
||||
* A state piece which reads bytes from a trace, but caches writes internally.
|
||||
*
|
||||
* <p>
|
||||
* This provides for "read-only" emulation on a trace. Writes do not affect the source trace, but
|
||||
* rather are cached in this state. If desired, those cached writes can be written back out at a
|
||||
* later time.
|
||||
*/
|
||||
public class TraceCachedWriteBytesPcodeExecutorState
|
||||
extends AbstractBytesPcodeExecutorState<TraceMemorySpace, CachedSpace> {
|
||||
public class BytesTracePcodeExecutorStatePiece
|
||||
extends AbstractBytesPcodeExecutorStatePiece<CachedSpace>
|
||||
implements TracePcodeExecutorStatePiece<byte[], byte[]> {
|
||||
|
||||
protected final Trace trace;
|
||||
protected final long snap;
|
||||
protected final TraceThread thread;
|
||||
protected final int frame;
|
||||
|
||||
public TraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread,
|
||||
public BytesTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
super(trace.getBaseLanguage());
|
||||
this.trace = trace;
|
||||
|
@ -56,7 +57,7 @@ public class TraceCachedWriteBytesPcodeExecutorState
|
|||
this.frame = frame;
|
||||
}
|
||||
|
||||
public static class CachedSpace extends BytesPcodeExecutorStateSpace<TraceMemorySpace> {
|
||||
protected static class CachedSpace extends BytesPcodeExecutorStateSpace<TraceMemorySpace> {
|
||||
protected final RangeSet<UnsignedLong> written = TreeRangeSet.create();
|
||||
protected final long snap;
|
||||
|
||||
|
@ -126,50 +127,69 @@ public class TraceCachedWriteBytesPcodeExecutorState
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the state's source trace
|
||||
*
|
||||
* @return the trace
|
||||
*/
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source snap
|
||||
*
|
||||
* @return the snap
|
||||
*/
|
||||
public long getSnap() {
|
||||
return snap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source thread, if a local state
|
||||
*
|
||||
* @return the thread
|
||||
*/
|
||||
public TraceThread getThread() {
|
||||
return thread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source frame, if a local state
|
||||
*
|
||||
* @return the frame, probably 0
|
||||
*/
|
||||
public int getFrame() {
|
||||
return frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the accumulated writes into the given trace
|
||||
*
|
||||
* <p>
|
||||
* NOTE: This method requires a transaction to have already been started on the destination
|
||||
* trace.
|
||||
*
|
||||
* @param trace the trace to modify
|
||||
* @param snap the snap within the trace
|
||||
* @param thread the thread to take register writes
|
||||
* @param frame the frame for register writes
|
||||
*/
|
||||
public void writeCacheDown(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
@Override
|
||||
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
if (trace.getBaseLanguage() != language) {
|
||||
throw new IllegalArgumentException("Destination trace must be same language as source");
|
||||
}
|
||||
for (CachedSpace cached : spaces.values()) {
|
||||
for (CachedSpace cached : spaceMap.values()) {
|
||||
cached.writeDown(trace, snap, thread, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TraceMemorySpace getBacking(AddressSpace space) {
|
||||
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, false);
|
||||
/**
|
||||
* A space map which binds spaces to corresponding spaces in the trace
|
||||
*/
|
||||
protected class TraceBackedSpaceMap extends CacheingSpaceMap<TraceMemorySpace, CachedSpace> {
|
||||
@Override
|
||||
protected TraceMemorySpace getBacking(AddressSpace space) {
|
||||
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
|
||||
return new CachedSpace(language, space, backing, snap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
|
||||
return new CachedSpace(language, space, backing, snap);
|
||||
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
|
||||
return new TraceBackedSpaceMap();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.pcode.exec.DefaultPcodeExecutorState;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* An adapter that implements {@link TracePcodeExecutorState} given a
|
||||
* {@link TracePcodeExecutorStatePiece} whose address and value types already match
|
||||
*
|
||||
* @param <T> the type of values
|
||||
*/
|
||||
public class DefaultTracePcodeExecutorState<T> extends DefaultPcodeExecutorState<T>
|
||||
implements TracePcodeExecutorState<T> {
|
||||
|
||||
protected final TracePcodeExecutorStatePiece<T, T> piece;
|
||||
|
||||
/**
|
||||
* Wrap a state piece
|
||||
*
|
||||
* @param piece the piece
|
||||
*/
|
||||
public DefaultTracePcodeExecutorState(TracePcodeExecutorStatePiece<T, T> piece) {
|
||||
super(piece);
|
||||
this.piece = piece;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
piece.writeDown(trace, snap, thread, frame);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A state composing a single {@link DirectBytesTracePcodeExecutorStatePiece}
|
||||
*
|
||||
* <p>
|
||||
* Note this does not implement {@link DefaultTracePcodeExecutorState} because it treats the trace
|
||||
* as if it were a stand-alone state. The interface expects implementations to lazily load into a
|
||||
* cache and write it back down later. This does not do that.
|
||||
*
|
||||
* @see TraceSleighUtils
|
||||
*/
|
||||
public class DirectBytesTracePcodeExecutorState extends DefaultPcodeExecutorState<byte[]> {
|
||||
private final Trace trace;
|
||||
private final long snap;
|
||||
private final TraceThread thread;
|
||||
private final int frame;
|
||||
|
||||
/**
|
||||
* Create the state
|
||||
*
|
||||
* @param trace the trace the executor will access
|
||||
* @param snap the snap the executor will access
|
||||
* @param thread the thread for reading and writing registers
|
||||
* @param frame the frame for reading and writing registers
|
||||
*/
|
||||
public DirectBytesTracePcodeExecutorState(Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
super(new DirectBytesTracePcodeExecutorStatePiece(trace, snap, thread, frame));
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
this.thread = thread;
|
||||
this.frame = frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pair this state with an auxiliary {@link TraceMemoryState} piece
|
||||
*
|
||||
* @return the new state, composing this state with the new piece
|
||||
* @see TraceSleighUtils#buildByteWithStateExecutor(Trace, long, TraceThread, int)
|
||||
*/
|
||||
public PcodeExecutorState<Pair<byte[], TraceMemoryState>> withMemoryState() {
|
||||
return new PairedPcodeExecutorState<>(this,
|
||||
new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame));
|
||||
}
|
||||
}
|
|
@ -17,14 +17,14 @@ package ghidra.pcode.exec.trace;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.generic.util.datastruct.SemisparseByteArray;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
|
@ -32,8 +32,20 @@ import ghidra.trace.model.memory.TraceMemoryState;
|
|||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.DefaultTraceTimeViewport;
|
||||
|
||||
public class TraceBytesPcodeExecutorState
|
||||
extends AbstractLongOffsetPcodeExecutorState<byte[], TraceMemorySpace> {
|
||||
/**
|
||||
* An executor state piece that operates directly on trace memory and registers
|
||||
*
|
||||
* <p>
|
||||
* This differs from {@link BytesTracePcodeExecutorStatePiece} in that writes performed by the
|
||||
* emulator immediately affect the trace. There is no caching. In effect, the trace <em>is</em> the
|
||||
* state. This is used primarily in testing to initialize trace state using Sleigh, which is more
|
||||
* succinct than accessing trace memory and registers via the trace API. It may also be incorporated
|
||||
* into the UI at a later time.
|
||||
*
|
||||
* @see TraceSleighUtils
|
||||
*/
|
||||
public class DirectBytesTracePcodeExecutorStatePiece
|
||||
extends AbstractLongOffsetPcodeExecutorStatePiece<byte[], byte[], TraceMemorySpace> {
|
||||
|
||||
protected final SemisparseByteArray unique = new SemisparseByteArray();
|
||||
private final Trace trace;
|
||||
|
@ -43,8 +55,10 @@ public class TraceBytesPcodeExecutorState
|
|||
|
||||
private final DefaultTraceTimeViewport viewport;
|
||||
|
||||
public TraceBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
super(trace.getBaseLanguage(), BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()));
|
||||
protected DirectBytesTracePcodeExecutorStatePiece(Language language,
|
||||
PcodeArithmetic<byte[]> arithmetic, Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
super(language, arithmetic, arithmetic);
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
this.thread = thread;
|
||||
|
@ -54,48 +68,65 @@ public class TraceBytesPcodeExecutorState
|
|||
this.viewport.setSnap(snap);
|
||||
}
|
||||
|
||||
public PcodeExecutorState<Pair<byte[], TraceMemoryState>> withMemoryState() {
|
||||
return new PairedPcodeExecutorState<>(this,
|
||||
new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame)) {
|
||||
|
||||
@Override
|
||||
public void setVar(AddressSpace space, Pair<byte[], TraceMemoryState> offset, int size,
|
||||
boolean truncateAddressableUnit, Pair<byte[], TraceMemoryState> val) {
|
||||
if (offset.getRight() == TraceMemoryState.KNOWN) {
|
||||
super.setVar(space, offset, size, truncateAddressableUnit, val);
|
||||
return;
|
||||
}
|
||||
super.setVar(space, offset, size, truncateAddressableUnit,
|
||||
new ImmutablePair<>(val.getLeft(), TraceMemoryState.UNKNOWN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<byte[], TraceMemoryState> getVar(AddressSpace space,
|
||||
Pair<byte[], TraceMemoryState> offset, int size,
|
||||
boolean truncateAddressableUnit) {
|
||||
Pair<byte[], TraceMemoryState> result =
|
||||
super.getVar(space, offset, size, truncateAddressableUnit);
|
||||
if (offset.getRight() == TraceMemoryState.KNOWN) {
|
||||
return result;
|
||||
}
|
||||
return new ImmutablePair<>(result.getLeft(), TraceMemoryState.UNKNOWN);
|
||||
}
|
||||
};
|
||||
protected DirectBytesTracePcodeExecutorStatePiece(Language language, Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
this(language, BytesPcodeArithmetic.forLanguage(language), trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
public DirectBytesTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
this(trace.getBaseLanguage(), trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a state which computes an expression's {@link TraceMemoryState} as an auxiliary
|
||||
* attribute
|
||||
*
|
||||
* <p>
|
||||
* If every part of every input to the expression is {@link TraceMemoryState#KNOWN}, then the
|
||||
* expression's value will be marked {@link TraceMemoryState#KNOWN}. Otherwise, it's marked
|
||||
* {@link TraceMemoryState#UNKNOWN}.
|
||||
*
|
||||
* @return the paired executor state
|
||||
*/
|
||||
public PcodeExecutorStatePiece<byte[], Pair<byte[], TraceMemoryState>> withMemoryState() {
|
||||
return new PairedPcodeExecutorStatePiece<>(this,
|
||||
new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the trace
|
||||
*
|
||||
* @return the trace
|
||||
*/
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-bind this state to another snap
|
||||
*
|
||||
* @param snap the new snap
|
||||
*/
|
||||
public void setSnap(long snap) {
|
||||
this.snap = snap;
|
||||
this.viewport.setSnap(snap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current snap
|
||||
*
|
||||
* @return the snap
|
||||
*/
|
||||
public long getSnap() {
|
||||
return snap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-bind this state to another thread
|
||||
*
|
||||
* @param thread the new thread
|
||||
*/
|
||||
public void setThread(TraceThread thread) {
|
||||
if (thread != null & thread.getTrace() != trace) {
|
||||
throw new IllegalArgumentException("Thread, if given, must be part of the same trace");
|
||||
|
@ -103,28 +134,33 @@ public class TraceBytesPcodeExecutorState
|
|||
this.thread = thread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current thread
|
||||
*
|
||||
* @return the thread
|
||||
*/
|
||||
public TraceThread getThread() {
|
||||
return thread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-bind this state to another frame
|
||||
*
|
||||
* @param frame the new frame
|
||||
*/
|
||||
public void setFrame(int frame) {
|
||||
this.frame = frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current frame
|
||||
*
|
||||
* @return the frame
|
||||
*/
|
||||
public int getFrame() {
|
||||
return frame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long offsetToLong(byte[] offset) {
|
||||
return Utils.bytesToLong(offset, offset.length, language.isBigEndian());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] longToOffset(AddressSpace space, long l) {
|
||||
return arithmetic.fromConst(l, space.getPointerSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUnique(long offset, int size, byte[] val) {
|
||||
assert size == val.length;
|
||||
|
@ -164,7 +200,7 @@ public class TraceBytesPcodeExecutorState
|
|||
}
|
||||
|
||||
@Override
|
||||
public MemBuffer getConcreteBuffer(Address address) {
|
||||
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
|
||||
return trace.getMemoryManager().getBufferAt(snap, address);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.pcode.exec.PairedPcodeExecutorState;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A trace-bound state composed of another trace-bound state and a piece
|
||||
*
|
||||
* @param <L> the type of values for the left state
|
||||
* @param <R> the type of values for the right piece
|
||||
* @see PairedPcodeExecutorState
|
||||
*/
|
||||
public class PairedTracePcodeExecutorState<L, R> extends PairedPcodeExecutorState<L, R>
|
||||
implements TracePcodeExecutorState<Pair<L, R>> {
|
||||
|
||||
private final TracePcodeExecutorStatePiece<L, L> left;
|
||||
private final TracePcodeExecutorStatePiece<L, R> right;
|
||||
|
||||
public PairedTracePcodeExecutorState(PairedTracePcodeExecutorStatePiece<L, L, R> piece) {
|
||||
super(piece);
|
||||
this.left = piece.getLeft();
|
||||
this.right = piece.getRight();
|
||||
}
|
||||
|
||||
public PairedTracePcodeExecutorState(TracePcodeExecutorState<L> left,
|
||||
TracePcodeExecutorStatePiece<L, R> right) {
|
||||
super(left, right);
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
left.writeDown(trace, snap, thread, frame);
|
||||
right.writeDown(trace, snap, thread, frame);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.pcode.exec.PairedPcodeExecutorStatePiece;
|
||||
import ghidra.pcode.exec.PcodeArithmetic;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A trace-bound state piece composed of two other trace-bound pieces sharing the same address type
|
||||
*
|
||||
* @see PairedPcodeExecutorStatePiece
|
||||
* @param <A> the type of addresses
|
||||
* @param <L> the type of values for the left piece
|
||||
* @param <R> the type of values for the right piece
|
||||
*/
|
||||
public class PairedTracePcodeExecutorStatePiece<A, L, R>
|
||||
extends PairedPcodeExecutorStatePiece<A, L, R>
|
||||
implements TracePcodeExecutorStatePiece<A, Pair<L, R>> {
|
||||
|
||||
protected final TracePcodeExecutorStatePiece<A, L> left;
|
||||
protected final TracePcodeExecutorStatePiece<A, R> right;
|
||||
|
||||
public PairedTracePcodeExecutorStatePiece(TracePcodeExecutorStatePiece<A, L> left,
|
||||
TracePcodeExecutorStatePiece<A, R> right) {
|
||||
super(left, right);
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public PairedTracePcodeExecutorStatePiece(TracePcodeExecutorStatePiece<A, L> left,
|
||||
TracePcodeExecutorStatePiece<A, R> right, PcodeArithmetic<A> addressArithmetic,
|
||||
PcodeArithmetic<Pair<L, R>> arithmetic) {
|
||||
super(left, right, addressArithmetic, arithmetic);
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
left.writeDown(trace, snap, thread, frame);
|
||||
right.writeDown(trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorStatePiece<A, L> getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorStatePiece<A, R> getRight() {
|
||||
return right;
|
||||
}
|
||||
}
|
|
@ -15,31 +15,26 @@
|
|||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.pcode.exec.AccessPcodeExecutionException;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A state composing a single {@link RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece}
|
||||
*/
|
||||
public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorState
|
||||
extends RequireIsKnownTraceCachedWriteBytesPcodeExecutorState {
|
||||
extends DefaultTracePcodeExecutorState<byte[]> {
|
||||
|
||||
/**
|
||||
* Create the state
|
||||
*
|
||||
* @param trace the trace from which to load state
|
||||
* @param snap the snap from which to load state
|
||||
* @param thread if applicable, the thread identifying the register space
|
||||
* @param frame if applicable, the frame identifying the register space
|
||||
*/
|
||||
public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
super(trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AddressSetView getKnown(TraceMemorySpace source) {
|
||||
return source.getAddressesWithState(Range.closed(0L, snap),
|
||||
s -> s == TraceMemoryState.KNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AccessPcodeExecutionException excFor(AddressSetView unknown) {
|
||||
throw new AccessPcodeExecutionException("Memory at " + unknown + " has never been known.");
|
||||
super(new RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(trace, snap, thread,
|
||||
frame));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.pcode.exec.AccessPcodeExecutionException;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A relaxation of {@link RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece} that permits
|
||||
* reads of stale addresses
|
||||
*
|
||||
* <p>
|
||||
* An address can be read so long as it is {@link TraceMemoryState#KNOWN} for any non-scratch snap
|
||||
* up to and including the given snap.
|
||||
*/
|
||||
public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece
|
||||
extends RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece {
|
||||
|
||||
public RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
super(trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AddressSetView getKnown(TraceMemorySpace source) {
|
||||
return source.getAddressesWithState(Range.closed(0L, snap),
|
||||
s -> s == TraceMemoryState.KNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AccessPcodeExecutionException excFor(AddressSetView unknown) {
|
||||
throw new AccessPcodeExecutionException("Memory at " + unknown + " has never been known.");
|
||||
}
|
||||
}
|
|
@ -15,46 +15,26 @@
|
|||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.pcode.exec.AccessPcodeExecutionException;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A state composing a single {@link RequireIsKnownTraceCachedWriteBytesPcodeExecutorState}
|
||||
*/
|
||||
public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorState
|
||||
extends AbstractCheckedTraceCachedWriteBytesPcodeExecutorState {
|
||||
extends DefaultTracePcodeExecutorState<byte[]> {
|
||||
|
||||
/**
|
||||
* Create the state
|
||||
*
|
||||
* @param trace the trace from which to load state
|
||||
* @param snap the snap from which to load state
|
||||
* @param thread if applicable, the thread identifying the register space
|
||||
* @param frame if applicable, the frame identifying the register space
|
||||
*/
|
||||
public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
super(trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
protected AddressSetView getKnown(TraceMemorySpace source) {
|
||||
return source.getAddressesWithState(snap, s -> s == TraceMemoryState.KNOWN);
|
||||
}
|
||||
|
||||
protected AccessPcodeExecutionException excFor(AddressSetView unknown) {
|
||||
return new AccessPcodeExecutionException("Memory at " + unknown + " is unknown.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int checkUninitialized(TraceMemorySpace backing, Address start, int size,
|
||||
AddressSet uninitialized) {
|
||||
if (backing == null) {
|
||||
if (!uninitialized.contains(start)) {
|
||||
return (int) uninitialized.getMinAddress().subtract(start);
|
||||
}
|
||||
throw excFor(uninitialized);
|
||||
}
|
||||
// TODO: Could find first instead?
|
||||
AddressSetView unknown = uninitialized.subtract(getKnown(backing));
|
||||
if (unknown.isEmpty()) {
|
||||
return size;
|
||||
}
|
||||
if (!unknown.contains(start)) {
|
||||
return (int) unknown.getMinAddress().subtract(start);
|
||||
}
|
||||
throw excFor(unknown);
|
||||
super(new RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(trace, snap, thread,
|
||||
frame));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.pcode.exec.AccessPcodeExecutionException;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A space which requires reads to be completely {@link TraceMemorySpace#KNOWN} memory.
|
||||
*
|
||||
* <p>
|
||||
* If a read can be partially completed, then it will proceed up to but not including the first
|
||||
* non-known address. If the start address is non-known, the emulator will be interrupted.
|
||||
*/
|
||||
public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece
|
||||
extends AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece {
|
||||
|
||||
public RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
super(trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
protected AddressSetView getKnown(TraceMemorySpace source) {
|
||||
return source.getAddressesWithState(snap, s -> s == TraceMemoryState.KNOWN);
|
||||
}
|
||||
|
||||
protected AccessPcodeExecutionException excFor(AddressSetView unknown) {
|
||||
return new AccessPcodeExecutionException("Memory at " + unknown + " is unknown.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int checkUninitialized(TraceMemorySpace backing, Address start, int size,
|
||||
AddressSet uninitialized) {
|
||||
if (backing == null) {
|
||||
if (!uninitialized.contains(start)) {
|
||||
return (int) uninitialized.getMinAddress().subtract(start);
|
||||
}
|
||||
throw excFor(uninitialized);
|
||||
}
|
||||
// TODO: Could find first instead?
|
||||
AddressSetView unknown = uninitialized.subtract(getKnown(backing));
|
||||
if (unknown.isEmpty()) {
|
||||
return size;
|
||||
}
|
||||
if (!unknown.contains(start)) {
|
||||
return (int) unknown.getMinAddress().subtract(start);
|
||||
}
|
||||
throw excFor(unknown);
|
||||
}
|
||||
}
|
|
@ -17,22 +17,41 @@ package ghidra.pcode.exec.trace;
|
|||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import ghidra.pcode.exec.ConcretionError;
|
||||
import ghidra.pcode.exec.PcodeArithmetic;
|
||||
import ghidra.pcode.opbehavior.BinaryOpBehavior;
|
||||
import ghidra.pcode.opbehavior.UnaryOpBehavior;
|
||||
import ghidra.program.model.lang.Endian;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
|
||||
/**
|
||||
* The p-code arithmetic for {@link TraceMemoryState}
|
||||
*
|
||||
* <p>
|
||||
* This arithmetic is meant to be used as an auxiliary to a concrete arithmetic. It should be used
|
||||
* with a state that knows how to load state markings from the same trace as the concrete state, so
|
||||
* that it can compute the "state" of a Sleigh expression's value. It essentially works like a
|
||||
* rudimentary taint analyzer: If any part of any input to the expression in tainted, i.e., not
|
||||
* {@link TraceMemoryState#KNOWN}, then the result is {@link TraceMemoryState#UNKNOWN}. This is best
|
||||
* exemplified in
|
||||
* {@link #binaryOp(BinaryOpBehavior, int, int, TraceMemoryState, int, TraceMemoryState)}.
|
||||
*/
|
||||
public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemoryState> {
|
||||
/** The singleton instance */
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public TraceMemoryState unaryOp(UnaryOpBehavior op, int sizeout, int sizein1,
|
||||
public Endian getEndian() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryState unaryOp(int opcode, int sizeout, int sizein1,
|
||||
TraceMemoryState in1) {
|
||||
return in1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryState binaryOp(BinaryOpBehavior op, int sizeout, int sizein1,
|
||||
public TraceMemoryState binaryOp(int opcode, int sizeout, int sizein1,
|
||||
TraceMemoryState in1, int sizein2, TraceMemoryState in2) {
|
||||
if (in1 == TraceMemoryState.KNOWN && in2 == TraceMemoryState.KNOWN) {
|
||||
return TraceMemoryState.KNOWN;
|
||||
|
@ -41,7 +60,22 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemo
|
|||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryState fromConst(long value, int size) {
|
||||
public TraceMemoryState modBeforeStore(int sizeout, int sizeinAddress,
|
||||
TraceMemoryState inAddress, int sizeinValue, TraceMemoryState inValue) {
|
||||
return inValue; // Shouldn't see STORE during Sleigh eval, anyway
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryState modAfterLoad(int sizeout, int sizeinAddress, TraceMemoryState inAddress,
|
||||
int sizeinValue, TraceMemoryState inValue) {
|
||||
if (inAddress == TraceMemoryState.KNOWN && inValue == TraceMemoryState.KNOWN) {
|
||||
return TraceMemoryState.KNOWN;
|
||||
}
|
||||
return TraceMemoryState.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryState fromConst(byte[] value) {
|
||||
return TraceMemoryState.KNOWN;
|
||||
}
|
||||
|
||||
|
@ -51,17 +85,17 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemo
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrue(TraceMemoryState cond) {
|
||||
throw new AssertionError("Cannot decide branches using TraceMemoryState");
|
||||
public TraceMemoryState fromConst(long value, int size) {
|
||||
return TraceMemoryState.KNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger toConcrete(TraceMemoryState value, boolean isContextreg) {
|
||||
throw new AssertionError("Cannot make TraceMemoryState a 'concrete value'");
|
||||
public byte[] toConcrete(TraceMemoryState value, Purpose purpose) {
|
||||
throw new ConcretionError("Cannot make TraceMemoryState concrete", purpose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryState sizeOf(TraceMemoryState value) {
|
||||
public long sizeOf(TraceMemoryState value) {
|
||||
throw new AssertionError("Cannot get size of a TraceMemoryState");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ import java.util.Map;
|
|||
import com.google.common.collect.*;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import ghidra.pcode.exec.AbstractLongOffsetPcodeExecutorStatePiece;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
@ -30,6 +30,18 @@ import ghidra.trace.model.memory.TraceMemoryState;
|
|||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.DefaultTraceTimeViewport;
|
||||
|
||||
/**
|
||||
* The p-code execute state piece for {@link TraceMemoryState}
|
||||
*
|
||||
* <p>
|
||||
* This state piece is meant to be used as an auxiliary to a concrete trace-bound state. See
|
||||
* {@link DirectBytesTracePcodeExecutorState#withMemoryState()}. It should be used with
|
||||
* {@link TraceMemoryStatePcodeArithmetic} as a means of computing the "state" of a Sleigh
|
||||
* expression's value. It essentially works like a rudimentary taint analyzer: If any part of any
|
||||
* input to the expression in tainted, i.e., not {@link TraceMemoryState#KNOWN}, then the result is
|
||||
* {@link TraceMemoryState#UNKNOWN}. This is best exemplified in {@link #getUnique(long, int)},
|
||||
* though it's also exemplified in {@link #getFromSpace(TraceMemorySpace, long, int)}.
|
||||
*/
|
||||
public class TraceMemoryStatePcodeExecutorStatePiece extends
|
||||
AbstractLongOffsetPcodeExecutorStatePiece<byte[], TraceMemoryState, TraceMemorySpace> {
|
||||
|
||||
|
@ -43,7 +55,9 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
|
|||
|
||||
public TraceMemoryStatePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
super(trace.getBaseLanguage(), TraceMemoryStatePcodeArithmetic.INSTANCE);
|
||||
super(trace.getBaseLanguage(),
|
||||
BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()),
|
||||
TraceMemoryStatePcodeArithmetic.INSTANCE);
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
this.thread = thread;
|
||||
|
@ -99,16 +113,6 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long offsetToLong(byte[] offset) {
|
||||
return Utils.bytesToLong(offset, offset.length, language.isBigEndian());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] longToOffset(AddressSpace space, long l) {
|
||||
return Utils.longToBytes(l, space.getPointerSize(), language.isBigEndian());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUnique(long offset, int size, TraceMemoryState val) {
|
||||
unique.put(range(offset, size), val);
|
||||
|
@ -158,7 +162,7 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
|
|||
}
|
||||
|
||||
@Override
|
||||
public MemBuffer getConcreteBuffer(Address address) {
|
||||
throw new AssertionError("Cannot make TraceMemoryState into a concrete buffer");
|
||||
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
|
||||
throw new ConcretionError("Cannot make TraceMemoryState into a concrete buffer", purpose);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.emu.PcodeEmulator;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.PcodeExecutorState;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
|
||||
/**
|
||||
* An emulator that can read initial state from a trace
|
||||
*/
|
||||
public class TracePcodeEmulator extends PcodeEmulator {
|
||||
private static SleighLanguage assertSleigh(Language language) {
|
||||
if (!(language instanceof SleighLanguage)) {
|
||||
throw new IllegalArgumentException("Emulation requires a sleigh language");
|
||||
}
|
||||
return (SleighLanguage) language;
|
||||
}
|
||||
|
||||
protected final Trace trace;
|
||||
protected final long snap;
|
||||
|
||||
public TracePcodeEmulator(Trace trace, long snap) {
|
||||
super(assertSleigh(trace.getBaseLanguage()));
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
}
|
||||
|
||||
protected PcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, thread, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> createSharedState() {
|
||||
return newState(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> emuThread) {
|
||||
return newState(trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the accumulated writes into the given trace at the given snap
|
||||
*
|
||||
* <p>
|
||||
* NOTE: This method requires a transaction to have already been started on the destination
|
||||
* trace. The destination threads must have equal names/paths at the given threadsSnap. When
|
||||
* using scratch space, threadsSnap should be the source snap. If populating a new trace,
|
||||
* threadsSnap should probably be the destination snap.
|
||||
*
|
||||
* @param trace the trace to modify
|
||||
* @param destSnap the destination snap within the trace
|
||||
* @param threadsSnap the snap at which to find corresponding threads
|
||||
* @param synthesizeStacks true to synthesize the innermost stack frame of each thread
|
||||
*/
|
||||
public void writeDown(Trace trace, long destSnap, long threadsSnap, boolean synthesizeStacks) {
|
||||
TraceCachedWriteBytesPcodeExecutorState ss =
|
||||
(TraceCachedWriteBytesPcodeExecutorState) getSharedState();
|
||||
ss.writeCacheDown(trace, destSnap, null, 0);
|
||||
TraceThreadManager threadManager = trace.getThreadManager();
|
||||
for (PcodeThread<byte[]> emuThread : threads.values()) {
|
||||
TraceCachedWriteBytesPcodeExecutorState ls =
|
||||
(TraceCachedWriteBytesPcodeExecutorState) emuThread.getState().getLocalState();
|
||||
TraceThread traceThread = threadManager.getLiveThreadByPath(
|
||||
threadsSnap, emuThread.getName());
|
||||
if (traceThread == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Given trace does not have thread with name/path '" + emuThread.getName() +
|
||||
"' at snap " + destSnap);
|
||||
}
|
||||
ls.writeCacheDown(trace, destSnap, traceThread, 0);
|
||||
if (synthesizeStacks) {
|
||||
TraceStack stack = trace.getStackManager().getStack(traceThread, destSnap, true);
|
||||
stack.getFrame(0, true)
|
||||
.setProgramCounter(Range.atLeast(destSnap), emuThread.getCounter());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.pcode.exec.PcodeExecutorState;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* An interface for trace-bound states
|
||||
*
|
||||
* <p>
|
||||
* In particular, because this derives from {@link TracePcodeExecutorStatePiece}, such states are
|
||||
* required to implement {@link #writeDown(Trace, long, TraceThread, int)}. This interface also
|
||||
* derives from {@link PcodeExecutorState} so that, as the name implies, they can be used where a
|
||||
* state is required.
|
||||
*
|
||||
* @param <T> the type of values
|
||||
*/
|
||||
public interface TracePcodeExecutorState<T>
|
||||
extends PcodeExecutorState<T>, TracePcodeExecutorStatePiece<T, T> {
|
||||
// Nothing to add. Simply a composition of interfaces.
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A state piece which knows how to write its values back into a trace
|
||||
*
|
||||
* @param <A> the type of address offsets
|
||||
* @param <T> the type of values
|
||||
*/
|
||||
public interface TracePcodeExecutorStatePiece<A, T> extends PcodeExecutorStatePiece<A, T> {
|
||||
/**
|
||||
* Write the accumulated values (cache) into the given trace
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This method requires a transaction to have already been started on the
|
||||
* destination trace.
|
||||
*
|
||||
* @param trace the trace to modify
|
||||
* @param snap the snap within the trace
|
||||
* @param thread the thread to take register writes
|
||||
* @param frame the frame for register writes
|
||||
* @see TracePcodeMachine#writeDown(Trace, long, long)
|
||||
*/
|
||||
void writeDown(Trace trace, long snap, TraceThread thread, int frame);
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.emu.PcodeMachine;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
|
||||
/**
|
||||
* A p-code machine which sources its state from a trace and can record back into it
|
||||
*
|
||||
* <p>
|
||||
* This is a "mix in" interface. It is part of the SPI, but not the API. That is, emulator
|
||||
* developers should use this interface, but emulator clients should not. Clients should use
|
||||
* {@link PcodeMachine} instead.
|
||||
*
|
||||
* @param <T> the type of values manipulated by the machine
|
||||
*/
|
||||
public interface TracePcodeMachine<T> extends PcodeMachine<T> {
|
||||
/**
|
||||
* Get the trace from which this emulator reads its initial state
|
||||
*
|
||||
* @return the trace
|
||||
*/
|
||||
Trace getTrace();
|
||||
|
||||
/**
|
||||
* Get the snapshot from which this emulator reads its initial state
|
||||
*
|
||||
* @return the snapshot key
|
||||
*/
|
||||
long getSnap();
|
||||
|
||||
/**
|
||||
* Get the trace thread corresponding to the given p-code thread
|
||||
*
|
||||
* @param thread the p-code thread
|
||||
* @return the trace thread
|
||||
*/
|
||||
default TraceThread getTraceThread(PcodeThread<T> thread) {
|
||||
return getTrace().getThreadManager().getLiveThreadByPath(getSnap(), thread.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a shared state
|
||||
*
|
||||
* @return the shared state
|
||||
*/
|
||||
TracePcodeExecutorState<T> createSharedState();
|
||||
|
||||
/**
|
||||
* Create a local state
|
||||
*
|
||||
* @param thread the thread whose state is being created
|
||||
* @return the local state
|
||||
*/
|
||||
TracePcodeExecutorState<T> createLocalState(PcodeThread<T> thread);
|
||||
|
||||
/**
|
||||
* Check if a register has a {@link TraceMemoryState#KNOWN} value for the given thread
|
||||
*
|
||||
* @param thread the thread
|
||||
* @param register the register
|
||||
* @return true if known
|
||||
*/
|
||||
default boolean isRegisterKnown(PcodeThread<T> thread, Register register) {
|
||||
Trace trace = getTrace();
|
||||
long snap = getSnap();
|
||||
TraceThread traceThread =
|
||||
trace.getThreadManager().getLiveThreadByPath(snap, thread.getName());
|
||||
TraceMemoryRegisterSpace space =
|
||||
trace.getMemoryManager().getMemoryRegisterSpace(traceThread, false);
|
||||
if (space == null) {
|
||||
return false;
|
||||
}
|
||||
return space.getState(snap, register) == TraceMemoryState.KNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the given thread using context from the trace at its program counter
|
||||
*
|
||||
* @param thread the thread to initialize
|
||||
*/
|
||||
default void initializeThreadContext(PcodeThread<T> thread) {
|
||||
SleighLanguage language = getLanguage();
|
||||
Register contextreg = language.getContextBaseRegister();
|
||||
if (contextreg != Register.NO_CONTEXT && !isRegisterKnown(thread, contextreg)) {
|
||||
RegisterValue context = getTrace().getRegisterContextManager()
|
||||
.getValueWithDefault(language, contextreg, getSnap(), thread.getCounter());
|
||||
if (context != null) { // TODO: Why does this happen?
|
||||
thread.overrideContext(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the accumulated emulator state into the given trace at the given snap
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This method requires a transaction to have already been started on the
|
||||
* destination trace. The destination threads must have equal names/paths at the given
|
||||
* threadsSnap. When using scratch space, threadsSnap should be the source snap. If populating a
|
||||
* new trace, threadsSnap should probably be the destination snap.
|
||||
*
|
||||
* @param trace the trace to modify
|
||||
* @param destSnap the destination snap within the trace
|
||||
* @param threadsSnap the snap at which to find corresponding threads, usually the same as
|
||||
* {@link #getSnap()}
|
||||
*/
|
||||
default void writeDown(Trace trace, long destSnap, long threadsSnap) {
|
||||
TracePcodeExecutorState<T> ss = (TracePcodeExecutorState<T>) getSharedState();
|
||||
ss.writeDown(trace, destSnap, null, 0);
|
||||
TraceThreadManager threadManager = trace.getThreadManager();
|
||||
for (PcodeThread<T> emuThread : getAllThreads()) {
|
||||
TracePcodeExecutorState<T> ls =
|
||||
(TracePcodeExecutorState<T>) emuThread.getState().getLocalState();
|
||||
TraceThread traceThread =
|
||||
threadManager.getLiveThreadByPath(threadsSnap, emuThread.getName());
|
||||
if (traceThread == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Given trace does not have thread with name/path '" + emuThread.getName() +
|
||||
"' at snap " + destSnap);
|
||||
}
|
||||
ls.writeDown(trace, destSnap, traceThread, 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,9 +32,26 @@ import ghidra.trace.model.memory.TraceMemorySpace;
|
|||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* Various utilities for using Sleigh with traces
|
||||
*/
|
||||
public enum TraceSleighUtils {
|
||||
;
|
||||
|
||||
/**
|
||||
* Get the trace memory space for the given "coordinates"
|
||||
*
|
||||
* <p>
|
||||
* This is used to find "backing" objects for a p-code executor state bound to a trace, whether
|
||||
* direct or cached.
|
||||
*
|
||||
* @param space the address space
|
||||
* @param trace the trace
|
||||
* @param thread the thread, if a register space
|
||||
* @param frame the frame, if a register space
|
||||
* @param toWrite true if the state intends to write to the space, i.e., the space must exist
|
||||
* @return the space, or null if it doesn't exist
|
||||
*/
|
||||
public static TraceMemorySpace getSpaceForExecution(AddressSpace space, Trace trace,
|
||||
TraceThread thread, int frame, boolean toWrite) {
|
||||
if (space.isRegisterSpace()) {
|
||||
|
@ -47,10 +64,24 @@ public enum TraceSleighUtils {
|
|||
return trace.getMemoryManager().getMemorySpace(space, toWrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a p-code executor that operates directly on bytes of the given trace
|
||||
*
|
||||
* <p>
|
||||
* This execute is most suitable for evaluating Sleigh expression on a given trace snapshot, and
|
||||
* for manipulating or initializing variables using Sleigh code. It is generally not suitable
|
||||
* for use in an emulator. For that, consider {@link BytesTracePcodeEmulator}.
|
||||
*
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the executor
|
||||
*/
|
||||
public static PcodeExecutor<byte[]> buildByteExecutor(Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
TraceBytesPcodeExecutorState state =
|
||||
new TraceBytesPcodeExecutorState(trace, snap, thread, frame);
|
||||
DirectBytesTracePcodeExecutorState state =
|
||||
new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame);
|
||||
Language language = trace.getBaseLanguage();
|
||||
if (!(language instanceof SleighLanguage)) {
|
||||
throw new IllegalArgumentException("Trace must use a SLEIGH language");
|
||||
|
@ -59,10 +90,24 @@ public enum TraceSleighUtils {
|
|||
BytesPcodeArithmetic.forLanguage(language), state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a p-code executor that operates directly on bytes and memory state of the given trace
|
||||
*
|
||||
* <p>
|
||||
* This executor is most suitable for evaluating Sleigh expressions on a given trace snapshot,
|
||||
* when the client would also like to know if all variables involved are
|
||||
* {@link TraceMemoryState#KNOWN}.
|
||||
*
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the executor
|
||||
*/
|
||||
public static PcodeExecutor<Pair<byte[], TraceMemoryState>> buildByteWithStateExecutor(
|
||||
Trace trace, long snap, TraceThread thread, int frame) {
|
||||
TraceBytesPcodeExecutorState state =
|
||||
new TraceBytesPcodeExecutorState(trace, snap, thread, frame);
|
||||
DirectBytesTracePcodeExecutorState state =
|
||||
new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame);
|
||||
PcodeExecutorState<Pair<byte[], TraceMemoryState>> paired = state.withMemoryState();
|
||||
Language language = trace.getBaseLanguage();
|
||||
if (!(language instanceof SleighLanguage)) {
|
||||
|
@ -73,6 +118,16 @@ public enum TraceSleighUtils {
|
|||
paired);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a compiled p-code expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value of the expression as a byte array
|
||||
*/
|
||||
public static byte[] evaluateBytes(PcodeExpression expr, Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
SleighLanguage language = expr.getLanguage();
|
||||
|
@ -84,6 +139,16 @@ public enum TraceSleighUtils {
|
|||
return expr.evaluate(executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a compiled p-code expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value of the expression as a big integer
|
||||
*/
|
||||
public static BigInteger evaluate(PcodeExpression expr, Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
byte[] bytes = evaluateBytes(expr, trace, snap, thread, frame);
|
||||
|
@ -91,6 +156,16 @@ public enum TraceSleighUtils {
|
|||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a compiled p-code expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value and state of the expression
|
||||
*/
|
||||
public static Pair<byte[], TraceMemoryState> evaluateBytesWithState(PcodeExpression expr,
|
||||
Trace trace, long snap, TraceThread thread, int frame) {
|
||||
SleighLanguage language = expr.getLanguage();
|
||||
|
@ -104,6 +179,16 @@ public enum TraceSleighUtils {
|
|||
return expr.evaluate(executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a compiled p-code expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value and state of the expression
|
||||
*/
|
||||
public static Pair<BigInteger, TraceMemoryState> evaluateWithState(PcodeExpression expr,
|
||||
Trace trace, long snap, TraceThread thread, int frame) {
|
||||
Pair<byte[], TraceMemoryState> bytesPair =
|
||||
|
@ -114,6 +199,16 @@ public enum TraceSleighUtils {
|
|||
bytesPair.getRight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a Sleigh expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value of the expression as a byte array
|
||||
*/
|
||||
public static byte[] evaluateBytes(String expr, Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
Language language = trace.getBaseLanguage();
|
||||
|
@ -125,6 +220,16 @@ public enum TraceSleighUtils {
|
|||
trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a Sleigh expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value of the expression as a big integer
|
||||
*/
|
||||
public static BigInteger evaluate(String expr, Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
Language language = trace.getBaseLanguage();
|
||||
|
@ -135,6 +240,16 @@ public enum TraceSleighUtils {
|
|||
trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a Sleigh expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value and state of the expression
|
||||
*/
|
||||
public static Entry<byte[], TraceMemoryState> evaluateBytesWithState(String expr, Trace trace,
|
||||
long snap, TraceThread thread, int frame) {
|
||||
Language language = trace.getBaseLanguage();
|
||||
|
@ -146,6 +261,16 @@ public enum TraceSleighUtils {
|
|||
trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a Sleigh expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value and state of the expression
|
||||
*/
|
||||
public static Entry<BigInteger, TraceMemoryState> evaluateWithState(String expr, Trace trace,
|
||||
long snap, TraceThread thread, int frame) {
|
||||
Language language = trace.getBaseLanguage();
|
||||
|
@ -157,6 +282,18 @@ public enum TraceSleighUtils {
|
|||
trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the expression for retrieving a memory range
|
||||
*
|
||||
* <p>
|
||||
* In general, it does not make sense to use this directly with the above evaluation methods.
|
||||
* More likely, this is used in the UI to aid the user in generating an expression. From the
|
||||
* API, it's much easier to access the memory state directly.
|
||||
*
|
||||
* @param language the language
|
||||
* @param range the range
|
||||
* @return the expression
|
||||
*/
|
||||
public static String generateExpressionForRange(Language language, AddressRange range) {
|
||||
AddressSpace space = range.getAddressSpace();
|
||||
long length = range.getLength();
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace.auxiliary;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory;
|
||||
import ghidra.pcode.exec.trace.*;
|
||||
|
||||
/**
|
||||
* An auxiliary emulator parts factory capable of integrating with a trace
|
||||
*
|
||||
* <p>
|
||||
* This can manufacture parts for an emulator that reads and writes its state (concrete and
|
||||
* auxiliary pieces) from and to a trace, as well as all the parts for the less integrated forms of
|
||||
* the same emulator. The pattern of use is generally to read from a given "source" snap, execute
|
||||
* some stepping schedule, then write the cache to a given "destination" snap.
|
||||
*
|
||||
* @param <U> the type of auxiliary values
|
||||
*/
|
||||
public interface AuxTraceEmulatorPartsFactory<U> extends AuxEmulatorPartsFactory<U> {
|
||||
/**
|
||||
* Create the shared (memory) state of a new trace-integrated emulator
|
||||
*
|
||||
* <p>
|
||||
* This is usually composed of pieces using {@link PairedTracePcodeExecutorStatePiece}, but it
|
||||
* does not have to be. It must incorporate the concrete piece provided. The state must be
|
||||
* capable of lazily loading state from a trace and later writing its cache back into the trace
|
||||
* at another snapshot. The given concrete piece is already capable of doing that for concrete
|
||||
* values. The auxiliary piece should be able to independently load its state from the trace,
|
||||
* since this is one way a user expects to initialize the auxiliary values.
|
||||
*
|
||||
* @param emulator the emulator
|
||||
* @param concrete the concrete piece
|
||||
* @return the composed state
|
||||
*/
|
||||
TracePcodeExecutorState<Pair<byte[], U>> createTraceSharedState(
|
||||
AuxTracePcodeEmulator<U> emulator, BytesTracePcodeExecutorStatePiece concrete);
|
||||
|
||||
/**
|
||||
* Create the local (register) state of a new trace-integrated thread
|
||||
*
|
||||
* <p>
|
||||
* This must have the same capabilities as
|
||||
* {@link #createTraceSharedState(AuxTracePcodeEmulator, BytesTracePcodeExecutorStatePiece)}.
|
||||
*
|
||||
* @param emulator the emulator
|
||||
* @param thread the new thread
|
||||
* @param concrete the concrete piece
|
||||
* @return the composed state
|
||||
*/
|
||||
TracePcodeExecutorState<Pair<byte[], U>> createTraceLocalState(
|
||||
AuxTracePcodeEmulator<U> emulator, PcodeThread<Pair<byte[], U>> thread,
|
||||
BytesTracePcodeExecutorStatePiece concrete);
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace.auxiliary;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory;
|
||||
import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator;
|
||||
import ghidra.pcode.exec.trace.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
/**
|
||||
* An trace-integrated emulator whose parts are manufactured by a
|
||||
* {@link AuxTraceEmulatorPartsFactory}
|
||||
*
|
||||
* <p>
|
||||
* See the parts factory interface and its super interfaces:
|
||||
* <ul>
|
||||
* <li>{@link AuxTraceEmulatorPartsFactory}</li>
|
||||
* <li>{@link AuxEmulatorPartsFactory}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <U> the type of auxiliary values
|
||||
*/
|
||||
public abstract class AuxTracePcodeEmulator<U> extends AuxPcodeEmulator<U>
|
||||
implements TracePcodeMachine<Pair<byte[], U>> {
|
||||
|
||||
protected final Trace trace;
|
||||
protected final long snap;
|
||||
|
||||
/**
|
||||
* Create a new emulator
|
||||
*
|
||||
* @param trace the trace from which the emulator loads state
|
||||
* @param snap the snap from which the emulator loads state
|
||||
*/
|
||||
public AuxTracePcodeEmulator(Trace trace, long snap) {
|
||||
super(trace.getBaseLanguage());
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected abstract AuxTraceEmulatorPartsFactory<U> getPartsFactory();
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSnap() {
|
||||
return snap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeThread<Pair<byte[], U>> createThread(String name) {
|
||||
PcodeThread<Pair<byte[], U>> thread = super.createThread(name);
|
||||
initializeThreadContext(thread);
|
||||
return thread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorState<Pair<byte[], U>> createSharedState() {
|
||||
return getPartsFactory().createTraceSharedState(this,
|
||||
new BytesTracePcodeExecutorStatePiece(trace, snap, null, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorState<Pair<byte[], U>> createLocalState(
|
||||
PcodeThread<Pair<byte[], U>> thread) {
|
||||
return getPartsFactory().createTraceLocalState(this, thread,
|
||||
new BytesTracePcodeExecutorStatePiece(trace, snap, getTraceThread(thread), 0));
|
||||
}
|
||||
}
|
|
@ -37,9 +37,19 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.Abstract
|
|||
import ghidra.util.database.DBAnnotatedObject;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||
|
||||
/**
|
||||
* Various utilities used for implementing the trace database
|
||||
*
|
||||
* <p>
|
||||
* Some of these are also useful from the API perspective. TODO: We should probably separate trace
|
||||
* API utilities into another class.
|
||||
*/
|
||||
public enum DBTraceUtils {
|
||||
;
|
||||
|
||||
/**
|
||||
* A tuple used to index/locate a block in the trace's byte stores (memory manager)
|
||||
*/
|
||||
public static class OffsetSnap {
|
||||
public final long offset;
|
||||
public final long snap;
|
||||
|
@ -83,6 +93,9 @@ public enum DBTraceUtils {
|
|||
}
|
||||
|
||||
// TODO: Should this be in by default?
|
||||
/**
|
||||
* A codec or URLs
|
||||
*/
|
||||
public static class URLDBFieldCodec<OT extends DBAnnotatedObject>
|
||||
extends AbstractDBFieldCodec<URL, OT, StringField> {
|
||||
public URLDBFieldCodec(Class<OT> objectType, Field field, int column) {
|
||||
|
@ -125,6 +138,9 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A codec for language IDs
|
||||
*/
|
||||
public static class LanguageIDDBFieldCodec<OT extends DBAnnotatedObject>
|
||||
extends AbstractDBFieldCodec<LanguageID, OT, StringField> {
|
||||
|
||||
|
@ -162,6 +178,9 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A codec for compiler spec IDs
|
||||
*/
|
||||
public static class CompilerSpecIDDBFieldCodec<OT extends DBAnnotatedObject>
|
||||
extends AbstractDBFieldCodec<CompilerSpecID, OT, StringField> {
|
||||
|
||||
|
@ -199,6 +218,9 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A (abstract) codec for the offset-snap tuple
|
||||
*/
|
||||
public abstract static class AbstractOffsetSnapDBFieldCodec<OT extends DBAnnotatedObject>
|
||||
extends AbstractDBFieldCodec<OffsetSnap, OT, BinaryField> {
|
||||
|
||||
|
@ -248,6 +270,7 @@ public enum DBTraceUtils {
|
|||
/**
|
||||
* Codec for storing {@link OffsetSnap}s as {@link BinaryField}s.
|
||||
*
|
||||
* <p>
|
||||
* Encodes the address space ID followed by the address then the snap.
|
||||
*
|
||||
* @param <OT> the type of the object whose field is encoded/decoded.
|
||||
|
@ -277,6 +300,9 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A codec for reference types
|
||||
*/
|
||||
public static class RefTypeDBFieldCodec<OT extends DBAnnotatedObject>
|
||||
extends AbstractDBFieldCodec<RefType, OT, ByteField> {
|
||||
public RefTypeDBFieldCodec(Class<OT> objectType, Field field, int column) {
|
||||
|
@ -309,27 +335,103 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method outline for setting an entry in a range map where coalescing is desired
|
||||
*
|
||||
* @param <E> the type of entries
|
||||
* @param <D> the type of range bounds
|
||||
* @param <R> the type of ranges
|
||||
* @param <V> the type of values
|
||||
*/
|
||||
public static abstract class RangeMapSetter<E, D extends Comparable<D>, R, V> {
|
||||
/**
|
||||
* Get the range of the given entry
|
||||
*
|
||||
* @param entry the entry
|
||||
* @return the range
|
||||
*/
|
||||
protected abstract R getRange(E entry);
|
||||
|
||||
/**
|
||||
* Get the value of the given entry
|
||||
*
|
||||
* @param entry the entry
|
||||
* @return the value
|
||||
*/
|
||||
protected abstract V getValue(E entry);
|
||||
|
||||
/**
|
||||
* Remove an entry from the map
|
||||
*
|
||||
* @param entry the entry
|
||||
*/
|
||||
protected abstract void remove(E entry);
|
||||
|
||||
/**
|
||||
* Get the lower bound of the range
|
||||
*
|
||||
* @param range the range
|
||||
* @return the lower bound
|
||||
*/
|
||||
protected abstract D getLower(R range);
|
||||
|
||||
/**
|
||||
* Get the upper bound of the range
|
||||
*
|
||||
* @param range the range
|
||||
* @return the upper bound
|
||||
*/
|
||||
protected abstract D getUpper(R range);
|
||||
|
||||
/**
|
||||
* Create a closed range with the given bounds
|
||||
*
|
||||
* @param lower the lower bound
|
||||
* @param upper the upper bound
|
||||
* @return the range
|
||||
*/
|
||||
protected abstract R toRange(D lower, D upper);
|
||||
|
||||
/**
|
||||
* Get the number immediately preceding the given bound
|
||||
*
|
||||
* @param d the bound
|
||||
* @return the previous bound, or null if it doesn't exist
|
||||
*/
|
||||
protected abstract D getPrevious(D d);
|
||||
|
||||
/**
|
||||
* Get the number immediately following the given bound
|
||||
*
|
||||
* @param d the bound
|
||||
* @return the next bound, or null if it doesn't exist
|
||||
*/
|
||||
protected abstract D getNext(D d);
|
||||
|
||||
/**
|
||||
* Get all entries intersecting the closed range formed by the given bounds
|
||||
*
|
||||
* @param lower the lower bound
|
||||
* @param upper the upper bound
|
||||
* @return the intersecting entries
|
||||
*/
|
||||
protected abstract Iterable<E> getIntersecting(D lower, D upper);
|
||||
|
||||
/**
|
||||
* Place an entry into the map
|
||||
*
|
||||
* @param range the range of the entry
|
||||
* @param value the value of the entry
|
||||
* @return the new entry (or an existing entry)
|
||||
*/
|
||||
protected abstract E put(R range, V value);
|
||||
|
||||
/**
|
||||
* Get the previous bound or this same bound, if the previous doesn't exist
|
||||
*
|
||||
* @param d the bound
|
||||
* @return the previous or same bound
|
||||
*/
|
||||
protected D getPreviousOrSame(D d) {
|
||||
D prev = getPrevious(d);
|
||||
if (prev == null) {
|
||||
|
@ -338,6 +440,12 @@ public enum DBTraceUtils {
|
|||
return prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next bound or this same bound, if the next doesn't exist
|
||||
*
|
||||
* @param d the bound
|
||||
* @return the next or same bound
|
||||
*/
|
||||
protected D getNextOrSame(D d) {
|
||||
D next = getNext(d);
|
||||
if (next == null) {
|
||||
|
@ -346,15 +454,40 @@ public enum DBTraceUtils {
|
|||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the two ranges are connected
|
||||
*
|
||||
* <p>
|
||||
* The ranges are connected if they intersect, or if their bounds abut.
|
||||
*
|
||||
* @param r1 the first range
|
||||
* @param r2 the second range
|
||||
* @return true if connected
|
||||
*/
|
||||
protected boolean connects(R r1, R r2) {
|
||||
return getPreviousOrSame(getLower(r1)).compareTo(getUpper(r2)) <= 0 ||
|
||||
getPreviousOrSame(getLower(r2)).compareTo(getUpper(r1)) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point: Set the given range to the given value, coalescing where possible
|
||||
*
|
||||
* @param range the range
|
||||
* @param value the value
|
||||
* @return the entry containing the value
|
||||
*/
|
||||
public E set(R range, V value) {
|
||||
return set(getLower(range), getUpper(range), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point: Set the given range to the given value, coalescing where possible
|
||||
*
|
||||
* @param lower the lower bound
|
||||
* @param upper the upper bound
|
||||
* @param value the value
|
||||
* @return the entry containing the value
|
||||
*/
|
||||
public E set(D lower, D upper, V value) {
|
||||
// Go one out to find abutting ranges, too.
|
||||
D prev = getPreviousOrSame(lower);
|
||||
|
@ -395,6 +528,12 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A setter which works on ranges of addresses
|
||||
*
|
||||
* @param <E> the type of entry
|
||||
* @param <V> the type of value
|
||||
*/
|
||||
public static abstract class AddressRangeMapSetter<E, V>
|
||||
extends RangeMapSetter<E, Address, AddressRange, V> {
|
||||
@Override
|
||||
|
@ -423,6 +562,12 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A setter which operates on spans of snapshot keys
|
||||
*
|
||||
* @param <E> the type of entry
|
||||
* @param <V> the type of value
|
||||
*/
|
||||
public static abstract class LifespanMapSetter<E, V>
|
||||
extends RangeMapSetter<E, Long, Range<Long>, V> {
|
||||
|
||||
|
@ -458,6 +603,16 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lower endpoint as stored in the database
|
||||
*
|
||||
* <p>
|
||||
* {@link Long#MIN_VALUE} represents no lower bound. Endpoints should always be closed unless
|
||||
* unbounded. If open, it will be converted to closed (at one greater).
|
||||
*
|
||||
* @param range the range
|
||||
* @return the endpoint
|
||||
*/
|
||||
public static long lowerEndpoint(Range<Long> range) {
|
||||
if (!range.hasLowerBound()) {
|
||||
return Long.MIN_VALUE;
|
||||
|
@ -468,6 +623,16 @@ public enum DBTraceUtils {
|
|||
return range.lowerEndpoint().longValue() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the upper endpoint as stored in the database
|
||||
*
|
||||
* <p>
|
||||
* {@link Long#MAX_VALUE} represents no upper bound. Endpoints should alwyas be closed unless
|
||||
* unbounded. If open, it will be converted to closed (at one less).
|
||||
*
|
||||
* @param range the range
|
||||
* @return the endpoint
|
||||
*/
|
||||
public static long upperEndpoint(Range<Long> range) {
|
||||
if (!range.hasUpperBound()) {
|
||||
return Long.MAX_VALUE;
|
||||
|
@ -478,6 +643,13 @@ public enum DBTraceUtils {
|
|||
return range.upperEndpoint().longValue() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given enpoints to a range
|
||||
*
|
||||
* @param lowerEndpoint the lower endpoint, where {@link Long#MIN_VALUE} indicates unbounded
|
||||
* @param upperEndpoint the upper endpoint, where {@link Long#MAX_VALUE} indicates unbounded
|
||||
* @return the range
|
||||
*/
|
||||
public static Range<Long> toRange(long lowerEndpoint, long upperEndpoint) {
|
||||
if (lowerEndpoint == Long.MIN_VALUE && upperEndpoint == Long.MAX_VALUE) {
|
||||
return Range.all();
|
||||
|
@ -491,10 +663,27 @@ public enum DBTraceUtils {
|
|||
return Range.closed(lowerEndpoint, upperEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the range starting at the given snap, to infinity
|
||||
*
|
||||
* @param snap the starting snap
|
||||
* @return the range [snap, +inf)
|
||||
*/
|
||||
public static Range<Long> toRange(long snap) {
|
||||
return toRange(snap, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the two ranges intersect
|
||||
*
|
||||
* <p>
|
||||
* This is a bit obtuse in Guava's API, so here's the convenience method
|
||||
*
|
||||
* @param <T> the type of range endpoints
|
||||
* @param a the first range
|
||||
* @param b the second range
|
||||
* @return true if they intersect
|
||||
*/
|
||||
public static <T extends Comparable<T>> boolean intersect(Range<T> a, Range<T> b) {
|
||||
// Because we're working with a discrete domain, we have to be careful to never use open
|
||||
// lower bounds. Otherwise, the following two inputs would cause a true return value when,
|
||||
|
@ -502,10 +691,45 @@ public enum DBTraceUtils {
|
|||
return a.isConnected(b) && !a.intersection(b).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given snapshot key is designated as scratch space
|
||||
*
|
||||
* <p>
|
||||
* Conventionally, negative snaps are scratch space.
|
||||
*
|
||||
* @param snap the snap
|
||||
* @return true if scratch space
|
||||
*/
|
||||
public static boolean isScratch(long snap) {
|
||||
return snap < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form a range starting at the given snap that does not traverse both scratch and non-scratch
|
||||
* space
|
||||
*
|
||||
* @param start the starting snap
|
||||
* @return the range [start,0] if start is in scratch space, or [start, +inf) if start is not in
|
||||
* scratch space
|
||||
*/
|
||||
public static Range<Long> atLeastMaybeScratch(long start) {
|
||||
if (start < 0) {
|
||||
return Range.closed(start, -1L);
|
||||
}
|
||||
return Range.atLeast(start);
|
||||
}
|
||||
|
||||
/**
|
||||
* "Compare" two ranges
|
||||
*
|
||||
* <p>
|
||||
* This is just to impose a sorting order for display.
|
||||
*
|
||||
* @param <C> the type of endpoints
|
||||
* @param a the first range
|
||||
* @param b the second range
|
||||
* @return the result as in {@link Comparable#compareTo(Object)}
|
||||
*/
|
||||
public static <C extends Comparable<C>> int compareRanges(Range<C> a, Range<C> b) {
|
||||
int result;
|
||||
if (!a.hasLowerBound() && b.hasLowerBound()) {
|
||||
|
@ -548,6 +772,15 @@ public enum DBTraceUtils {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the table name for a given addres/register space
|
||||
*
|
||||
* @param baseName the base name of the table group
|
||||
* @param space the address space
|
||||
* @param threadKey the thread key, -1 usually indicating "no thread"
|
||||
* @param frameLevel the frame level
|
||||
* @return the table name
|
||||
*/
|
||||
public static String tableName(String baseName, AddressSpace space, long threadKey,
|
||||
int frameLevel) {
|
||||
if (space.isRegisterSpace()) {
|
||||
|
@ -560,15 +793,17 @@ public enum DBTraceUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* TODO: Document me
|
||||
* Truncate or delete an entry to make room
|
||||
*
|
||||
* <p>
|
||||
* Only call this method for entries which definitely intersect the given span
|
||||
* Only call this method for entries which definitely intersect the given span. This does not
|
||||
* verify intersection. If the data's start snap is contained in the span to clear, the entry is
|
||||
* deleted. Otherwise, it's end snap is set to one less than the span's start snap.
|
||||
*
|
||||
* @param data
|
||||
* @param span
|
||||
* @param lifespanSetter
|
||||
* @param deleter
|
||||
* @param data the entry subject to truncation or deletion
|
||||
* @param span the span to clear up
|
||||
* @param lifespanSetter the method used to truncate the entry
|
||||
* @param deleter the method used to delete the entry
|
||||
*/
|
||||
public static <DR extends AbstractDBTraceAddressSnapRangePropertyMapData<?>> void makeWay(
|
||||
DR data, Range<Long> span, BiConsumer<? super DR, Range<Long>> lifespanSetter,
|
||||
|
@ -582,6 +817,13 @@ public enum DBTraceUtils {
|
|||
lifespanSetter.accept(data, toRange(data.getY1(), lowerEndpoint(span) - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sutract two ranges, yielding 0, 1, or 2 ranges
|
||||
*
|
||||
* @param a the first range
|
||||
* @param b the second range
|
||||
* @return the list of ranges
|
||||
*/
|
||||
public static List<Range<Long>> subtract(Range<Long> a, Range<Long> b) {
|
||||
RangeSet<Long> set = TreeRangeSet.create();
|
||||
set.add(a);
|
||||
|
@ -592,12 +834,25 @@ public enum DBTraceUtils {
|
|||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast an iterator to a less-specific type, given that it cannot insert elements
|
||||
*
|
||||
* @param <T> the desired type
|
||||
* @param it the iterator of more specific type
|
||||
* @return the same iterator
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Iterator<T> covariantIterator(Iterator<? extends T> it) {
|
||||
// Iterators only support read and remove, not insert. Safe to cast.
|
||||
return (Iterator<T>) it;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over all the longs contained in a given range
|
||||
*
|
||||
* @param span the range
|
||||
* @return the iterator
|
||||
*/
|
||||
public static Iterator<Long> iterateSpan(Range<Long> span) {
|
||||
return new Iterator<>() {
|
||||
final long end = upperEndpoint(span);
|
||||
|
@ -617,6 +872,17 @@ public enum DBTraceUtils {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the addresses in a factory, starting at the given place
|
||||
*
|
||||
* <p>
|
||||
* If backward, this yields all addresses coming before start
|
||||
*
|
||||
* @param factory the factory
|
||||
* @param start the start (or end) address
|
||||
* @param forward true for all after, false for all before
|
||||
* @return the address set
|
||||
*/
|
||||
public static AddressSetView getAddressSet(AddressFactory factory, Address start,
|
||||
boolean forward) {
|
||||
AddressSet all = factory.getAddressSet();
|
||||
|
@ -628,6 +894,14 @@ public enum DBTraceUtils {
|
|||
return factory.getAddressSet(min, start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address range, checking the endpoints
|
||||
*
|
||||
* @param min the min address, which must be less than or equal to max
|
||||
* @param max the max address, which must be greater than or equal to min
|
||||
* @return the range
|
||||
* @throws IllegalArgumentException if max is less than min
|
||||
*/
|
||||
public static AddressRange toRange(Address min, Address max) {
|
||||
if (min.compareTo(max) > 0) {
|
||||
throw new IllegalArgumentException("min must precede max");
|
||||
|
|
|
@ -206,17 +206,16 @@ public class DBTraceDataSettingsAdapter
|
|||
}
|
||||
|
||||
@Override
|
||||
protected DBTraceAddressSnapRangePropertyMapSpace<DBTraceSettingsEntry, DBTraceSettingsEntry> createSpace(
|
||||
AddressSpace space, DBTraceSpaceEntry ent) throws VersionException, IOException {
|
||||
protected DBTraceDataSettingsSpace createSpace(AddressSpace space, DBTraceSpaceEntry ent)
|
||||
throws VersionException, IOException {
|
||||
return new DBTraceDataSettingsSpace(
|
||||
tableName(space, ent.getThreadKey(), ent.getFrameLevel()),
|
||||
trace.getStoreFactory(), lock, space, dataType, dataFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DBTraceAddressSnapRangePropertyMapRegisterSpace<DBTraceSettingsEntry, DBTraceSettingsEntry> createRegisterSpace(
|
||||
AddressSpace space, TraceThread thread, DBTraceSpaceEntry ent)
|
||||
throws VersionException, IOException {
|
||||
protected DBTraceDataSettingsRegisterSpace createRegisterSpace(AddressSpace space,
|
||||
TraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException {
|
||||
return new DBTraceDataSettingsRegisterSpace(
|
||||
tableName(space, ent.getThreadKey(), ent.getFrameLevel()),
|
||||
trace.getStoreFactory(), lock, space, thread, ent.getFrameLevel(), dataType,
|
||||
|
|
|
@ -84,10 +84,10 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
|
|||
@Override
|
||||
default <T> void setProperty(String name, Class<T> valueClass, T value) {
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().writeLock())) {
|
||||
TracePropertySetter<T> setter =
|
||||
getTrace().getInternalAddressPropertyManager()
|
||||
.getOrCreatePropertySetter(name, valueClass);
|
||||
setter.set(getLifespan(), getAddress(), value);
|
||||
TracePropertyMap<? super T> map = getTrace().getInternalAddressPropertyManager()
|
||||
.getOrCreatePropertyMapSuper(name, valueClass);
|
||||
TracePropertyMapSpace<? super T> space = map.getPropertyMapSpace(getTraceSpace(), true);
|
||||
space.set(getLifespan(), getAddress(), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,9 +122,18 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
|
|||
@Override
|
||||
default <T> T getProperty(String name, Class<T> valueClass) {
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
|
||||
TracePropertyGetter<T> getter =
|
||||
getTrace().getInternalAddressPropertyManager().getPropertyGetter(name, valueClass);
|
||||
return getter.get(getStartSnap(), getAddress());
|
||||
TracePropertyMap<? extends T> map =
|
||||
getTrace().getInternalAddressPropertyManager()
|
||||
.getPropertyMapExtends(name, valueClass);
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
TracePropertyMapSpace<? extends T> space =
|
||||
map.getPropertyMapSpace(getTraceSpace(), false);
|
||||
if (space == null) {
|
||||
return null;
|
||||
}
|
||||
return space.get(getStartSnap(), getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,7 +159,7 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
|
|||
@Override
|
||||
default boolean hasProperty(String name) {
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
|
||||
TracePropertyMap<?> map =
|
||||
TracePropertyMapOperations<?> map =
|
||||
getTrace().getInternalAddressPropertyManager().getPropertyMap(name);
|
||||
if (map == null) {
|
||||
return false;
|
||||
|
@ -163,13 +172,18 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
|
|||
@Override
|
||||
default boolean getVoidProperty(String name) {
|
||||
// NOTE: Nearly identical to hasProperty, except named property must be Void type
|
||||
// NOTE: No need to use Extends. Nothing extends Void.
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
|
||||
TracePropertyGetter<Void> getter =
|
||||
getTrace().getInternalAddressPropertyManager().getPropertyGetter(name, Void.class);
|
||||
if (getter == null) {
|
||||
TracePropertyMap<Void> map =
|
||||
getTrace().getInternalAddressPropertyManager().getPropertyMap(name, Void.class);
|
||||
if (map == null) {
|
||||
return false;
|
||||
}
|
||||
return getter.getAddressSetView(Range.singleton(getStartSnap())).contains(getAddress());
|
||||
TracePropertyMapSpace<Void> space = map.getPropertyMapSpace(getTraceSpace(), false);
|
||||
if (space == null) {
|
||||
return false;
|
||||
}
|
||||
return map.getAddressSetView(Range.singleton(getStartSnap())).contains(getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,7 +198,7 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
|
|||
@Override
|
||||
default void removeProperty(String name) {
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().writeLock())) {
|
||||
TracePropertyMap<?> map =
|
||||
TracePropertyMapOperations<?> map =
|
||||
getTrace().getInternalAddressPropertyManager().getPropertyMap(name);
|
||||
if (map == null) {
|
||||
return;
|
||||
|
@ -196,7 +210,7 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
|
|||
@Override
|
||||
default void visitProperty(PropertyVisitor visitor, String propertyName) {
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
|
||||
TracePropertyMap<?> map =
|
||||
TracePropertyMapOperations<?> map =
|
||||
getTrace().getInternalAddressPropertyManager().getPropertyMap(propertyName);
|
||||
if (map == null) {
|
||||
return;
|
||||
|
|
|
@ -18,26 +18,29 @@ package ghidra.trace.database.map;
|
|||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import db.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
|
||||
import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.property.TracePropertyMap;
|
||||
import ghidra.trace.model.property.*;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||
import ghidra.util.database.annot.*;
|
||||
import ghidra.util.exception.NotYetImplementedException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -53,10 +56,12 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
|
|||
dataFactory);
|
||||
}
|
||||
|
||||
// TODO: These next several methods are repeated thrice in this file....
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void makeWay(Entry<TraceAddressSnapRange, T> entry, Range<Long> span) {
|
||||
// TODO: Would rather not rely on implementation knowledge here
|
||||
// The shape is the database record in AbstracctDBTraceAddressSnapRangePropertyMapData
|
||||
// The shape is the database record in AbstractDBTraceAddressSnapRangePropertyMapData
|
||||
makeWay((DR) entry.getKey(), span);
|
||||
}
|
||||
|
||||
|
@ -87,12 +92,21 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
|
|||
}
|
||||
|
||||
@Override
|
||||
public void clear(Range<Long> span, AddressRange range) {
|
||||
public Collection<Entry<TraceAddressSnapRange, T>> getEntries(Range<Long> lifespan,
|
||||
AddressRange range) {
|
||||
return reduce(TraceAddressSnapRangeQuery.intersecting(range, lifespan)).entries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clear(Range<Long> span, AddressRange range) {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
boolean result = false;
|
||||
for (Entry<TraceAddressSnapRange, T> entry : reduce(
|
||||
TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) {
|
||||
makeWay(entry, span);
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,6 +121,211 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DBTracePropertyMapSpace createSpace(AddressSpace space,
|
||||
DBTraceSpaceEntry ent) throws VersionException, IOException {
|
||||
return new DBTracePropertyMapSpace(
|
||||
tableName(space, ent.getThreadKey(), ent.getFrameLevel()), trace.getStoreFactory(),
|
||||
lock, space, dataType, dataFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DBTracePropertyMapRegisterSpace createRegisterSpace(
|
||||
AddressSpace space, TraceThread thread, DBTraceSpaceEntry ent)
|
||||
throws VersionException, IOException {
|
||||
return new DBTracePropertyMapRegisterSpace(
|
||||
tableName(space, ent.getThreadKey(), ent.getFrameLevel()), trace.getStoreFactory(),
|
||||
lock, space, thread, ent.getFrameLevel(), dataType, dataFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePropertyMapSpace<T> getPropertyMapSpace(AddressSpace space,
|
||||
boolean createIfAbsent) {
|
||||
return (DBTracePropertyMapSpace) getForSpace(space, createIfAbsent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePropertyMapRegisterSpace<T> getPropertyMapRegisterSpace(TraceThread thread,
|
||||
int frameLevel, boolean createIfAbsent) {
|
||||
return (DBTracePropertyMapRegisterSpace) getForRegisterSpace(thread, frameLevel,
|
||||
createIfAbsent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
throw new NotYetImplementedException();
|
||||
}
|
||||
|
||||
public class DBTracePropertyMapSpace
|
||||
extends DBTraceAddressSnapRangePropertyMapSpace<T, DR>
|
||||
implements TracePropertyMapSpace<T> {
|
||||
|
||||
public DBTracePropertyMapSpace(String tableName, DBCachedObjectStoreFactory storeFactory,
|
||||
ReadWriteLock lock, AddressSpace space, Class<DR> dataType,
|
||||
DBTraceAddressSnapRangePropertyMapDataFactory<T, DR> dataFactory)
|
||||
throws VersionException, IOException {
|
||||
super(tableName, storeFactory, lock, space, dataType, dataFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getValueClass() {
|
||||
return AbstractDBTracePropertyMap.this.getValueClass();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void makeWay(Entry<TraceAddressSnapRange, T> entry, Range<Long> span) {
|
||||
// TODO: Would rather not rely on implementation knowledge here
|
||||
// The shape is the database record in AbstractDBTraceAddressSnapRangePropertyMapData
|
||||
makeWay((DR) entry.getKey(), span);
|
||||
}
|
||||
|
||||
protected void makeWay(DR data, Range<Long> span) {
|
||||
DBTraceUtils.makeWay(data, span, (d, s) -> d.doSetLifespan(s), d -> deleteData(d));
|
||||
// TODO: Any events?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Range<Long> lifespan, Address address, T value) {
|
||||
put(address, lifespan, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Range<Long> lifespan, AddressRange range, T value) {
|
||||
put(range, lifespan, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long snap, Address address) {
|
||||
return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<TraceAddressSnapRange, T> getEntry(long snap, Address address) {
|
||||
return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstEntry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Entry<TraceAddressSnapRange, T>> getEntries(Range<Long> lifespan,
|
||||
AddressRange range) {
|
||||
return reduce(TraceAddressSnapRangeQuery.intersecting(range, lifespan)).entries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clear(Range<Long> span, AddressRange range) {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
boolean result = false;
|
||||
for (Entry<TraceAddressSnapRange, T> entry : reduce(
|
||||
TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) {
|
||||
makeWay(entry, span);
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T put(TraceAddressSnapRange shape, T value) {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
for (Entry<TraceAddressSnapRange, T> entry : reduce(
|
||||
TraceAddressSnapRangeQuery.intersecting(shape)).entries()) {
|
||||
makeWay(entry, shape.getLifespan());
|
||||
}
|
||||
return super.put(shape, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DBTracePropertyMapRegisterSpace
|
||||
extends DBTraceAddressSnapRangePropertyMapRegisterSpace<T, DR>
|
||||
implements TracePropertyMapRegisterSpace<T> {
|
||||
|
||||
public DBTracePropertyMapRegisterSpace(String tableName,
|
||||
DBCachedObjectStoreFactory storeFactory, ReadWriteLock lock, AddressSpace space,
|
||||
TraceThread thread, int frameLevel, Class<DR> dataType,
|
||||
DBTraceAddressSnapRangePropertyMapDataFactory<T, DR> dataFactory)
|
||||
throws VersionException, IOException {
|
||||
super(tableName, storeFactory, lock, space, thread, frameLevel, dataType, dataFactory);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getValueClass() {
|
||||
return AbstractDBTracePropertyMap.this.getValueClass();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void makeWay(Entry<TraceAddressSnapRange, T> entry, Range<Long> span) {
|
||||
// TODO: Would rather not rely on implementation knowledge here
|
||||
// The shape is the database record in AbstractDBTraceAddressSnapRangePropertyMapData
|
||||
makeWay((DR) entry.getKey(), span);
|
||||
}
|
||||
|
||||
protected void makeWay(DR data, Range<Long> span) {
|
||||
DBTraceUtils.makeWay(data, span, (d, s) -> d.doSetLifespan(s), d -> deleteData(d));
|
||||
// TODO: Any events?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Range<Long> lifespan, Address address, T value) {
|
||||
put(address, lifespan, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Range<Long> lifespan, AddressRange range, T value) {
|
||||
put(range, lifespan, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long snap, Address address) {
|
||||
return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<TraceAddressSnapRange, T> getEntry(long snap, Address address) {
|
||||
return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstEntry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Entry<TraceAddressSnapRange, T>> getEntries(Range<Long> lifespan,
|
||||
AddressRange range) {
|
||||
return reduce(TraceAddressSnapRangeQuery.intersecting(range, lifespan)).entries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clear(Range<Long> span, AddressRange range) {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
boolean result = false;
|
||||
for (Entry<TraceAddressSnapRange, T> entry : reduce(
|
||||
TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) {
|
||||
makeWay(entry, span);
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T put(TraceAddressSnapRange shape, T value) {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
for (Entry<TraceAddressSnapRange, T> entry : reduce(
|
||||
TraceAddressSnapRangeQuery.intersecting(shape)).entries()) {
|
||||
makeWay(entry, shape.getLifespan());
|
||||
}
|
||||
return super.put(shape, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class DBTraceIntPropertyMap
|
||||
extends AbstractDBTracePropertyMap<Integer, DBTraceIntPropertyMapEntry> {
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ import ghidra.trace.model.listing.*;
|
|||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.program.TraceProgramViewListing;
|
||||
import ghidra.trace.model.property.TracePropertyMap;
|
||||
import ghidra.trace.model.property.TracePropertyMapOperations;
|
||||
import ghidra.trace.model.symbol.TraceFunctionSymbol;
|
||||
import ghidra.trace.util.*;
|
||||
import ghidra.util.*;
|
||||
|
@ -361,7 +361,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
// TODO: Other "special" property types
|
||||
|
||||
// TODO: Cover this in testing
|
||||
TracePropertyMap<?> map =
|
||||
TracePropertyMapOperations<?> map =
|
||||
program.trace.getInternalAddressPropertyManager().getPropertyMap(property);
|
||||
if (map == null) {
|
||||
return new WrappingCodeUnitIterator(Collections.emptyIterator());
|
||||
|
@ -383,7 +383,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
// TODO: Other "special" property types
|
||||
|
||||
// TODO: Cover this in testing
|
||||
TracePropertyMap<?> map =
|
||||
TracePropertyMapOperations<?> map =
|
||||
program.trace.getInternalAddressPropertyManager().getPropertyMap(property);
|
||||
if (map == null) {
|
||||
return new WrappingCodeUnitIterator(Collections.emptyIterator());
|
||||
|
@ -406,7 +406,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
// TODO: Other "special" property types
|
||||
|
||||
// TODO: Cover this in testing
|
||||
TracePropertyMap<?> map =
|
||||
TracePropertyMapOperations<?> map =
|
||||
program.trace.getInternalAddressPropertyManager().getPropertyMap(property);
|
||||
if (map == null) {
|
||||
return new WrappingCodeUnitIterator(Collections.emptyIterator());
|
||||
|
|
|
@ -17,112 +17,435 @@ package ghidra.trace.database.program;
|
|||
|
||||
import java.util.Iterator;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.util.*;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.model.property.TracePropertyMap;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.Saveable;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.prop.PropertyVisitor;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DBTraceProgramViewPropertyMapManager implements PropertyMapManager {
|
||||
protected final DBTraceProgramView program;
|
||||
|
||||
protected abstract class AbstractDBTraceProgramViewPropertyMap<T> implements PropertyMap {
|
||||
protected final TracePropertyMap<T> map;
|
||||
protected final String name;
|
||||
|
||||
public AbstractDBTraceProgramViewPropertyMap(TracePropertyMap<T> map, String name) {
|
||||
this.map = map;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
protected AddressSetView getAddressSetView() {
|
||||
return map.getAddressSetView(Range.singleton(program.snap));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersects(Address start, Address end) {
|
||||
return getAddressSetView().intersects(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersects(AddressSetView set) {
|
||||
return getAddressSetView().intersects(set);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeRange(Address start, Address end) {
|
||||
return map.clear(Range.singleton(program.snap), new AddressRangeImpl(start, end));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Address addr) {
|
||||
return removeRange(addr, addr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasProperty(Address addr) {
|
||||
return intersects(addr, addr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getObject(Address addr) {
|
||||
return map.get(program.snap, addr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getNextPropertyAddress(Address addr) {
|
||||
Address next = addr.next();
|
||||
if (next == null) {
|
||||
return null;
|
||||
}
|
||||
AddressRangeIterator it = getAddressSetView().getAddressRanges(next, true);
|
||||
if (!it.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
AddressRange range = it.next();
|
||||
if (!range.contains(next)) {
|
||||
return next;
|
||||
}
|
||||
return range.getMinAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getPreviousPropertyAddress(Address addr) {
|
||||
Address prev = addr.previous();
|
||||
if (prev == null) {
|
||||
return null;
|
||||
}
|
||||
AddressRangeIterator it = getAddressSetView().getAddressRanges(prev, false);
|
||||
if (!it.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
AddressRange range = it.next();
|
||||
if (!range.contains(prev)) {
|
||||
return prev;
|
||||
}
|
||||
return range.getMaxAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getFirstPropertyAddress() {
|
||||
return getAddressSetView().getMinAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getLastPropertyAddress() {
|
||||
return getAddressSetView().getMaxAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return (int) getAddressSetView().getNumAddresses();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getPropertyIterator(Address start, Address end) {
|
||||
return getPropertyIterator(start, end, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getPropertyIterator(Address start, Address end, boolean forward) {
|
||||
return getAddressSetView().intersectRange(start, end).getAddresses(forward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getPropertyIterator() {
|
||||
return getAddressSetView().getAddresses(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getPropertyIterator(AddressSetView asv) {
|
||||
return getPropertyIterator(asv, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getPropertyIterator(AddressSetView asv, boolean forward) {
|
||||
return getAddressSetView().intersect(asv).getAddresses(forward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getPropertyIterator(Address start, boolean forward) {
|
||||
return getAddressSetView().getAddresses(start, forward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveRange(Address start, Address end, Address newStart) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
protected class DBTraceProgramViewIntPropertyMap
|
||||
extends AbstractDBTraceProgramViewPropertyMap<Integer> implements IntPropertyMap {
|
||||
|
||||
public DBTraceProgramViewIntPropertyMap(TracePropertyMap<Integer> map, String name) {
|
||||
super(map, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyValue(PropertyVisitor visitor, Address addr) {
|
||||
Integer value = getObject(addr);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
visitor.visit(value.intValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Address addr, int value) {
|
||||
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(Address addr) throws NoValueException {
|
||||
Integer value = getObject(addr);
|
||||
if (value == null) {
|
||||
throw new NoValueException();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
protected class DBTraceProgramViewLongPropertyMap
|
||||
extends AbstractDBTraceProgramViewPropertyMap<Long> implements LongPropertyMap {
|
||||
|
||||
public DBTraceProgramViewLongPropertyMap(TracePropertyMap<Long> map, String name) {
|
||||
super(map, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyValue(PropertyVisitor visitor, Address addr) {
|
||||
Long value = getObject(addr);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
// TODO: In program, this throws NotYetImplemented....
|
||||
visitor.visit(value.longValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Address addr, long value) {
|
||||
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(Address addr) throws NoValueException {
|
||||
Long value = getObject(addr);
|
||||
if (value == null) {
|
||||
throw new NoValueException();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
protected class DBTraceProgramViewStringPropertyMap
|
||||
extends AbstractDBTraceProgramViewPropertyMap<String> implements StringPropertyMap {
|
||||
|
||||
public DBTraceProgramViewStringPropertyMap(TracePropertyMap<String> map, String name) {
|
||||
super(map, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyValue(PropertyVisitor visitor, Address addr) {
|
||||
String value = getObject(addr);
|
||||
visitor.visit(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Address addr, String value) {
|
||||
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(Address addr) {
|
||||
return getObject(addr);
|
||||
}
|
||||
}
|
||||
|
||||
protected class DBTraceProgramViewObjectPropertyMap<T extends Saveable>
|
||||
extends AbstractDBTraceProgramViewPropertyMap<T> implements ObjectPropertyMap {
|
||||
|
||||
public DBTraceProgramViewObjectPropertyMap(TracePropertyMap<T> map, String name) {
|
||||
super(map, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyValue(PropertyVisitor visitor, Address addr) {
|
||||
Saveable value = getObject(addr);
|
||||
visitor.visit(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Address addr, Saveable value) {
|
||||
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr,
|
||||
map.getValueClass().cast(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectClass() {
|
||||
return map.getValueClass();
|
||||
}
|
||||
}
|
||||
|
||||
protected class DBTraceProgramViewVoidPropertyMap
|
||||
extends AbstractDBTraceProgramViewPropertyMap<Void> implements VoidPropertyMap {
|
||||
|
||||
public DBTraceProgramViewVoidPropertyMap(TracePropertyMap<Void> map, String name) {
|
||||
super(map, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyValue(PropertyVisitor visitor, Address addr) {
|
||||
if (!hasProperty(addr)) {
|
||||
return;
|
||||
}
|
||||
visitor.visit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Address addr) {
|
||||
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, null);
|
||||
}
|
||||
}
|
||||
|
||||
public DBTraceProgramViewPropertyMapManager(DBTraceProgramView program) {
|
||||
this.program = program;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntPropertyMap createIntPropertyMap(String propertyName) throws DuplicateNameException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return new DBTraceProgramViewIntPropertyMap(program.trace.getAddressPropertyManager()
|
||||
.createPropertyMap(propertyName, Integer.class),
|
||||
propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LongPropertyMap createLongPropertyMap(String propertyName)
|
||||
throws DuplicateNameException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return new DBTraceProgramViewLongPropertyMap(program.trace.getAddressPropertyManager()
|
||||
.createPropertyMap(propertyName, Long.class),
|
||||
propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringPropertyMap createStringPropertyMap(String propertyName)
|
||||
throws DuplicateNameException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return new DBTraceProgramViewStringPropertyMap(program.trace.getAddressPropertyManager()
|
||||
.createPropertyMap(propertyName, String.class),
|
||||
propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectPropertyMap createObjectPropertyMap(String propertyName,
|
||||
Class<? extends Saveable> objectClass) throws DuplicateNameException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return new DBTraceProgramViewObjectPropertyMap<>(program.trace.getAddressPropertyManager()
|
||||
.createPropertyMap(propertyName, objectClass),
|
||||
propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidPropertyMap createVoidPropertyMap(String propertyName)
|
||||
throws DuplicateNameException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return new DBTraceProgramViewVoidPropertyMap(program.trace.getAddressPropertyManager()
|
||||
.createPropertyMap(propertyName, Void.class),
|
||||
propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public PropertyMap getPropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
TracePropertyMap<?> map =
|
||||
program.trace.getAddressPropertyManager().getPropertyMap(propertyName);
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
Class<?> cls = map.getValueClass();
|
||||
if (cls == Integer.class) {
|
||||
return new DBTraceProgramViewIntPropertyMap((TracePropertyMap<Integer>) map,
|
||||
propertyName);
|
||||
}
|
||||
if (cls == Long.class) {
|
||||
return new DBTraceProgramViewLongPropertyMap((TracePropertyMap<Long>) map,
|
||||
propertyName);
|
||||
}
|
||||
if (cls == String.class) {
|
||||
return new DBTraceProgramViewStringPropertyMap((TracePropertyMap<String>) map,
|
||||
propertyName);
|
||||
}
|
||||
if (cls == Void.class) {
|
||||
return new DBTraceProgramViewVoidPropertyMap((TracePropertyMap<Void>) map,
|
||||
propertyName);
|
||||
}
|
||||
if (Saveable.class.isAssignableFrom(cls)) {
|
||||
return new DBTraceProgramViewObjectPropertyMap<>(
|
||||
(TracePropertyMap<? extends Saveable>) map, propertyName);
|
||||
}
|
||||
throw new AssertionError("Where did this property map type come from? " + cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntPropertyMap getIntPropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
TracePropertyMap<Integer> map = program.trace.getAddressPropertyManager()
|
||||
.getPropertyMap(propertyName, Integer.class);
|
||||
return map == null ? null : new DBTraceProgramViewIntPropertyMap(map, propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LongPropertyMap getLongPropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
TracePropertyMap<Long> map = program.trace.getAddressPropertyManager()
|
||||
.getPropertyMap(propertyName, Long.class);
|
||||
return map == null ? null : new DBTraceProgramViewLongPropertyMap(map, propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringPropertyMap getStringPropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
TracePropertyMap<String> map = program.trace.getAddressPropertyManager()
|
||||
.getPropertyMap(propertyName, String.class);
|
||||
return map == null ? null : new DBTraceProgramViewStringPropertyMap(map, propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public ObjectPropertyMap getObjectPropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
TracePropertyMap<?> map =
|
||||
program.trace.getAddressPropertyManager().getPropertyMap(propertyName);
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
if (!Saveable.class.isAssignableFrom(map.getValueClass())) {
|
||||
throw new TypeMismatchException("Property " + propertyName + " is not object type");
|
||||
}
|
||||
return new DBTraceProgramViewObjectPropertyMap<>((TracePropertyMap<? extends Saveable>) map,
|
||||
propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidPropertyMap getVoidPropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
TracePropertyMap<Void> map = program.trace.getAddressPropertyManager()
|
||||
.getPropertyMap(propertyName, Void.class);
|
||||
return map == null ? null : new DBTraceProgramViewVoidPropertyMap(map, propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removePropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
// It would delete for entire trace, not just this view
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> propertyManagers() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return program.trace.getAddressPropertyManager().getAllProperties().keySet().iterator();
|
||||
}
|
||||
|
||||
protected void removeAll(Range<Long> span, AddressRange range) {
|
||||
try (LockHold hold = program.trace.lockWrite()) {
|
||||
for (TracePropertyMap<?> map : program.trace.getAddressPropertyManager()
|
||||
.getAllProperties()
|
||||
.values()) {
|
||||
map.clear(span, range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll(Address addr) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
removeAll(DBTraceUtils.atLeastMaybeScratch(program.snap), new AddressRangeImpl(addr, addr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll(Address startAddr, Address endAddr, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
removeAll(DBTraceUtils.atLeastMaybeScratch(program.snap),
|
||||
new AddressRangeImpl(startAddr, endAddr));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,8 @@ import ghidra.trace.database.DBTraceManager;
|
|||
import ghidra.trace.database.map.AbstractDBTracePropertyMap;
|
||||
import ghidra.trace.database.map.AbstractDBTracePropertyMap.*;
|
||||
import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||
import ghidra.trace.model.property.*;
|
||||
import ghidra.trace.model.property.TraceAddressPropertyManager;
|
||||
import ghidra.trace.model.property.TracePropertyMap;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.annot.*;
|
||||
|
@ -197,6 +198,23 @@ public class DBTraceAddressPropertyManager implements TraceAddressPropertyManage
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> TracePropertyMap<? extends T> getPropertyMapExtends(String name,
|
||||
Class<T> valueClass) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
AbstractDBTracePropertyMap<?, ?> map = propertyMapsByName.get(name);
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
if (!valueClass.isAssignableFrom(map.getValueClass())) {
|
||||
throw new TypeMismatchException("Property " + name + " has type " +
|
||||
map.getValueClass() + ", which does not extend " + valueClass);
|
||||
}
|
||||
return (TracePropertyMap<? extends T>) map;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> AbstractDBTracePropertyMap<T, ?> getOrCreatePropertyMap(String name,
|
||||
Class<T> valueClass) {
|
||||
|
@ -216,23 +234,7 @@ public class DBTraceAddressPropertyManager implements TraceAddressPropertyManage
|
|||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> TracePropertyGetter<T> getPropertyGetter(String name, Class<T> valueClass) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
AbstractDBTracePropertyMap<?, ?> map = propertyMapsByName.get(name);
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
if (!valueClass.isAssignableFrom(map.getValueClass())) {
|
||||
throw new TypeMismatchException("Property " + name + " has type " +
|
||||
map.getValueClass() + ", which does not extend " + valueClass);
|
||||
}
|
||||
return (TracePropertyGetter<T>) map;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> TracePropertySetter<T> getOrCreatePropertySetter(String name,
|
||||
public <T> TracePropertyMap<? super T> getOrCreatePropertyMapSuper(String name,
|
||||
Class<T> valueClass) {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
AbstractDBTracePropertyMap<?, ?> map = propertyMapsByName.get(name);
|
||||
|
@ -248,7 +250,7 @@ public class DBTraceAddressPropertyManager implements TraceAddressPropertyManage
|
|||
throw new TypeMismatchException("Property " + name + " has type " +
|
||||
map.getValueClass() + ", which is not a super-type of " + valueClass);
|
||||
}
|
||||
return (TracePropertyMap<T>) map;
|
||||
return (TracePropertyMap<? super T>) map;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@ import java.util.Map;
|
|||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.trace.model.property.*;
|
||||
import ghidra.trace.model.property.TraceAddressPropertyManager;
|
||||
import ghidra.trace.model.property.TracePropertyMap;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
class DBTraceAddressPropertyManagerApiView implements TraceAddressPropertyManager {
|
||||
|
@ -43,20 +44,21 @@ class DBTraceAddressPropertyManagerApiView implements TraceAddressPropertyManage
|
|||
}
|
||||
|
||||
@Override
|
||||
public <T> TracePropertyMap<T> getOrCreatePropertyMap(String name, Class<T> valueClass) {
|
||||
public <T> TracePropertyMap<? extends T> getPropertyMapExtends(String name,
|
||||
Class<T> valueClass) {
|
||||
return internalView.getPropertyMapExtends(API_PREFIX + name, valueClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TracePropertyMap<T> getOrCreatePropertyMap(String name,
|
||||
Class<T> valueClass) {
|
||||
return internalView.getOrCreatePropertyMap(API_PREFIX + name, valueClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TracePropertyGetter<T> getPropertyGetter(String name,
|
||||
public <T> TracePropertyMap<? super T> getOrCreatePropertyMapSuper(String name,
|
||||
Class<T> valueClass) {
|
||||
return internalView.getPropertyGetter(API_PREFIX + name, valueClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TracePropertySetter<T> getOrCreatePropertySetter(String name,
|
||||
Class<T> valueClass) {
|
||||
return internalView.getOrCreatePropertySetter(API_PREFIX + name, valueClass);
|
||||
return internalView.getOrCreatePropertyMapSuper(API_PREFIX + name, valueClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -21,6 +21,13 @@ import ghidra.program.model.util.TypeMismatchException;
|
|||
import ghidra.util.Saveable;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
/**
|
||||
* The manager for user properties of a trace
|
||||
*
|
||||
* <p>
|
||||
* Clients may create property maps of various value types. Each map is named, also considered the
|
||||
* "property name," and can be retrieve by that name.
|
||||
*/
|
||||
public interface TraceAddressPropertyManager {
|
||||
/**
|
||||
* Create a property map with the given name having the given type
|
||||
|
@ -59,6 +66,16 @@ public interface TraceAddressPropertyManager {
|
|||
*/
|
||||
<T> TracePropertyMap<T> getPropertyMap(String name, Class<T> valueClass);
|
||||
|
||||
/**
|
||||
* Get the property map with the given name, if its values extend the given type
|
||||
*
|
||||
* @param name the name
|
||||
* @param valueClass the expected type of values
|
||||
* @return the property map, or null if it does not exist
|
||||
* @throws TypeMismatchException if it exists but does not have the expected type
|
||||
*/
|
||||
<T> TracePropertyMap<? extends T> getPropertyMapExtends(String name, Class<T> valueClass);
|
||||
|
||||
/**
|
||||
* Get the property map with the given name, creating it if necessary, of the given type
|
||||
*
|
||||
|
@ -70,23 +87,17 @@ public interface TraceAddressPropertyManager {
|
|||
<T> TracePropertyMap<T> getOrCreatePropertyMap(String name, Class<T> valueClass);
|
||||
|
||||
/**
|
||||
* Get the property map with the given name, if its type extends the given type
|
||||
* Get the property map with the given name, creating it if necessary, of the given type
|
||||
*
|
||||
* @param name the name
|
||||
* @param valueClass the expected type of values to get
|
||||
* @return the property map suitable for getting values of the given type
|
||||
*/
|
||||
<T> TracePropertyGetter<T> getPropertyGetter(String name, Class<T> valueClass);
|
||||
|
||||
/**
|
||||
* Get the property map with the given name, if its type is a super-type of the given type
|
||||
* <p>
|
||||
* If the map already exists, then its values' type must be a super type of that given.
|
||||
*
|
||||
* @see #createPropertyMap(String, Class)
|
||||
* @see #getOrCreatePropertyMap(String, Class)
|
||||
* @param name the name
|
||||
* @param valueClass the expected type of values to set
|
||||
* @return the property map suitable for setting values of the given type
|
||||
* @param valueClass the expected type of values
|
||||
* @return the (possibly new) property map
|
||||
*/
|
||||
<T> TracePropertySetter<T> getOrCreatePropertySetter(String name, Class<T> valueClass);
|
||||
<T> TracePropertyMap<? super T> getOrCreatePropertyMapSuper(String name, Class<T> valueClass);
|
||||
|
||||
/**
|
||||
* Get the property map with the given name.
|
||||
|
@ -94,8 +105,8 @@ public interface TraceAddressPropertyManager {
|
|||
* <p>
|
||||
* Note that no type checking is performed (there is no {@code valueClass} parameter). Thus, the
|
||||
* returned map is suitable only for clearing and querying where the property is present. The
|
||||
* caller may perform run-time type checking via the {@link TracePropertyMap#getValueClass()}
|
||||
* method.
|
||||
* caller may perform run-time type checking via the
|
||||
* {@link TracePropertyMapOperations#getValueClass()} method.
|
||||
*
|
||||
* @param name the name
|
||||
* @return the property map
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.trace.model.property;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
|
||||
public interface TracePropertyGetter<T> {
|
||||
/**
|
||||
* Get the class for values of the map
|
||||
*
|
||||
* @return the value class
|
||||
*/
|
||||
Class<? extends T> getValueClass();
|
||||
|
||||
/**
|
||||
* Get the value at the given address-snap pair
|
||||
*
|
||||
* @param snap the snap
|
||||
* @param address the address
|
||||
* @return the value
|
||||
*/
|
||||
T get(long snap, Address address);
|
||||
|
||||
/**
|
||||
* Get the union of address ranges for entries which intersect the given span
|
||||
*
|
||||
* @param span the range of snaps
|
||||
* @return the address set
|
||||
*/
|
||||
AddressSetView getAddressSetView(Range<Long> span);
|
||||
}
|
|
@ -15,34 +15,82 @@
|
|||
*/
|
||||
package ghidra.trace.model.property;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.trace.model.stack.TraceStackFrame;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
|
||||
/**
|
||||
* A map from address-snap pairs to user-defined values in a {@link Trace}
|
||||
* A range map for storing properties in a trace
|
||||
*
|
||||
* <p>
|
||||
* Technically, each range is actually a "box" in two dimensions: time and space. Time is
|
||||
* represented by the span of snapshots covered, and space is represented by the range of addresses
|
||||
* covered. Currently, no effort is made to optimize coverage for entries having the same value. For
|
||||
* operations on entries, see {@link TracePropertyMapOperations}.
|
||||
*
|
||||
* <p>
|
||||
* This interface is the root of a multi-space property map. For memory spaces, clients can
|
||||
* generally use the operations inherited on this interface. For register spaces, clients must use
|
||||
* {@link #getPropertyMapRegisterSpace(TraceThread, int, boolean)} or similar.
|
||||
*
|
||||
* @param <T> the type of values
|
||||
*/
|
||||
public interface TracePropertyMap<T> extends TracePropertySetter<T>, TracePropertyGetter<T> {
|
||||
public interface TracePropertyMap<T> extends TracePropertyMapOperations<T> {
|
||||
/**
|
||||
* Get the class for values of the map
|
||||
* Get the map space for the given address space
|
||||
*
|
||||
* @return the value class
|
||||
* @param space the address space
|
||||
* @param createIfAbsent true to create the map space if it doesn't already exist
|
||||
* @return the space, or null
|
||||
*/
|
||||
@Override
|
||||
Class<T> getValueClass();
|
||||
TracePropertyMapSpace<T> getPropertyMapSpace(AddressSpace space, boolean createIfAbsent);
|
||||
|
||||
/**
|
||||
* Get the entry at the given address-snap pair
|
||||
* Get the map space for the registers of a given thread and frame
|
||||
*
|
||||
* @param thread the thread
|
||||
* @param frameLevel the frame level, 0 being the innermost
|
||||
* @param createIfAbsent true to create the map space if it doesn't already exist
|
||||
* @return the space, or null
|
||||
*/
|
||||
TracePropertyMapRegisterSpace<T> getPropertyMapRegisterSpace(TraceThread thread, int frameLevel,
|
||||
boolean createIfAbsent);
|
||||
|
||||
/**
|
||||
* Get the map space for the registers of a given frame (which knows its thread)
|
||||
*
|
||||
* @param frame the frame
|
||||
* @param createIfAbsent true to create the map space if it doesn't already exist
|
||||
* @return the space, or null
|
||||
*/
|
||||
default TracePropertyMapRegisterSpace<T> getPropertyMapRegisterSpace(TraceStackFrame frame,
|
||||
boolean createIfAbsent) {
|
||||
return getPropertyMapRegisterSpace(frame.getStack().getThread(), frame.getLevel(),
|
||||
createIfAbsent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the map space for the given trace space
|
||||
*
|
||||
* @param traceSpace the trace space, giving the memory space or thread/frame register space
|
||||
* @param createIfAbsent true to create the map space if it doesn't already exist
|
||||
* @return the space, or null
|
||||
*/
|
||||
default TracePropertyMapSpace<T> getPropertyMapSpace(TraceAddressSpace traceSpace,
|
||||
boolean createIfAbsent) {
|
||||
if (traceSpace.getAddressSpace().isRegisterSpace()) {
|
||||
return getPropertyMapRegisterSpace(traceSpace.getThread(), traceSpace.getFrameLevel(),
|
||||
createIfAbsent);
|
||||
}
|
||||
return getPropertyMapSpace(traceSpace.getAddressSpace(), createIfAbsent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this property and remove all of its maps
|
||||
*
|
||||
* <p>
|
||||
* Because there exists {@link Map.Entry#setValue(Object)}, this method cannot be in
|
||||
* {@link TracePropertyGetter}.
|
||||
*
|
||||
* @param snap the snap
|
||||
* @param address the address
|
||||
* @return the entry, which includes the ranges and the value
|
||||
* The property can be re-created with the same or different value type.
|
||||
*/
|
||||
Map.Entry<TraceAddressSnapRange, T> getEntry(long snap, Address address);
|
||||
void delete();
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue